Эх сурвалжийг харах

v0.0.1-alpha.8 - Implemented Minecraft Settings Tab.

Implemented minecraft settings tab.
Updated distro index documentation to reflext the file system overhaul.
Daniel Scalzi 7 жил өмнө
parent
commit
790a3e0e8b

+ 120 - 3
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)                                            *

+ 0 - 2
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':

+ 25 - 24
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
 }
 

+ 4 - 4
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')

+ 124 - 3
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()
+prepareSettings(true)

+ 46 - 1
app/settings.ejs

@@ -30,7 +30,7 @@
                 </button>
             </div>
             <div id="settingsCurrentAccountsHeader">
-                <span id="settingsCurrentAccountsHeaderText">Current Accounts</span>
+                <span class="settingsFieldTitle">Current Accounts</span>
             </div>
             <div id="settingsCurrentAccounts">
                 <!-- Auth accounts populated here. -->
@@ -41,6 +41,51 @@
                 <span class="settingsTabHeaderText">Minecraft Settings</span>
                 <span class="settingsTabHeaderDesc">Options related to game launch.</span>
             </div>
+            <div id="settingsGameResolutionContainer">
+                <span class="settingsFieldTitle">Game Resolution</span>
+                <div id="settingsGameResolutionContent">
+                    <input type="number" id="settingsGameWidth" min="0" cValue="GameWidth">
+                    <div id="settingsGameResolutionCross">&#10006;</div>
+                    <input type="number" id="settingsGameHeight" min="0" cValue="GameHeight">
+                </div>
+            </div>
+            <div class="settingsDivider"></div>
+            <div class="settingsFieldContainer">
+                <div class="settingsFieldLeft">
+                    <span class="settingsFieldTitle">Launch in fullscreen.</span>
+                </div>
+                <div class="settingsFieldRight">
+                    <label class="toggleSwitch">
+                        <input type="checkbox" cValue="Fullscreen">
+                        <span class="toggleSwitchSlider"></span>
+                    </label>
+                </div>
+            </div>
+            <div class="settingsDivider"></div>
+            <div class="settingsFieldContainer">
+                <div class="settingsFieldLeft">
+                    <span class="settingsFieldTitle">Automatically connect to the server on launch.</span>
+                </div>
+                <div class="settingsFieldRight">
+                    <label class="toggleSwitch">
+                        <input type="checkbox" cValue="AutoConnect">
+                        <span class="toggleSwitchSlider"></span>
+                    </label>
+                </div>
+            </div>
+            <div class="settingsDivider"></div>
+            <div class="settingsFieldContainer">
+                <div class="settingsFieldLeft">
+                    <span class="settingsFieldTitle">Launch game process detached from launcher.</span>
+                    <span class="settingsFieldDesc">If the game is not detached, closing the launcher will also close the game.</span>
+                </div>
+                <div class="settingsFieldRight">
+                    <label class="toggleSwitch">
+                        <input type="checkbox" cValue="LaunchDetached">
+                        <span class="toggleSwitchSlider"></span>
+                    </label>
+                </div>
+            </div>
         </div>
         <div id="settingsTabJava" class="settingsTab" style="display: none;">
             <div class="settingsTabHeader">

+ 7 - 7
docs/distro.md

@@ -99,16 +99,16 @@ type = forgemod
 id = com.westeroscraft:westerosblocks:1.0.0
 extension = .jar
 
-resolved_path = {base}/modstore/com/westeroscraft/westerosblocks/1.0.0/westerosblocks-1.0.0.jar
+resolved_path = {commonDirectory}/modstore/com/westeroscraft/westerosblocks/1.0.0/westerosblocks-1.0.0.jar
 ```
 
 The resolved path depends on the type. Currently, there are several recognized module types:
 
-- `forge-hosted` ({base}/libraries/{path OR resolved})
-- `library` ({base}/libraries/{path OR resolved})
-- `forgemod` ({base}/modstore/{path OR resolved})
-- `litemod` ({base}/modstore/{path OR resolved})
-- `file` ({base}/{path OR resolved})
+- `forge-hosted` ({commonDirectory}/libraries/{path OR resolved})
+- `library` ({commonDirectory}/common/libraries/{path OR resolved})
+- `forgemod` ({commonDirectory}/common/modstore/{path OR resolved})
+- `litemod` ({commonDirectory}/common/modstore/{path OR resolved})
+- `file` ({instanceDirectory}/{serverID}/{path OR resolved})
 
 ---
 
@@ -204,7 +204,7 @@ This module type is being actively considered and changed, until finalized there
 
 ### file
 
-The module type `file` represents a generic file required by the client, another module, etc.
+The module type `file` represents a generic file required by the client, another module, etc. These files are stored in the server's instance directory.
 
 Ex.
 

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "westeroscraftlauncher",
-  "version": "0.0.1-alpha.7",
+  "version": "0.0.1-alpha.8",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "westeroscraftlauncher",
-  "version": "0.0.1-alpha.7",
+  "version": "0.0.1-alpha.8",
   "description": "Custom modded launcher for Westeroscraft",
   "productName": "WesterosCraft Launcher",
   "main": "index.js",