From db7ba0d45081d37e4533e73439f4d4a80e3d796c Mon Sep 17 00:00:00 2001
From: Daniel Scalzi <d_scalzi@yahoo.com>
Date: Mon, 11 Jun 2018 22:11:05 -0400
Subject: [PATCH] Initial work on Java tab. Added custom range slider.

Also added two more settings tabs. These are experimental and subject to change.
---
 app/assets/css/launcher.css       |  32 +++++++++
 app/assets/js/configmanager.js    |  17 ++++-
 app/assets/js/scripts/settings.js | 104 ++++++++++++++++++++++++++++++
 app/settings.ejs                  |  23 +++++++
 4 files changed, 175 insertions(+), 1 deletion(-)

diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css
index 20bc9ca8..cc2cc3b6 100644
--- a/app/assets/css/launcher.css
+++ b/app/assets/css/launcher.css
@@ -948,6 +948,11 @@ body, button {
     text-shadow: none;
 }
 
+/* Div to add some space between nav items. */
+.settingsNavSpacer {
+    height: 25px;
+}
+
 /* Content container for the done button. */
 #settingsNavContentBottom {
     position: absolute;
@@ -1122,6 +1127,33 @@ input:checked + .toggleSwitchSlider:before {
     transform: translateX(21px);
 }
 
+/* Range Slider styles. */
+.rangeSlider {
+    width: 35%;
+    height: 5px;
+    margin: 15px 0px;
+    background: grey;
+    border-radius: 3px;
+    position: relative;
+}
+.rangeSliderBar {
+    position: absolute;
+    background: #8be88b;
+    width: 50%;
+    height: 5px;
+    border-radius: 3px 0px 0px 3px;
+}
+.rangeSliderTrack {
+    position: absolute;
+    top: -7.5px;
+    width: 7px;
+    height: 20px;
+    background: white;
+    border-radius: 3px;
+    left: 50%;
+    cursor: ew-resize;
+}
+
 /* * *
 * Settings View (Account Tab)
 * * */
diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js
index 5bc60f3a..138d28d2 100644
--- a/app/assets/js/configmanager.js
+++ b/app/assets/js/configmanager.js
@@ -9,11 +9,26 @@ const dataPath = path.join(sysRoot, '.westeroscraft')
 
 const firstLaunch = !fs.existsSync(dataPath)
 
+exports.getAbsoluteMinRAM = function(){
+    const mem = os.totalmem()
+    return mem >= 6000000000 ? 3 : 2
+}
+
+exports.getAbsoluteMaxRAM = function(){
+    const mem = os.totalmem()
+    const gT16 = mem-16000000000
+    return Math.floor((mem-1000000000-(gT16 > 0 ? (Number.parseInt(gT16/8) + 16000000000/4) : mem/4))/1000000000)
+}
+
 function resolveMaxRAM(){
     const mem = os.totalmem()
     return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G')
 }
 
