Fixed minor issue where an extra tick was added. The bar fill color now changes to yellow and red depending on how much memory is being allocated.
512 lines
18 KiB
JavaScript
512 lines
18 KiB
JavaScript
const os = require('os')
|
|
|
|
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')
|
|
|
|
// Java Tab
|
|
const settingsMaxRAMRange = document.getElementById('settingsMaxRAMRange')
|
|
const settingsMinRAMRange = document.getElementById('settingsMinRAMRange')
|
|
const settingsMaxRAMLabel = document.getElementById('settingsMaxRAMLabel')
|
|
const settingsMinRAMLabel = document.getElementById('settingsMinRAMLabel')
|
|
const settingsMemoryTotal = document.getElementById('settingsMemoryTotal')
|
|
const settingsMemoryAvail = document.getElementById('settingsMemoryAvail')
|
|
|
|
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()
|
|
}
|
|
} else if(v.tagName === 'DIV'){
|
|
if(v.classList.contains('rangeSlider')){
|
|
// Special Conditions
|
|
const cVal = v.getAttribute('cValue')
|
|
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
|
|
let val = gFn()
|
|
if(val.endsWith('M')){
|
|
val = Number(val.substring(0, val.length-1))/1000
|
|
} else {
|
|
val = Number.parseFloat(val)
|
|
}
|
|
|
|
v.setAttribute('value', val)
|
|
} else {
|
|
v.setAttribute('value', Number.parseFloat(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)
|
|
// Special Conditions
|
|
const cVal = v.getAttribute('cValue')
|
|
if(cVal === 'AllowPrerelease'){
|
|
changeAllowPrerelease(v.checked)
|
|
}
|
|
}
|
|
} else if(v.tagName === 'DIV'){
|
|
if(v.classList.contains('rangeSlider')){
|
|
// Special Conditions
|
|
const cVal = v.getAttribute('cValue')
|
|
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
|
|
let val = Number(v.getAttribute('value'))
|
|
if(val%1 > 0){
|
|
val = val*1000 + 'M'
|
|
} else {
|
|
val = val + 'G'
|
|
}
|
|
|
|
sFn(val)
|
|
} else {
|
|
sFn(v.getAttribute('value'))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
let selectedTab = 'settingsTabAccount'
|
|
|
|
/**
|
|
* Bind functionality for the settings navigation items.
|
|
*/
|
|
function setupSettingsTabs(){
|
|
Array.from(document.getElementsByClassName('settingsNavItem')).map((val) => {
|
|
val.onclick = (e) => {
|
|
if(val.hasAttribute('selected')){
|
|
return
|
|
}
|
|
const navItems = document.getElementsByClassName('settingsNavItem')
|
|
for(let i=0; i<navItems.length; i++){
|
|
if(navItems[i].hasAttribute('selected')){
|
|
navItems[i].removeAttribute('selected')
|
|
}
|
|
}
|
|
val.setAttribute('selected', '')
|
|
let prevTab = selectedTab
|
|
selectedTab = val.getAttribute('rSc')
|
|
$(`#${prevTab}`).fadeOut(250, () => {
|
|
$(`#${selectedTab}`).fadeIn(250)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
}
|
|
|
|
/**
|
|
* Account Management Tab
|
|
*/
|
|
|
|
// Bind the add account button.
|
|
settingsAddAccount.onclick = (e) => {
|
|
switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
|
|
loginViewOnCancel = VIEWS.settings
|
|
loginViewOnSuccess = VIEWS.settings
|
|
loginCancelEnabled(true)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Bind functionality for the account selection buttons. If another account
|
|
* is selected, the UI of the previously selected account will be updated.
|
|
*/
|
|
function bindAuthAccountSelect(){
|
|
Array.from(document.getElementsByClassName('settingsAuthAccountSelect')).map((val) => {
|
|
val.onclick = (e) => {
|
|
if(val.hasAttribute('selected')){
|
|
return
|
|
}
|
|
const selectBtns = document.getElementsByClassName('settingsAuthAccountSelect')
|
|
for(let i=0; i<selectBtns.length; i++){
|
|
if(selectBtns[i].hasAttribute('selected')){
|
|
selectBtns[i].removeAttribute('selected')
|
|
selectBtns[i].innerHTML = 'Select Account'
|
|
}
|
|
}
|
|
val.setAttribute('selected', '')
|
|
val.innerHTML = 'Selected Account ✔'
|
|
setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Bind functionality for the log out button. If the logged out account was
|
|
* the selected account, another account will be selected and the UI will
|
|
* be updated accordingly.
|
|
*/
|
|
function bindAuthAccountLogOut(){
|
|
Array.from(document.getElementsByClassName('settingsAuthAccountLogOut')).map((val) => {
|
|
val.onclick = (e) => {
|
|
let isLastAccount = false
|
|
if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
|
|
isLastAccount = true
|
|
setOverlayContent(
|
|
'Warning<br>This is Your Last Account',
|
|
'In order to use the launcher you must be logged into at least one account. You will need to login again after.<br><br>Are you sure you want to log out?',
|
|
'I\'m Sure',
|
|
'Cancel'
|
|
)
|
|
setOverlayHandler(() => {
|
|
processLogOut(val, isLastAccount)
|
|
switchView(getCurrentView(), VIEWS.login)
|
|
toggleOverlay(false)
|
|
})
|
|
setDismissHandler(() => {
|
|
toggleOverlay(false)
|
|
})
|
|
toggleOverlay(true, true)
|
|
} else {
|
|
processLogOut(val, isLastAccount)
|
|
}
|
|
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Process a log out.
|
|
*
|
|
* @param {Element} val The log out button element.
|
|
* @param {boolean} isLastAccount If this logout is on the last added account.
|
|
*/
|
|
function processLogOut(val, isLastAccount){
|
|
const parent = val.closest('.settingsAuthAccount')
|
|
const uuid = parent.getAttribute('uuid')
|
|
const prevSelAcc = ConfigManager.getSelectedAccount()
|
|
AuthManager.removeAccount(uuid).then(() => {
|
|
if(!isLastAccount && uuid === prevSelAcc.uuid){
|
|
const selAcc = ConfigManager.getSelectedAccount()
|
|
refreshAuthAccountSelected(selAcc.uuid)
|
|
updateSelectedAccount(selAcc)
|
|
validateSelectedAccount()
|
|
}
|
|
})
|
|
$(parent).fadeOut(250, () => {
|
|
parent.remove()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Refreshes the status of the selected account on the auth account
|
|
* elements.
|
|
*
|
|
* @param {string} uuid The UUID of the new selected account.
|
|
*/
|
|
function refreshAuthAccountSelected(uuid){
|
|
Array.from(document.getElementsByClassName('settingsAuthAccount')).map((val) => {
|
|
const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
|
|
if(uuid === val.getAttribute('uuid')){
|
|
selBtn.setAttribute('selected', '')
|
|
selBtn.innerHTML = 'Selected Account ✔'
|
|
} else {
|
|
if(selBtn.hasAttribute('selected')){
|
|
selBtn.removeAttribute('selected')
|
|
}
|
|
selBtn.innerHTML = 'Select Account'
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Add auth account elements for each one stored in the authentication database.
|
|
*/
|
|
function populateAuthAccounts(){
|
|
const authAccounts = ConfigManager.getAuthAccounts()
|
|
const authKeys = Object.keys(authAccounts)
|
|
const selectedUUID = ConfigManager.getSelectedAccount().uuid
|
|
|
|
let authAccountStr = ``
|
|
|
|
authKeys.map((val) => {
|
|
const acc = authAccounts[val]
|
|
authAccountStr += `<div class="settingsAuthAccount" uuid="${acc.uuid}">
|
|
<div class="settingsAuthAccountLeft">
|
|
<img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://crafatar.com/renders/body/${acc.uuid}?scale=3&default=MHF_Steve&overlay">
|
|
</div>
|
|
<div class="settingsAuthAccountRight">
|
|
<div class="settingsAuthAccountDetails">
|
|
<div class="settingsAuthAccountDetailPane">
|
|
<div class="settingsAuthAccountDetailTitle">Username</div>
|
|
<div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
|
|
</div>
|
|
<div class="settingsAuthAccountDetailPane">
|
|
<div class="settingsAuthAccountDetailTitle">${acc.displayName === acc.username ? 'UUID' : 'Email'}</div>
|
|
<div class="settingsAuthAccountDetailValue">${acc.displayName === acc.username ? acc.uuid : acc.username}</div>
|
|
</div>
|
|
</div>
|
|
<div class="settingsAuthAccountActions">
|
|
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account ✔' : '>Select Account'}</button>
|
|
<div class="settingsAuthAccountWrapper">
|
|
<button class="settingsAuthAccountLogOut">Log Out</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
})
|
|
|
|
settingsCurrentAccounts.innerHTML = authAccountStr
|
|
}
|
|
|
|
function prepareAccountsTab() {
|
|
populateAuthAccounts()
|
|
bindAuthAccountSelect()
|
|
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()
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 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'))
|
|
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
|
|
const max = (os.totalmem()-1000000000)/1000000000
|
|
if(sMinV >= max/2){
|
|
bar.style.background = '#e86060'
|
|
} else if(sMinV >= max/4) {
|
|
bar.style.background = '#e8e18b'
|
|
} else {
|
|
bar.style.background = null
|
|
}
|
|
if(sMaxV < sMinV){
|
|
const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
|
|
updateRangedSlider(settingsMaxRAMRange, sMinV,
|
|
((sMinV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
|
|
settingsMaxRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
|
|
}
|
|
settingsMinRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
|
|
}
|
|
|
|
settingsMaxRAMRange.onchange = (e) => {
|
|
const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
|
|
const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
|
|
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
|
|
const max = (os.totalmem()-1000000000)/1000000000
|
|
if(sMaxV >= max/2){
|
|
bar.style.background = '#e86060'
|
|
} else if(sMaxV >= max/4) {
|
|
bar.style.background = '#e8e18b'
|
|
} else {
|
|
bar.style.background = null
|
|
}
|
|
if(sMaxV < sMinV){
|
|
const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
|
|
updateRangedSlider(settingsMinRAMRange, sMaxV,
|
|
((sMaxV-sliderMeta.min)/sliderMeta.step)*sliderMeta.inc)
|
|
settingsMinRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
|
|
}
|
|
settingsMaxRAMLabel.innerHTML = sMaxV.toFixed(1) + 'G'
|
|
}
|
|
|
|
function calculateRangeSliderMeta(v){
|
|
const val = {
|
|
max: Number(v.getAttribute('max')),
|
|
min: Number(v.getAttribute('min')),
|
|
step: Number(v.getAttribute('step')),
|
|
}
|
|
val.ticks = (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,
|
|
((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)), 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 bindMemoryStatus(){
|
|
settingsMemoryTotal.innerHTML = Number((os.totalmem()-1000000000)/1000000000).toFixed(1) + 'G'
|
|
settingsMemoryAvail.innerHTML = Number(os.freemem()/1000000000).toFixed(1) + 'G'
|
|
}
|
|
|
|
function prepareJavaTab(){
|
|
bindRangeSlider()
|
|
bindMemoryStatus()
|
|
}
|
|
|
|
|
|
/**
|
|
* Settings preparation functions.
|
|
*/
|
|
|
|
/**
|
|
* Prepare the entire settings UI.
|
|
*
|
|
* @param {boolean} first Whether or not it is the first load.
|
|
*/
|
|
function prepareSettings(first = false) {
|
|
if(first){
|
|
setupSettingsTabs()
|
|
initSettingsValidators()
|
|
}
|
|
initSettingsValues()
|
|
prepareAccountsTab()
|
|
prepareJavaTab()
|
|
}
|
|
|
|
// Prepare the settings UI on startup.
|
|
prepareSettings(true) |