diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index aa49696..bef1a99 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -1026,6 +1026,101 @@ body, button { font-size: 12px; } +/* Remove spin button from number inputs. */ +#settingsContainer input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +/* Default styles for text/number inputs. */ +#settingsContainer input[type=number], +#settingsContainer input[type=text] { + color: white; + background: rgba(0, 0, 0, 0.25); + border-radius: 3px; + border: 1px solid rgba(126, 126, 126, 0.57); + font-family: 'Avenir Book'; + transition: 0.25s ease; +} +#settingsContainer input[type=number]:focus, +#settingsContainer input[type=text]:focus { + outline: none; + border-color: rgba(126, 126, 126, 0.87); +} +#settingsContainer input[type=number][error] { + border-color: rgb(255, 27, 12); + background: rgba(236, 0, 0, 0.25); + color: rgb(255, 27, 12); +} + +/* Styles for a generic settings entry. */ +.settingsFieldContainer { + display: flex; + align-items: center; + justify-content: space-between; + margin: 20px 0px; + width: 75%; +} +.settingsFieldLeft { + display: flex; + flex-direction: column; +} +.settingsFieldTitle { + font-size: 14px; + font-family: 'Avenir Medium'; + color: rgba(255, 255, 255, 0.95); +} +.settingsFieldDesc { + font-size: 12px; + color: rgba(255, 255, 255, .95); +} +.settingsDivider { + height: 1px; + width: 75%; + background: rgba(255, 255, 255, 0.25); +} + +/* Toggle Switch */ +.toggleSwitch { + position: relative; + display: inline-block; + width: 50px; + height: 25px; + border-radius: 3px; + box-sizing: border-box; +} +.toggleSwitch input { + display:none; +} +.toggleSwitchSlider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.25); + transition: .4s; + border-radius: 3px; + border: 1px solid rgba(126, 126, 126, 0.57); +} +.toggleSwitchSlider:before { + position: absolute; + content: ""; + height: 17px; + width: 21px; + left: 3px; + bottom: 3px; + background-color: rgb(202, 202, 202); + border-radius: 3px; + transition: .4s; +} +input:checked + .toggleSwitchSlider { + background-color: rgba(36, 255, 0, 0.25); +} +input:checked + .toggleSwitchSlider:before { + transform: translateX(21px); +} + /* * * * Settings View (Account Tab) * * */ @@ -1056,9 +1151,6 @@ body, button { #settingsCurrentAccountsHeader { margin: 20px 0px; } -#settingsCurrentAccountsHeaderText { - font-size: 16px; -} /* Auth account list container styles. */ #settingsCurrentAccounts { @@ -1183,6 +1275,31 @@ body, button { opacity: 1; } +/* * * +* Settings View (Minecraft Tab) +* * */ + +/* Game resolution UI elements. */ +#settingsGameResolutionContainer { + display: flex; + flex-direction: column; + margin: 20px 0px; +} +#settingsGameResolutionContent { + display: flex; + align-items: center; + padding-top: 10px; +} +#settingsGameResolutionCross { + color: grey; + padding: 0px 15px; +} +#settingsGameWidth, +#settingsGameHeight { + padding: 7.5px 5px; + width: 75px; +} + /******************************************************************************* * * * Landing View (Structural Styles) * diff --git a/app/assets/js/assetguard.js b/app/assets/js/assetguard.js index cf941dd..8c4c865 100644 --- a/app/assets/js/assetguard.js +++ b/app/assets/js/assetguard.js @@ -1488,11 +1488,9 @@ class AssetGuard extends EventEmitter { obPath = path.join(this.commonPath, 'libraries', obPath) break case 'forgemod': - //obPath = path.join(this.basePath, 'mods', obPath) obPath = path.join(this.commonPath, 'modstore', obPath) break case 'litemod': - //obPath = path.join(this.basePath, 'mods', version, obPath) obPath = path.join(this.commonPath, 'modstore', obPath) break case 'file': diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index dfabbc0..8343782 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -394,25 +394,6 @@ exports.setJVMOptions = function(jvmOptions){ // Game Settings -/** - * Retrieve the absolute path of the game directory. - * - * @param {boolean} def Optional. If true, the default value will be returned. - * @returns {string} The absolute path of the game directory. - */ -exports.getGameDirectory = function(def = false){ - return !def ? config.settings.game.directory : DEFAULT_CONFIG.settings.game.directory -} - -/** - * Set the absolute path of the game directory. - * - * @param {string} directory The absolute path of the new game directory. - */ -exports.setGameDirectory = function(directory){ - config.settings.game.directory = directory -} - /** * Retrieve the width of the game window. * @@ -429,7 +410,17 @@ exports.getGameWidth = function(def = false){ * @param {number} resWidth The new width of the game window. */ exports.setGameWidth = function(resWidth){ - config.settings.game.resWidth = resWidth + config.settings.game.resWidth = Number.parseInt(resWidth) +} + +/** + * Validate a potential new width value. + * + * @param {*} resWidth The width value to validate. + */ +exports.validateGameWidth = function(resWidth){ + const nVal = Number.parseInt(resWidth) + return Number.isInteger(nVal) && nVal >= 0 } /** @@ -448,7 +439,17 @@ exports.getGameHeight = function(def = false){ * @param {number} resHeight The new height of the game window. */ exports.setGameHeight = function(resHeight){ - config.settings.game.resHeight = resHeight + config.settings.game.resHeight = Number.parseInt(resHeight) +} + +/** + * Validate a potential new height value. + * + * @param {*} resHeight The height value to validate. + */ +exports.validateGameHeight = function(resHeight){ + const nVal = Number.parseInt(resHeight) + return Number.isInteger(nVal) && nVal >= 0 } /** @@ -457,7 +458,7 @@ exports.setGameHeight = function(resHeight){ * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game is set to launch in fullscreen mode. */ -exports.isFullscreen = function(def = false){ +exports.getFullscreen = function(def = false){ return !def ? config.settings.game.fullscreen : DEFAULT_CONFIG.settings.game.fullscreen } @@ -476,7 +477,7 @@ exports.setFullscreen = function(fullscreen){ * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game should auto connect to servers. */ -exports.isAutoConnect = function(def = false){ +exports.getAutoConnect = function(def = false){ return !def ? config.settings.game.autoConnect : DEFAULT_CONFIG.settings.game.autoConnect } @@ -495,7 +496,7 @@ exports.setAutoConnect = function(autoConnect){ * @param {boolean} def Optional. If true, the default value will be returned. * @returns {boolean} Whether or not the game will launch as a detached process. */ -exports.isLaunchDetached = function(def = false){ +exports.getLaunchDetached = function(def = false){ return !def ? config.settings.game.launchDetached : DEFAULT_CONFIG.settings.game.launchDetached } diff --git a/app/assets/js/processbuilder.js b/app/assets/js/processbuilder.js index 1a7491a..8bb12f0 100644 --- a/app/assets/js/processbuilder.js +++ b/app/assets/js/processbuilder.js @@ -47,10 +47,10 @@ class ProcessBuilder { const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, { cwd: this.gameDir, - detached: ConfigManager.isLaunchDetached() + detached: ConfigManager.getLaunchDetached() }) - if(ConfigManager.isLaunchDetached()){ + if(ConfigManager.getLaunchDetached()){ child.unref() } @@ -188,7 +188,7 @@ class ProcessBuilder { mcArgs.push('absolute:' + this.fmlDir) // Prepare game resolution - if(ConfigManager.isFullscreen()){ + if(ConfigManager.getFullscreen()){ mcArgs.unshift('--fullscreen') } else { mcArgs.unshift(ConfigManager.getGameWidth()) @@ -198,7 +198,7 @@ class ProcessBuilder { } // Prepare autoconnect - if(ConfigManager.isAutoConnect() && this.server.autoconnect){ + if(ConfigManager.getAutoConnect() && this.server.autoconnect){ const serverURL = new URL('my://' + this.server.server_ip) mcArgs.unshift(serverURL.hostname) mcArgs.unshift('--server') diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 1dcbd98..ebd7f5f 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -1,11 +1,97 @@ const settingsNavDone = document.getElementById('settingsNavDone') + +// Account Management Tab const settingsAddAccount = document.getElementById('settingsAddAccount') const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts') +// Minecraft Tab +const settingsGameWidth = document.getElementById('settingsGameWidth') +const settingsGameHeight = document.getElementById('settingsGameHeight') + +const settingsState = { + invalid: new Set() +} + /** * General Settings Functions */ + /** + * Bind value validators to the settings UI elements. These will + * validate against the criteria defined in the ConfigManager (if + * and). If the value is invalid, the UI will reflect this and saving + * will be disabled until the value is corrected. This is an automated + * process. More complex UI may need to be bound separately. + */ +function initSettingsValidators(){ + const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') + Array.from(sEls).map((v, index, arr) => { + const vFn = ConfigManager['validate' + v.getAttribute('cValue')] + if(typeof vFn === 'function'){ + if(v.tagName === 'INPUT'){ + if(v.type === 'number' || v.type === 'text'){ + v.addEventListener('keyup', (e) => { + const v = e.target + if(!vFn(v.value)){ + settingsState.invalid.add(v.id) + v.setAttribute('error', '') + settingsSaveDisabled(true) + } else { + if(v.hasAttribute('error')){ + v.removeAttribute('error') + settingsState.invalid.delete(v.id) + if(settingsState.invalid.size === 0){ + settingsSaveDisabled(false) + } + } + } + }) + } + } + } + + }) +} + +/** + * Load configuration values onto the UI. This is an automated process. + */ +function initSettingsValues(){ + const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') + Array.from(sEls).map((v, index, arr) => { + const gFn = ConfigManager['get' + v.getAttribute('cValue')] + if(typeof gFn === 'function'){ + if(v.tagName === 'INPUT'){ + if(v.type === 'number' || v.type === 'text'){ + v.value = gFn() + } else if(v.type === 'checkbox'){ + v.checked = gFn() + } + } + } + + }) +} + +/** + * Save the settings values. + */ +function saveSettingsValues(){ + const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') + Array.from(sEls).map((v, index, arr) => { + const sFn = ConfigManager['set' + v.getAttribute('cValue')] + if(typeof sFn === 'function'){ + if(v.tagName === 'INPUT'){ + if(v.type === 'number' || v.type === 'text'){ + sFn(v.value) + } else if(v.type === 'checkbox'){ + sFn(v.checked) + } + } + } + }) +} + let selectedTab = 'settingsTabAccount' /** @@ -33,8 +119,19 @@ function setupSettingsTabs(){ }) } +/** + * Set if the settings save (done) button is disabled. + * + * @param {boolean} v True to disable, false to enable. + */ +function settingsSaveDisabled(v){ + settingsNavDone.disabled = v +} + /* Closes the settings view and saves all data. */ settingsNavDone.onclick = () => { + saveSettingsValues() + ConfigManager.save() switchView(getCurrentView(), VIEWS.landing) } @@ -199,17 +296,41 @@ function prepareAccountsTab() { bindAuthAccountLogOut() } +/** + * Minecraft Tab + */ + + /** + * Disable decimals, negative signs, and scientific notation. + */ +settingsGameWidth.addEventListener('keydown', (e) => { + if(/[-\.eE]/.test(e.key)){ + e.preventDefault() + } +}) +settingsGameHeight.addEventListener('keydown', (e) => { + if(/[-\.eE]/.test(e.key)){ + e.preventDefault() + } +}) + /** * Settings preparation functions. */ /** * Prepare the entire settings UI. + * + * @param {boolean} first Whether or not it is the first load. */ -function prepareSettings() { - setupSettingsTabs() +function prepareSettings(first = false) { + if(first){ + setupSettingsTabs() + initSettingsValidators() + } + initSettingsValues() prepareAccountsTab() } // Prepare the settings UI on startup. -prepareSettings() \ No newline at end of file +prepareSettings(true) \ No newline at end of file diff --git a/app/settings.ejs b/app/settings.ejs index e3719f7..5471e65 100644 --- a/app/settings.ejs +++ b/app/settings.ejs @@ -30,7 +30,7 @@