+function resolveMinRAM(){
+    return exports.getAbsoluteMinRAM() + 'G'
+}
+
 /**
  * Three types of values:
  * Static = Explicitly declared.
@@ -23,7 +38,7 @@ function resolveMaxRAM(){
 const DEFAULT_CONFIG = {
     settings: {
         java: {
-            minRAM: '2G',
+            minRAM: resolveMinRAM(),
             maxRAM: resolveMaxRAM(), // Dynamic
             executable: null,
             jvmOptions: [
diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js
index d40e59d8..690baa38 100644
--- a/app/assets/js/scripts/settings.js
+++ b/app/assets/js/scripts/settings.js
@@ -8,6 +8,10 @@ const settingsCurrentAccounts = document.getElementById('settingsCurrentAccounts
 const settingsGameWidth       = document.getElementById('settingsGameWidth')
 const settingsGameHeight      = document.getElementById('settingsGameHeight')
 
+// Java Tab
+const settingsMaxRAMRange     = document.getElementById('settingsMaxRAMRange')
+const settingsMinRAMRange     = document.getElementById('settingsMinRAMRange')
+
 const settingsState = {
     invalid: new Set()
 }
@@ -67,6 +71,10 @@ function initSettingsValues(){
                 } else if(v.type === 'checkbox'){
                     v.checked = gFn()
                 }
+            } else if(v.tagName === 'DIV'){
+                if(v.classList.contains('rangeSlider')){
+                    v.setAttribute('value', Number.parseFloat(gFn()))
+                }
             }
         }
 
@@ -319,6 +327,101 @@ settingsGameHeight.addEventListener('keydown', (e) => {
     }
 })
 
+/**
+ * Java Tab
+ */
+
+settingsMaxRAMRange.setAttribute('max', ConfigManager.getAbsoluteMaxRAM())
+settingsMaxRAMRange.setAttribute('min', ConfigManager.getAbsoluteMinRAM())
+settingsMinRAMRange.setAttribute('max', ConfigManager.getAbsoluteMaxRAM())
+settingsMinRAMRange.setAttribute('min', ConfigManager.getAbsoluteMinRAM())
+
+settingsMinRAMRange.onchange = (e) => {
+    const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
+    const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
+    if(sMaxV < sMinV){
+        const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
+        updateRangedSlider(settingsMaxRAMRange, sMinV,
+        (1+(sMinV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
+    }
+}
+settingsMaxRAMRange.onchange = (e) => {
+    const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
+    const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
+    if(sMaxV < sMinV){
+        e.preventDefault()
+    }
+}
+
+function calculateRangeSliderMeta(v){
+    const val = {
+        max: Number(v.getAttribute('max')),
+        min: Number(v.getAttribute('min')),
+        step: Number(v.getAttribute('step')),
+    }
+    val.ticks = 1+(val.max-val.min)/val.step
+    val.inc = 100/val.ticks
+    return val
+}
+
+function bindRangeSlider(){
+    Array.from(document.getElementsByClassName('rangeSlider')).map((v) => {
+        const track = v.getElementsByClassName('rangeSliderTrack')[0]
+
+        const value = v.getAttribute('value')
+        const sliderMeta = calculateRangeSliderMeta(v)
+        updateRangedSlider(v, value, 
+            (1+(value-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
+
+        track.onmousedown = (e) => {
+
+            document.onmouseup = (e) => {
+                document.onmousemove = null
+                document.onmouseup = null
+            }
+
+            document.onmousemove = (e) => {
+                const diff = e.pageX - v.offsetLeft - track.offsetWidth/2
+                
+                if(diff >= 0 && diff <= v.offsetWidth-track.offsetWidth/2){
+
+                    const perc = (diff/v.offsetWidth)*100
+                    const notch = Number(perc/sliderMeta.inc).toFixed(0)*sliderMeta.inc
+
+                    if(Math.abs(perc-notch) < sliderMeta.inc/2){
+                        updateRangedSlider(v, sliderMeta.min+(sliderMeta.step*((notch/sliderMeta.inc)-1)), notch)
+                    }
+                }
+            }
+        }
+    }) 
+}
+
+function updateRangedSlider(element, value, notch){
+    const oldVal = element.getAttribute('value')
+    const bar = element.getElementsByClassName('rangeSliderBar')[0]
+    const track = element.getElementsByClassName('rangeSliderTrack')[0]
+    element.setAttribute('value', value)
+    const event = new MouseEvent('change', {
+        target: element,
+        type: 'change',
+        bubbles: false,
+        cancelable: true
+    })
+    let cancelled = !element.dispatchEvent(event)
+    if(!cancelled){
+        track.style.left = notch + '%'
+        bar.style.width = notch + '%'
+    } else {
+        element.setAttribute('value', oldVal)
+    }
+}
+
+function prepareJavaTab(){
+    bindRangeSlider()
+}
+
+
 /**
  * Settings preparation functions.
  */
@@ -335,6 +438,7 @@ function prepareSettings(first = false) {
     }
     initSettingsValues()
     prepareAccountsTab()
+    prepareJavaTab()
 }
 
 // Prepare the settings UI on startup.
diff --git a/app/settings.ejs b/app/settings.ejs
index a9136b5a..cd2b549b 100644
--- a/app/settings.ejs
+++ b/app/settings.ejs
@@ -10,6 +10,9 @@
                     <button class="settingsNavItem" rSc="settingsTabMinecraft">Minecraft</button>
                     <button class="settingsNavItem" rSc="settingsTabJava">Java</button>
                     <button class="settingsNavItem" rSc="settingsTabLauncher">Launcher</button>
+                    <div class="settingsNavSpacer"></div>
+                    <button class="settingsNavItem" rSc="settingsTabAbout">About</button>
+                    <button class="settingsNavItem" rSc="settingsTabUpdates">Updates</button>
                     <div id="settingsNavContentBottom">
                         <div class="settingsNavDivider"></div>
                         <button id="settingsNavDone">Done</button>
@@ -92,6 +95,14 @@
                 <span class="settingsTabHeaderText">Java Settings</span>
                 <span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span>
             </div>
+            <div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" min="3" max="8" value="8" step="0.5">
+                <div class="rangeSliderBar"></div>
+                <div class="rangeSliderTrack"></div>
+            </div>
+            <div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" min="3" max="8" value="8" step="0.5">
+                <div class="rangeSliderBar"></div>
+                <div class="rangeSliderTrack"></div>
+            </div>
         </div>
         <div id="settingsTabLauncher" class="settingsTab" style="display: none;">
             <div class="settingsTabHeader">
@@ -111,6 +122,18 @@
                 </div>
             </div>
         </div>
+        <div id="settingsTabAbout" class="settingsTab" style="display: none;">
+            <div class="settingsTabHeader">
+                <span class="settingsTabHeaderText">About</span>
+                <span class="settingsTabHeaderDesc">Lorem ipsum dolor sit amet.</span>
+            </div>
+        </div>
+        <div id="settingsTabUpdates" class="settingsTab" style="display: none;">
+            <div class="settingsTabHeader">
+                <span class="settingsTabHeaderText">Software Updates</span>
+                <span class="settingsTabHeaderDesc">Manage and review application updates.</span>
+            </div>
+        </div>
     </div>
     <script src="./assets/js/scripts/settings.js"></script>
 </div>
\ No newline at end of file