2018-06-20 03:15:10 -07:00
// Requirements
const os = require ( 'os' )
const semver = require ( 'semver' )
2018-06-11 19:11:05 -07:00
2019-03-05 20:05:01 -08:00
const { JavaGuard } = require ( './assets/js/assetguard' )
2018-08-07 01:16:15 -07:00
const DropinModUtil = require ( './assets/js/dropinmodutil' )
2022-02-06 20:03:06 -08:00
const { MSFT _OPCODE , MSFT _REPLY _TYPE , MSFT _ERROR } = require ( './assets/js/ipcconstants' )
2018-07-22 08:40:15 -07:00
2018-06-04 13:28:17 -07:00
const settingsState = {
invalid : new Set ( )
}
2018-12-21 03:51:08 -08:00
function bindSettingsSelect ( ) {
for ( let ele of document . getElementsByClassName ( 'settingsSelectContainer' ) ) {
const selectedDiv = ele . getElementsByClassName ( 'settingsSelectSelected' ) [ 0 ]
selectedDiv . onclick = ( e ) => {
e . stopPropagation ( )
closeSettingsSelect ( e . target )
e . target . nextElementSibling . toggleAttribute ( 'hidden' )
e . target . classList . toggle ( 'select-arrow-active' )
}
}
}
function closeSettingsSelect ( el ) {
for ( let ele of document . getElementsByClassName ( 'settingsSelectContainer' ) ) {
const selectedDiv = ele . getElementsByClassName ( 'settingsSelectSelected' ) [ 0 ]
const optionsDiv = ele . getElementsByClassName ( 'settingsSelectOptions' ) [ 0 ]
if ( ! ( selectedDiv === el ) ) {
selectedDiv . classList . remove ( 'select-arrow-active' )
optionsDiv . setAttribute ( 'hidden' , '' )
}
}
}
/ * I f t h e u s e r c l i c k s a n y w h e r e o u t s i d e t h e s e l e c t b o x ,
then close all select boxes : * /
document . addEventListener ( 'click' , closeSettingsSelect )
bindSettingsSelect ( )
2019-01-04 11:25:27 -08:00
function bindFileSelectors ( ) {
2020-05-21 18:02:58 -07:00
for ( let ele of document . getElementsByClassName ( 'settingsFileSelButton' ) ) {
ele . onclick = async e => {
const isJavaExecSel = ele . id === 'settingsJavaExecSel'
const directoryDialog = ele . hasAttribute ( 'dialogDirectory' ) && ele . getAttribute ( 'dialogDirectory' ) == 'true'
const properties = directoryDialog ? [ 'openDirectory' , 'createDirectory' ] : [ 'openFile' ]
const options = {
properties
2019-01-04 11:25:27 -08:00
}
2020-05-21 18:02:58 -07:00
if ( ele . hasAttribute ( 'dialogTitle' ) ) {
options . title = ele . getAttribute ( 'dialogTitle' )
}
if ( isJavaExecSel && process . platform === 'win32' ) {
options . filters = [
{ name : 'Executables' , extensions : [ 'exe' ] } ,
{ name : 'All Files' , extensions : [ '*' ] }
]
}
const res = await remote . dialog . showOpenDialog ( remote . getCurrentWindow ( ) , options )
if ( ! res . canceled ) {
ele . previousElementSibling . value = res . filePaths [ 0 ]
if ( isJavaExecSel ) {
populateJavaExecDetails ( ele . previousElementSibling . value )
}
2019-01-04 11:25:27 -08:00
}
}
}
}
bindFileSelectors ( )
2018-05-30 19:22:17 -07:00
/ * *
* General Settings Functions
* /
2018-07-22 10:31:15 -07:00
/ * *
2018-06-04 13:28:17 -07:00
* Bind value validators to the settings UI elements . These will
* validate against the criteria defined in the ConfigManager ( if
2021-10-30 23:20:03 -07:00
* any ) . If the value is invalid , the UI will reflect this and saving
2018-06-04 13:28:17 -07:00
* 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 ) => {
2018-07-29 05:42:11 -07:00
const cVal = v . getAttribute ( 'cValue' )
const gFn = ConfigManager [ 'get' + cVal ]
2018-06-04 13:28:17 -07:00
if ( typeof gFn === 'function' ) {
if ( v . tagName === 'INPUT' ) {
if ( v . type === 'number' || v . type === 'text' ) {
2018-07-22 10:31:15 -07:00
// Special Conditions
2018-06-14 00:49:55 -07:00
if ( cVal === 'JavaExecutable' ) {
populateJavaExecDetails ( v . value )
2018-06-14 03:09:09 -07:00
v . value = gFn ( )
2019-01-04 11:25:27 -08:00
} else if ( cVal === 'DataDirectory' ) {
v . value = gFn ( )
2018-06-14 03:09:09 -07:00
} else if ( cVal === 'JVMOptions' ) {
v . value = gFn ( ) . join ( ' ' )
} else {
v . value = gFn ( )
2018-06-14 00:49:55 -07:00
}
2018-06-04 13:28:17 -07:00
} else if ( v . type === 'checkbox' ) {
v . checked = gFn ( )
}
2018-06-11 19:11:05 -07:00
} else if ( v . tagName === 'DIV' ) {
if ( v . classList . contains ( 'rangeSlider' ) ) {
2018-06-12 00:25:36 -07:00
// Special Conditions
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 ( ) ) )
}
2018-06-11 19:11:05 -07:00
}
2018-06-04 13:28:17 -07:00
}
}
} )
}
/ * *
* Save the settings values .
* /
function saveSettingsValues ( ) {
const sEls = document . getElementById ( 'settingsContainer' ) . querySelectorAll ( '[cValue]' )
Array . from ( sEls ) . map ( ( v , index , arr ) => {
2018-07-29 05:42:11 -07:00
const cVal = v . getAttribute ( 'cValue' )
const sFn = ConfigManager [ 'set' + cVal ]
2018-06-04 13:28:17 -07:00
if ( typeof sFn === 'function' ) {
if ( v . tagName === 'INPUT' ) {
if ( v . type === 'number' || v . type === 'text' ) {
2018-06-14 03:09:09 -07:00
// Special Conditions
if ( cVal === 'JVMOptions' ) {
sFn ( v . value . split ( ' ' ) )
} else {
sFn ( v . value )
}
2018-06-04 13:28:17 -07:00
} else if ( v . type === 'checkbox' ) {
sFn ( v . checked )
2018-06-04 16:34:47 -07:00
// Special Conditions
if ( cVal === 'AllowPrerelease' ) {
changeAllowPrerelease ( v . checked )
}
2018-06-04 13:28:17 -07:00
}
2018-06-12 00:25:36 -07:00
} else if ( v . tagName === 'DIV' ) {
if ( v . classList . contains ( 'rangeSlider' ) ) {
// Special Conditions
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' ) )
}
}
2018-06-04 13:28:17 -07:00
}
}
} )
}
2018-06-21 09:38:21 -07:00
let selectedSettingsTab = 'settingsTabAccount'
2018-05-30 13:00:07 -07:00
2018-06-20 04:38:53 -07:00
/ * *
* Modify the settings container UI when the scroll threshold reaches
* a certain poin .
*
* @ param { UIEvent } e The scroll event .
* /
function settingsTabScrollListener ( e ) {
if ( e . target . scrollTop > Number . parseFloat ( getComputedStyle ( e . target . firstElementChild ) . marginTop ) ) {
document . getElementById ( 'settingsContainer' ) . setAttribute ( 'scrolled' , '' )
} else {
document . getElementById ( 'settingsContainer' ) . removeAttribute ( 'scrolled' )
}
}
2018-05-30 19:22:17 -07:00
/ * *
* Bind functionality for the settings navigation items .
* /
2018-05-30 13:00:07 -07:00
function setupSettingsTabs ( ) {
Array . from ( document . getElementsByClassName ( 'settingsNavItem' ) ) . map ( ( val ) => {
2018-06-20 04:38:53 -07:00
if ( val . hasAttribute ( 'rSc' ) ) {
2018-06-21 09:38:21 -07:00
val . onclick = ( ) => {
settingsNavItemListener ( val )
2018-05-30 13:00:07 -07:00
}
}
} )
}
2018-06-21 09:38:21 -07:00
/ * *
* Settings nav item onclick lisener . Function is exposed so that
* other UI elements can quickly toggle to a certain tab from other views .
*
* @ param { Element } ele The nav item which has been clicked .
* @ param { boolean } fade Optional . True to fade transition .
* /
function settingsNavItemListener ( ele , fade = true ) {
if ( ele . 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' )
}
}
ele . setAttribute ( 'selected' , '' )
let prevTab = selectedSettingsTab
selectedSettingsTab = ele . getAttribute ( 'rSc' )
document . getElementById ( prevTab ) . onscroll = null
document . getElementById ( selectedSettingsTab ) . onscroll = settingsTabScrollListener
if ( fade ) {
$ ( ` # ${ prevTab } ` ) . fadeOut ( 250 , ( ) => {
$ ( ` # ${ selectedSettingsTab } ` ) . fadeIn ( {
duration : 250 ,
start : ( ) => {
settingsTabScrollListener ( {
target : document . getElementById ( selectedSettingsTab )
} )
}
} )
} )
} else {
$ ( ` # ${ prevTab } ` ) . hide ( 0 , ( ) => {
$ ( ` # ${ selectedSettingsTab } ` ) . show ( {
duration : 0 ,
start : ( ) => {
settingsTabScrollListener ( {
target : document . getElementById ( selectedSettingsTab )
} )
}
} )
} )
}
}
2018-06-20 03:15:10 -07:00
const settingsNavDone = document . getElementById ( 'settingsNavDone' )
2018-06-04 13:28:17 -07:00
/ * *
* Set if the settings save ( done ) button is disabled .
*
* @ param { boolean } v True to disable , false to enable .
* /
function settingsSaveDisabled ( v ) {
settingsNavDone . disabled = v
}
2018-05-30 20:32:51 -07:00
/* Closes the settings view and saves all data. */
settingsNavDone . onclick = ( ) => {
2018-06-04 13:28:17 -07:00
saveSettingsValues ( )
2018-07-29 05:42:11 -07:00
saveModConfiguration ( )
2018-06-04 13:28:17 -07:00
ConfigManager . save ( )
2018-08-07 01:16:15 -07:00
saveDropinModConfiguration ( )
2018-12-21 05:02:24 -08:00
saveShaderpackSettings ( )
2018-05-30 20:32:51 -07:00
switchView ( getCurrentView ( ) , VIEWS . landing )
}
2018-05-30 19:22:17 -07:00
/ * *
* Account Management Tab
* /
2022-02-06 21:46:23 -08:00
const msftLoginLogger = LoggerUtil . getLogger ( 'Microsoft Login' )
const msftLogoutLogger = LoggerUtil . getLogger ( 'Microsoft Logout' )
2022-02-06 20:03:06 -08:00
// Bind the add mojang account button.
2022-02-06 15:59:46 -08:00
document . getElementById ( 'settingsAddMojangAccount' ) . onclick = ( e ) => {
2018-05-30 19:22:17 -07:00
switchView ( getCurrentView ( ) , VIEWS . login , 500 , 500 , ( ) => {
loginViewOnCancel = VIEWS . settings
loginViewOnSuccess = VIEWS . settings
loginCancelEnabled ( true )
} )
}
2022-02-06 20:03:06 -08:00
// Bind the add microsoft account button.
document . getElementById ( 'settingsAddMicrosoftAccount' ) . onclick = ( e ) => {
switchView ( getCurrentView ( ) , VIEWS . waiting , 500 , 500 , ( ) => {
2022-02-08 17:53:28 -08:00
ipcRenderer . send ( MSFT _OPCODE . OPEN _LOGIN , VIEWS . settings , VIEWS . settings )
2022-02-06 20:03:06 -08:00
} )
}
// Bind reply for Microsoft Login.
ipcRenderer . on ( MSFT _OPCODE . REPLY _LOGIN , ( _ , ... arguments _ ) => {
if ( arguments _ [ 0 ] === MSFT _REPLY _TYPE . ERROR ) {
2022-02-08 17:53:28 -08:00
const viewOnClose = arguments _ [ 2 ]
console . log ( arguments _ )
switchView ( getCurrentView ( ) , viewOnClose , 500 , 500 , ( ) => {
if ( arguments _ [ 1 ] === MSFT _ERROR . NOT _FINISHED ) {
2022-02-06 20:03:06 -08:00
// User cancelled.
2022-02-06 21:46:23 -08:00
msftLoginLogger . info ( 'Login cancelled by user.' )
2022-02-06 20:03:06 -08:00
return
}
// Unexpected error.
setOverlayContent (
'Something Went Wrong' ,
'Microsoft authentication failed. Please try again.' ,
'OK'
)
setOverlayHandler ( ( ) => {
toggleOverlay ( false )
} )
toggleOverlay ( true )
} )
} else if ( arguments _ [ 0 ] === MSFT _REPLY _TYPE . SUCCESS ) {
const queryMap = arguments _ [ 1 ]
2022-02-08 17:53:28 -08:00
const viewOnClose = arguments _ [ 2 ]
2022-02-06 20:03:06 -08:00
// Error from request to Microsoft.
if ( Object . prototype . hasOwnProperty . call ( queryMap , 'error' ) ) {
2022-02-08 17:53:28 -08:00
switchView ( getCurrentView ( ) , viewOnClose , 500 , 500 , ( ) => {
2022-02-09 18:37:47 -08:00
// TODO Dont know what these errors are. Just show them I guess.
// This is probably if you messed up the app registration with Azure.
console . log ( 'Error getting authCode, is Azure application registered correctly?' )
console . log ( error )
console . log ( error _description )
console . log ( 'Full query map' , queryMap )
let error = queryMap . error // Error might be 'access_denied' ?
2022-02-08 17:53:28 -08:00
let errorDesc = queryMap . error _description
setOverlayContent (
2022-02-09 18:37:47 -08:00
error ,
errorDesc ,
2022-02-08 17:53:28 -08:00
'OK'
)
setOverlayHandler ( ( ) => {
toggleOverlay ( false )
} )
toggleOverlay ( true )
2022-02-06 20:03:06 -08:00
} )
} else {
2022-02-06 21:46:23 -08:00
msftLoginLogger . info ( 'Acquired authCode, proceeding with authentication.' )
2022-02-06 20:03:06 -08:00
const authCode = queryMap . code
AuthManager . addMicrosoftAccount ( authCode ) . then ( value => {
updateSelectedAccount ( value )
2022-02-08 17:53:28 -08:00
switchView ( getCurrentView ( ) , viewOnClose , 500 , 500 , ( ) => {
2022-02-06 21:55:54 -08:00
prepareSettings ( )
2022-02-06 20:03:06 -08:00
} )
} )
2022-02-09 17:52:57 -08:00
. catch ( ( displayableError ) => {
let actualDisplayableError
if ( isDisplayableError ( displayableError ) ) {
msftLoginLogger . error ( 'Error while logging in.' , displayableError )
actualDisplayableError = displayableError
} else {
// Uh oh.
msftLoginLogger . error ( 'Unhandled error during login.' , displayableError )
actualDisplayableError = {
title : 'Unknown Error During Login' ,
desc : 'An unknown error has occurred. Please see the console for details.'
}
}
switchView ( getCurrentView ( ) , viewOnClose , 500 , 500 , ( ) => {
setOverlayContent ( actualDisplayableError . title , actualDisplayableError . desc , Lang . queryJS ( 'login.tryAgain' ) )
setOverlayHandler ( ( ) => {
toggleOverlay ( false )
} )
toggleOverlay ( true )
} )
} )
2022-02-06 20:03:06 -08:00
}
}
} )
2018-05-30 19:22:17 -07:00
/ * *
* 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 ✔'
2018-05-30 20:32:51 -07:00
setSelectedAccount ( val . closest ( '.settingsAuthAccount' ) . getAttribute ( 'uuid' ) )
2018-05-30 19:22:17 -07:00
}
} )
}
/ * *
* 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 ) => {
2018-05-30 20:32:51 -07:00
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 )
toggleOverlay ( false )
} )
setDismissHandler ( ( ) => {
toggleOverlay ( false )
} )
toggleOverlay ( true , true )
} else {
processLogOut ( val , isLastAccount )
}
}
} )
}
2022-02-06 21:55:54 -08:00
let msAccDomElementCache
2018-05-30 20:32:51 -07:00
/ * *
* 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 ( )
2022-02-06 20:03:06 -08:00
const targetAcc = ConfigManager . getAuthAccount ( uuid )
if ( targetAcc . type === 'microsoft' ) {
2022-02-06 21:55:54 -08:00
msAccDomElementCache = parent
2022-02-06 20:03:06 -08:00
switchView ( getCurrentView ( ) , VIEWS . waiting , 500 , 500 , ( ) => {
ipcRenderer . send ( MSFT _OPCODE . OPEN _LOGOUT , uuid , isLastAccount )
} )
} else {
AuthManager . removeMojangAccount ( uuid ) . then ( ( ) => {
if ( ! isLastAccount && uuid === prevSelAcc . uuid ) {
const selAcc = ConfigManager . getSelectedAccount ( )
refreshAuthAccountSelected ( selAcc . uuid )
updateSelectedAccount ( selAcc )
validateSelectedAccount ( )
}
2022-02-09 15:23:50 -08:00
if ( isLastAccount ) {
loginOptionsCancelEnabled ( false )
loginOptionsViewOnLoginSuccess = VIEWS . settings
loginOptionsViewOnLoginCancel = VIEWS . loginOptions
switchView ( getCurrentView ( ) , VIEWS . loginOptions )
}
2022-02-06 20:03:06 -08:00
} )
$ ( parent ) . fadeOut ( 250 , ( ) => {
parent . remove ( )
} )
}
2018-05-30 19:22:17 -07:00
}
2022-02-06 20:03:06 -08:00
// Bind reply for Microsoft Logout.
ipcRenderer . on ( MSFT _OPCODE . REPLY _LOGOUT , ( _ , ... arguments _ ) => {
if ( arguments _ [ 0 ] === MSFT _REPLY _TYPE . ERROR ) {
switchView ( getCurrentView ( ) , VIEWS . settings , 500 , 500 , ( ) => {
if ( arguments _ . length > 1 && arguments _ [ 1 ] === MSFT _ERROR . NOT _FINISHED ) {
// User cancelled.
2022-02-06 21:46:23 -08:00
msftLogoutLogger . info ( 'Logout cancelled by user.' )
2022-02-06 20:03:06 -08:00
return
}
// Unexpected error.
setOverlayContent (
'Something Went Wrong' ,
'Microsoft logout failed. Please try again.' ,
'OK'
)
setOverlayHandler ( ( ) => {
toggleOverlay ( false )
} )
toggleOverlay ( true )
} )
} else if ( arguments _ [ 0 ] === MSFT _REPLY _TYPE . SUCCESS ) {
const uuid = arguments _ [ 1 ]
const isLastAccount = arguments _ [ 2 ]
const prevSelAcc = ConfigManager . getSelectedAccount ( )
2022-02-06 21:46:23 -08:00
msftLogoutLogger . info ( 'Logout Successful. uuid:' , uuid )
2022-02-06 20:03:06 -08:00
AuthManager . removeMicrosoftAccount ( uuid )
. then ( ( ) => {
if ( ! isLastAccount && uuid === prevSelAcc . uuid ) {
const selAcc = ConfigManager . getSelectedAccount ( )
refreshAuthAccountSelected ( selAcc . uuid )
updateSelectedAccount ( selAcc )
validateSelectedAccount ( )
}
2022-02-09 15:23:50 -08:00
if ( isLastAccount ) {
loginOptionsCancelEnabled ( false )
loginOptionsViewOnLoginSuccess = VIEWS . settings
loginOptionsViewOnLoginCancel = VIEWS . loginOptions
switchView ( getCurrentView ( ) , VIEWS . loginOptions )
}
2022-02-06 21:55:54 -08:00
if ( msAccDomElementCache ) {
msAccDomElementCache . remove ( )
msAccDomElementCache = null
}
2022-02-06 20:03:06 -08:00
} )
. finally ( ( ) => {
2022-02-09 15:23:50 -08:00
if ( ! isLastAccount ) {
switchView ( getCurrentView ( ) , VIEWS . settings , 500 , 500 )
}
2022-02-06 20:03:06 -08:00
} )
}
} )
2018-05-30 19:22:17 -07:00
/ * *
* 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'
}
} )
}
2022-02-07 16:11:30 -08:00
const settingsCurrentMicrosoftAccounts = document . getElementById ( 'settingsCurrentMicrosoftAccounts' )
const settingsCurrentMojangAccounts = document . getElementById ( 'settingsCurrentMojangAccounts' )
2018-06-20 03:15:10 -07:00
2018-05-30 19:22:17 -07:00
/ * *
* Add auth account elements for each one stored in the authentication database .
* /
function populateAuthAccounts ( ) {
const authAccounts = ConfigManager . getAuthAccounts ( )
const authKeys = Object . keys ( authAccounts )
2018-08-13 03:31:46 -07:00
if ( authKeys . length === 0 ) {
return
}
2018-05-30 19:22:17 -07:00
const selectedUUID = ConfigManager . getSelectedAccount ( ) . uuid
2022-02-07 16:11:30 -08:00
let microsoftAuthAccountStr = ''
let mojangAuthAccountStr = ''
2018-05-30 19:22:17 -07:00
2022-02-07 16:11:30 -08:00
authKeys . forEach ( ( val ) => {
2018-05-30 19:22:17 -07:00
const acc = authAccounts [ val ]
2022-02-07 16:11:30 -08:00
const accHtml = ` <div class="settingsAuthAccount" uuid=" ${ acc . uuid } ">
2018-05-30 19:22:17 -07:00
< div class = "settingsAuthAccountLeft" >
2021-07-19 08:08:55 -07:00
< img class = "settingsAuthAccountImage" alt = "${acc.displayName}" src = "https://mc-heads.net/body/${acc.uuid}/60" >
2018-05-30 19:22:17 -07:00
< / d i v >
< div class = "settingsAuthAccountRight" >
< div class = "settingsAuthAccountDetails" >
< div class = "settingsAuthAccountDetailPane" >
< div class = "settingsAuthAccountDetailTitle" > Username < / d i v >
< div class = "settingsAuthAccountDetailValue" > $ { acc . displayName } < / d i v >
< / d i v >
< div class = "settingsAuthAccountDetailPane" >
2018-07-01 23:08:48 -07:00
< div class = "settingsAuthAccountDetailTitle" > UUID < / d i v >
< div class = "settingsAuthAccountDetailValue" > $ { acc . uuid } < / d i v >
2018-05-30 19:22:17 -07:00
< / d i v >
< / d i v >
< div class = "settingsAuthAccountActions" >
< button class = "settingsAuthAccountSelect" $ { selectedUUID === acc . uuid ? 'selected>Selected Account ✔' : '>Select Account' } < / b u t t o n >
< div class = "settingsAuthAccountWrapper" >
< button class = "settingsAuthAccountLogOut" > Log Out < / b u t t o n >
< / d i v >
< / d i v >
< / d i v >
< / d i v > `
2022-02-07 16:11:30 -08:00
if ( acc . type === 'microsoft' ) {
microsoftAuthAccountStr += accHtml
} else {
mojangAuthAccountStr += accHtml
}
2018-05-30 19:22:17 -07:00
} )
2022-02-07 16:11:30 -08:00
settingsCurrentMicrosoftAccounts . innerHTML = microsoftAuthAccountStr
settingsCurrentMojangAccounts . innerHTML = mojangAuthAccountStr
2018-05-30 19:22:17 -07:00
}
2018-06-14 03:09:09 -07:00
/ * *
* Prepare the accounts tab for display .
* /
2018-05-30 19:22:17 -07:00
function prepareAccountsTab ( ) {
populateAuthAccounts ( )
bindAuthAccountSelect ( )
bindAuthAccountLogOut ( )
}
2018-06-04 13:28:17 -07:00
/ * *
* Minecraft Tab
* /
2018-07-22 10:31:15 -07:00
/ * *
2018-06-04 13:28:17 -07:00
* Disable decimals , negative signs , and scientific notation .
* /
2018-07-22 10:31:15 -07:00
document . getElementById ( 'settingsGameWidth' ) . addEventListener ( 'keydown' , ( e ) => {
if ( /^[-.eE]$/ . test ( e . key ) ) {
2018-06-04 13:28:17 -07:00
e . preventDefault ( )
}
} )
2018-06-20 03:15:10 -07:00
document . getElementById ( 'settingsGameHeight' ) . addEventListener ( 'keydown' , ( e ) => {
2018-07-22 10:31:15 -07:00
if ( /^[-.eE]$/ . test ( e . key ) ) {
2018-06-04 13:28:17 -07:00
e . preventDefault ( )
}
} )
2018-07-23 22:14:26 -07:00
/ * *
* Mods Tab
* /
2018-07-29 05:42:11 -07:00
const settingsModsContainer = document . getElementById ( 'settingsModsContainer' )
2018-08-07 01:41:26 -07:00
/ * *
* Resolve and update the mods on the UI .
* /
2018-07-23 22:14:26 -07:00
function resolveModsForUI ( ) {
const serv = ConfigManager . getSelectedServer ( )
const distro = DistroManager . getDistribution ( )
const servConf = ConfigManager . getModConfiguration ( serv )
const modStr = parseModulesForUI ( distro . getServer ( serv ) . getModules ( ) , false , servConf . mods )
document . getElementById ( 'settingsReqModsContent' ) . innerHTML = modStr . reqMods
document . getElementById ( 'settingsOptModsContent' ) . innerHTML = modStr . optMods
}
2018-08-07 01:41:26 -07:00
/ * *
* Recursively build the mod UI elements .
*
* @ param { Object [ ] } mdls An array of modules to parse .
* @ param { boolean } submodules Whether or not we are parsing submodules .
* @ param { Object } servConf The server configuration object for this module level .
* /
2018-08-06 21:58:32 -07:00
function parseModulesForUI ( mdls , submodules , servConf ) {
2018-07-23 22:14:26 -07:00
let reqMods = ''
let optMods = ''
for ( const mdl of mdls ) {
if ( mdl . getType ( ) === DistroManager . Types . ForgeMod || mdl . getType ( ) === DistroManager . Types . LiteMod || mdl . getType ( ) === DistroManager . Types . LiteLoader ) {
if ( mdl . getRequired ( ) . isRequired ( ) ) {
2018-08-07 01:16:15 -07:00
reqMods += ` <div id=" ${ mdl . getVersionlessID ( ) } " class="settingsBaseMod settings ${ submodules ? 'Sub' : '' } Mod" enabled>
2018-07-23 22:14:26 -07:00
< div class = "settingsModContent" >
< div class = "settingsModMainWrapper" >
< div class = "settingsModStatus" > < / d i v >
< div class = "settingsModDetails" >
2018-07-23 23:25:39 -07:00
< span class = "settingsModName" > $ { mdl . getName ( ) } < / s p a n >
2018-07-23 22:14:26 -07:00
< span class = "settingsModVersion" > v$ { mdl . getVersion ( ) } < / s p a n >
< / d i v >
< / d i v >
< label class = "toggleSwitch" reqmod >
< input type = "checkbox" checked >
< span class = "toggleSwitchSlider" > < / s p a n >
< / l a b e l >
< / d i v >
$ { mdl . hasSubModules ( ) ? ` <div class="settingsSubModContainer">
2018-07-29 07:32:41 -07:00
$ { Object . values ( parseModulesForUI ( mdl . getSubModules ( ) , true , servConf [ mdl . getVersionlessID ( ) ] ) ) . join ( '' ) }
2018-07-23 22:14:26 -07:00
< / d i v > ` : ' ' }
< / d i v > `
} else {
const conf = servConf [ mdl . getVersionlessID ( ) ]
const val = typeof conf === 'object' ? conf . value : conf
2018-08-07 01:16:15 -07:00
optMods += ` <div id=" ${ mdl . getVersionlessID ( ) } " class="settingsBaseMod settings ${ submodules ? 'Sub' : '' } Mod" ${ val ? 'enabled' : '' } >
2018-07-23 22:14:26 -07:00
< div class = "settingsModContent" >
< div class = "settingsModMainWrapper" >
< div class = "settingsModStatus" > < / d i v >
< div class = "settingsModDetails" >
2018-07-23 23:25:39 -07:00
< span class = "settingsModName" > $ { mdl . getName ( ) } < / s p a n >
2018-07-23 22:14:26 -07:00
< span class = "settingsModVersion" > v$ { mdl . getVersion ( ) } < / s p a n >
< / d i v >
< / d i v >
< label class = "toggleSwitch" >
2018-07-29 05:42:11 -07:00
< input type = "checkbox" formod = "${mdl.getVersionlessID()}" $ { val ? 'checked' : '' } >
2018-07-23 22:14:26 -07:00
< span class = "toggleSwitchSlider" > < / s p a n >
< / l a b e l >
< / d i v >
$ { mdl . hasSubModules ( ) ? ` <div class="settingsSubModContainer">
2018-07-29 05:42:11 -07:00
$ { Object . values ( parseModulesForUI ( mdl . getSubModules ( ) , true , conf . mods ) ) . join ( '' ) }
2018-07-23 22:14:26 -07:00
< / d i v > ` : ' ' }
< / d i v > `
}
}
}
return {
reqMods ,
optMods
}
}
2018-07-29 05:42:11 -07:00
/ * *
* Bind functionality to mod config toggle switches . Switching the value
* will also switch the status color on the left of the mod UI .
* /
function bindModsToggleSwitch ( ) {
const sEls = settingsModsContainer . querySelectorAll ( '[formod]' )
Array . from ( sEls ) . map ( ( v , index , arr ) => {
v . onchange = ( ) => {
if ( v . checked ) {
document . getElementById ( v . getAttribute ( 'formod' ) ) . setAttribute ( 'enabled' , '' )
} else {
document . getElementById ( v . getAttribute ( 'formod' ) ) . removeAttribute ( 'enabled' )
}
}
} )
}
/ * *
* Save the mod configuration based on the UI values .
* /
function saveModConfiguration ( ) {
const serv = ConfigManager . getSelectedServer ( )
const modConf = ConfigManager . getModConfiguration ( serv )
modConf . mods = _saveModConfiguration ( modConf . mods )
ConfigManager . setModConfiguration ( serv , modConf )
}
/ * *
* Recursively save mod config with submods .
*
* @ param { Object } modConf Mod config object to save .
* /
function _saveModConfiguration ( modConf ) {
2018-11-20 02:19:59 -08:00
for ( let m of Object . entries ( modConf ) ) {
2018-07-29 07:32:41 -07:00
const tSwitch = settingsModsContainer . querySelectorAll ( ` [formod=' ${ m [ 0 ] } '] ` )
2018-08-07 01:16:15 -07:00
if ( ! tSwitch [ 0 ] . hasAttribute ( 'dropin' ) ) {
if ( typeof m [ 1 ] === 'boolean' ) {
modConf [ m [ 0 ] ] = tSwitch [ 0 ] . checked
} else {
if ( m [ 1 ] != null ) {
if ( tSwitch . length > 0 ) {
modConf [ m [ 0 ] ] . value = tSwitch [ 0 ] . checked
}
modConf [ m [ 0 ] ] . mods = _saveModConfiguration ( modConf [ m [ 0 ] ] . mods )
2018-07-29 07:32:41 -07:00
}
2018-07-29 05:42:11 -07:00
}
}
}
return modConf
}
2018-08-07 01:41:26 -07:00
// Drop-in mod elements.
2018-08-06 21:58:32 -07:00
2018-08-07 01:16:15 -07:00
let CACHE _SETTINGS _MODS _DIR
let CACHE _DROPIN _MODS
2018-08-07 01:41:26 -07:00
/ * *
* Resolve any located drop - in mods for this server and
* populate the results onto the UI .
* /
2018-08-07 01:16:15 -07:00
function resolveDropinModsForUI ( ) {
const serv = DistroManager . getDistribution ( ) . getServer ( ConfigManager . getSelectedServer ( ) )
CACHE _SETTINGS _MODS _DIR = path . join ( ConfigManager . getInstanceDirectory ( ) , serv . getID ( ) , 'mods' )
CACHE _DROPIN _MODS = DropinModUtil . scanForDropinMods ( CACHE _SETTINGS _MODS _DIR , serv . getMinecraftVersion ( ) )
let dropinMods = ''
for ( dropin of CACHE _DROPIN _MODS ) {
dropinMods += ` <div id=" ${ dropin . fullName } " class="settingsBaseMod settingsDropinMod" ${ ! dropin . disabled ? 'enabled' : '' } >
< div class = "settingsModContent" >
< div class = "settingsModMainWrapper" >
< div class = "settingsModStatus" > < / d i v >
< div class = "settingsModDetails" >
< span class = "settingsModName" > $ { dropin . name } < / s p a n >
< div class = "settingsDropinRemoveWrapper" >
< button class = "settingsDropinRemoveButton" remmod = "${dropin.fullName}" > Remove < / b u t t o n >
< / d i v >
< / d i v >
< / d i v >
< label class = "toggleSwitch" >
< input type = "checkbox" formod = "${dropin.fullName}" dropin $ { ! dropin . disabled ? 'checked' : '' } >
< span class = "toggleSwitchSlider" > < / s p a n >
< / l a b e l >
< / d i v >
< / d i v > `
}
document . getElementById ( 'settingsDropinModsContent' ) . innerHTML = dropinMods
}
2018-08-07 01:41:26 -07:00
/ * *
* Bind the remove button for each loaded drop - in mod .
* /
2018-08-07 01:16:15 -07:00
function bindDropinModsRemoveButton ( ) {
const sEls = settingsModsContainer . querySelectorAll ( '[remmod]' )
Array . from ( sEls ) . map ( ( v , index , arr ) => {
2021-06-23 17:27:04 -07:00
v . onclick = async ( ) => {
2018-08-07 01:16:15 -07:00
const fullName = v . getAttribute ( 'remmod' )
2021-06-23 17:27:04 -07:00
const res = await DropinModUtil . deleteDropinMod ( CACHE _SETTINGS _MODS _DIR , fullName )
2018-08-07 01:16:15 -07:00
if ( res ) {
document . getElementById ( fullName ) . remove ( )
} else {
setOverlayContent (
` Failed to Delete<br>Drop-in Mod ${ fullName } ` ,
'Make sure the file is not in use and try again.' ,
'Okay'
)
setOverlayHandler ( null )
toggleOverlay ( true )
}
}
} )
}
2018-08-07 01:41:26 -07:00
/ * *
* Bind functionality to the file system button for the selected
* server configuration .
* /
2018-08-07 01:16:15 -07:00
function bindDropinModFileSystemButton ( ) {
const fsBtn = document . getElementById ( 'settingsDropinFileSystemButton' )
fsBtn . onclick = ( ) => {
2018-12-21 03:51:08 -08:00
DropinModUtil . validateDir ( CACHE _SETTINGS _MODS _DIR )
2020-05-21 18:02:58 -07:00
shell . openPath ( CACHE _SETTINGS _MODS _DIR )
2018-08-07 01:16:15 -07:00
}
2018-12-01 05:20:42 -08:00
fsBtn . ondragenter = e => {
e . dataTransfer . dropEffect = 'move'
fsBtn . setAttribute ( 'drag' , '' )
e . preventDefault ( )
}
fsBtn . ondragover = e => {
e . preventDefault ( )
}
fsBtn . ondragleave = e => {
fsBtn . removeAttribute ( 'drag' )
}
fsBtn . ondrop = e => {
fsBtn . removeAttribute ( 'drag' )
e . preventDefault ( )
DropinModUtil . addDropinMods ( e . dataTransfer . files , CACHE _SETTINGS _MODS _DIR )
reloadDropinMods ( )
}
2018-08-07 01:16:15 -07:00
}
2018-08-07 01:41:26 -07:00
/ * *
* Save drop - in mod states . Enabling and disabling is just a matter
* of adding / removing the . disabled extension .
* /
2018-08-07 01:16:15 -07:00
function saveDropinModConfiguration ( ) {
for ( dropin of CACHE _DROPIN _MODS ) {
const dropinUI = document . getElementById ( dropin . fullName )
if ( dropinUI != null ) {
const dropinUIEnabled = dropinUI . hasAttribute ( 'enabled' )
if ( DropinModUtil . isDropinModEnabled ( dropin . fullName ) != dropinUIEnabled ) {
DropinModUtil . toggleDropinMod ( CACHE _SETTINGS _MODS _DIR , dropin . fullName , dropinUIEnabled ) . catch ( err => {
if ( ! isOverlayVisible ( ) ) {
setOverlayContent (
'Failed to Toggle<br>One or More Drop-in Mods' ,
err . message ,
'Okay'
)
setOverlayHandler ( null )
toggleOverlay ( true )
}
} )
}
}
}
}
2018-08-07 01:41:26 -07:00
// Refresh the drop-in mods when F5 is pressed.
// Only active on the mods tab.
2018-08-07 01:16:15 -07:00
document . addEventListener ( 'keydown' , ( e ) => {
if ( getCurrentView ( ) === VIEWS . settings && selectedSettingsTab === 'settingsTabMods' ) {
if ( e . key === 'F5' ) {
2018-12-01 05:20:42 -08:00
reloadDropinMods ( )
2018-12-21 05:02:24 -08:00
saveShaderpackSettings ( )
resolveShaderpacksForUI ( )
2018-08-07 01:16:15 -07:00
}
}
} )
2018-12-01 05:20:42 -08:00
function reloadDropinMods ( ) {
resolveDropinModsForUI ( )
bindDropinModsRemoveButton ( )
bindDropinModFileSystemButton ( )
bindModsToggleSwitch ( )
}
2018-12-21 03:51:08 -08:00
// Shaderpack
let CACHE _SETTINGS _INSTANCE _DIR
let CACHE _SHADERPACKS
let CACHE _SELECTED _SHADERPACK
/ * *
* Load shaderpack information .
* /
function resolveShaderpacksForUI ( ) {
const serv = DistroManager . getDistribution ( ) . getServer ( ConfigManager . getSelectedServer ( ) )
CACHE _SETTINGS _INSTANCE _DIR = path . join ( ConfigManager . getInstanceDirectory ( ) , serv . getID ( ) )
CACHE _SHADERPACKS = DropinModUtil . scanForShaderpacks ( CACHE _SETTINGS _INSTANCE _DIR )
CACHE _SELECTED _SHADERPACK = DropinModUtil . getEnabledShaderpack ( CACHE _SETTINGS _INSTANCE _DIR )
setShadersOptions ( CACHE _SHADERPACKS , CACHE _SELECTED _SHADERPACK )
}
function setShadersOptions ( arr , selected ) {
const cont = document . getElementById ( 'settingsShadersOptions' )
cont . innerHTML = ''
for ( let opt of arr ) {
const d = document . createElement ( 'DIV' )
d . innerHTML = opt . name
d . setAttribute ( 'value' , opt . fullName )
if ( opt . fullName === selected ) {
d . setAttribute ( 'selected' , '' )
document . getElementById ( 'settingsShadersSelected' ) . innerHTML = opt . name
}
d . addEventListener ( 'click' , function ( e ) {
this . parentNode . previousElementSibling . innerHTML = this . innerHTML
for ( let sib of this . parentNode . children ) {
sib . removeAttribute ( 'selected' )
}
this . setAttribute ( 'selected' , '' )
closeSettingsSelect ( )
} )
cont . appendChild ( d )
}
}
2018-12-21 05:02:24 -08:00
function saveShaderpackSettings ( ) {
let sel = 'OFF'
for ( let opt of document . getElementById ( 'settingsShadersOptions' ) . childNodes ) {
if ( opt . hasAttribute ( 'selected' ) ) {
sel = opt . getAttribute ( 'value' )
}
}
DropinModUtil . setEnabledShaderpack ( CACHE _SETTINGS _INSTANCE _DIR , sel )
}
function bindShaderpackButton ( ) {
const spBtn = document . getElementById ( 'settingsShaderpackButton' )
spBtn . onclick = ( ) => {
const p = path . join ( CACHE _SETTINGS _INSTANCE _DIR , 'shaderpacks' )
DropinModUtil . validateDir ( p )
2020-05-21 18:02:58 -07:00
shell . openPath ( p )
2018-12-21 05:02:24 -08:00
}
spBtn . ondragenter = e => {
e . dataTransfer . dropEffect = 'move'
spBtn . setAttribute ( 'drag' , '' )
e . preventDefault ( )
}
spBtn . ondragover = e => {
e . preventDefault ( )
}
spBtn . ondragleave = e => {
spBtn . removeAttribute ( 'drag' )
}
spBtn . ondrop = e => {
spBtn . removeAttribute ( 'drag' )
e . preventDefault ( )
DropinModUtil . addShaderpacks ( e . dataTransfer . files , CACHE _SETTINGS _INSTANCE _DIR )
2018-12-21 13:06:34 -08:00
saveShaderpackSettings ( )
2018-12-21 05:02:24 -08:00
resolveShaderpacksForUI ( )
}
}
2018-08-07 01:41:26 -07:00
// Server status bar functions.
/ * *
* Load the currently selected server information onto the mods tab .
* /
function loadSelectedServerOnModsTab ( ) {
const serv = DistroManager . getDistribution ( ) . getServer ( ConfigManager . getSelectedServer ( ) )
document . getElementById ( 'settingsSelServContent' ) . innerHTML = `
< img class = "serverListingImg" src = "${serv.getIcon()}" / >
< div class = "serverListingDetails" >
< span class = "serverListingName" > $ { serv . getName ( ) } < / s p a n >
< span class = "serverListingDescription" > $ { serv . getDescription ( ) } < / s p a n >
< div class = "serverListingInfo" >
< div class = "serverListingVersion" > $ { serv . getMinecraftVersion ( ) } < / d i v >
< div class = "serverListingRevision" > $ { serv . getVersion ( ) } < / d i v >
$ { serv . isMainServer ( ) ? ` <div class="serverListingStarWrapper">
< svg id = "Layer_1" viewBox = "0 0 107.45 104.74" width = "20px" height = "20px" >
< defs >
< style > . cls - 1 { fill : # fff ; } . cls - 2 { fill : none ; stroke : # fff ; stroke - miterlimit : 10 ; } < / s t y l e >
< / d e f s >
< path class = "cls-1" d = "M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z" / >
< circle class = "cls-2" cx = "53.73" cy = "53.9" r = "38" / >
< / s v g >
< span class = "serverListingStarTooltip" > Main Server < / s p a n >
< / d i v > ` : ' ' }
< / d i v >
< / d i v >
`
}
// Bind functionality to the server switch button.
document . getElementById ( 'settingsSwitchServerButton' ) . addEventListener ( 'click' , ( e ) => {
e . target . blur ( )
toggleServerSelection ( true )
} )
2018-11-20 02:19:59 -08:00
/ * *
* Save mod configuration for the current selected server .
* /
function saveAllModConfigurations ( ) {
saveModConfiguration ( )
ConfigManager . save ( )
saveDropinModConfiguration ( )
}
2018-08-07 01:41:26 -07:00
/ * *
* Function to refresh the mods tab whenever the selected
* server is changed .
* /
2018-08-06 21:58:32 -07:00
function animateModsTabRefresh ( ) {
$ ( '#settingsTabMods' ) . fadeOut ( 500 , ( ) => {
prepareModsTab ( )
$ ( '#settingsTabMods' ) . fadeIn ( 500 )
} )
}
2018-07-23 22:14:26 -07:00
/ * *
2018-08-06 21:58:32 -07:00
* Prepare the Mods tab for display .
2018-07-23 22:14:26 -07:00
* /
2018-08-06 21:58:32 -07:00
function prepareModsTab ( first ) {
2018-07-23 22:14:26 -07:00
resolveModsForUI ( )
2018-08-07 01:16:15 -07:00
resolveDropinModsForUI ( )
2018-12-21 03:51:08 -08:00
resolveShaderpacksForUI ( )
2018-08-07 01:16:15 -07:00
bindDropinModsRemoveButton ( )
bindDropinModFileSystemButton ( )
2018-12-21 05:02:24 -08:00
bindShaderpackButton ( )
2018-07-29 05:42:11 -07:00
bindModsToggleSwitch ( )
2018-08-06 21:58:32 -07:00
loadSelectedServerOnModsTab ( )
2018-07-23 22:14:26 -07:00
}
2018-06-11 19:11:05 -07:00
/ * *
* Java Tab
* /
2018-06-20 03:15:10 -07:00
// DOM Cache
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 settingsJavaExecDetails = document . getElementById ( 'settingsJavaExecDetails' )
2018-06-14 03:09:09 -07:00
// Store maximum memory values.
const SETTINGS _MAX _MEMORY = ConfigManager . getAbsoluteMaxRAM ( )
const SETTINGS _MIN _MEMORY = ConfigManager . getAbsoluteMinRAM ( )
// Set the max and min values for the ranged sliders.
settingsMaxRAMRange . setAttribute ( 'max' , SETTINGS _MAX _MEMORY )
settingsMaxRAMRange . setAttribute ( 'min' , SETTINGS _MIN _MEMORY )
settingsMinRAMRange . setAttribute ( 'max' , SETTINGS _MAX _MEMORY )
settingsMinRAMRange . setAttribute ( 'min' , SETTINGS _MIN _MEMORY )
2018-06-11 19:11:05 -07:00
2018-06-14 03:09:09 -07:00
// Bind on change event for min memory container.
2018-06-11 19:11:05 -07:00
settingsMinRAMRange . onchange = ( e ) => {
2018-06-14 03:09:09 -07:00
// Current range values
2018-06-11 19:11:05 -07:00
const sMaxV = Number ( settingsMaxRAMRange . getAttribute ( 'value' ) )
const sMinV = Number ( settingsMinRAMRange . getAttribute ( 'value' ) )
2018-06-14 03:09:09 -07:00
// Get reference to range bar.
2018-06-12 00:48:36 -07:00
const bar = e . target . getElementsByClassName ( 'rangeSliderBar' ) [ 0 ]
2018-06-14 03:09:09 -07:00
// Calculate effective total memory.
2018-06-12 00:48:36 -07:00
const max = ( os . totalmem ( ) - 1000000000 ) / 1000000000
2018-06-14 03:09:09 -07:00
// Change range bar color based on the selected value.
2018-06-12 00:48:36 -07:00
if ( sMinV >= max / 2 ) {
bar . style . background = '#e86060'
} else if ( sMinV >= max / 4 ) {
bar . style . background = '#e8e18b'
} else {
bar . style . background = null
}
2018-06-14 03:09:09 -07:00
// Increase maximum memory if the minimum exceeds its value.
2018-06-11 19:11:05 -07:00
if ( sMaxV < sMinV ) {
const sliderMeta = calculateRangeSliderMeta ( settingsMaxRAMRange )
updateRangedSlider ( settingsMaxRAMRange , sMinV ,
2018-07-22 10:31:15 -07:00
( ( sMinV - sliderMeta . min ) / sliderMeta . step ) * sliderMeta . inc )
2018-06-12 00:25:36 -07:00
settingsMaxRAMLabel . innerHTML = sMinV . toFixed ( 1 ) + 'G'
2018-06-11 19:11:05 -07:00
}
2018-06-14 03:09:09 -07:00
// Update label
2018-06-12 00:25:36 -07:00
settingsMinRAMLabel . innerHTML = sMinV . toFixed ( 1 ) + 'G'
2018-06-11 19:11:05 -07:00
}
2018-06-12 00:25:36 -07:00
2018-06-14 03:09:09 -07:00
// Bind on change event for max memory container.
2018-06-11 19:11:05 -07:00
settingsMaxRAMRange . onchange = ( e ) => {
2018-06-14 03:09:09 -07:00
// Current range values
2018-06-11 19:11:05 -07:00
const sMaxV = Number ( settingsMaxRAMRange . getAttribute ( 'value' ) )
const sMinV = Number ( settingsMinRAMRange . getAttribute ( 'value' ) )
2018-06-14 03:09:09 -07:00
// Get reference to range bar.
2018-06-12 00:48:36 -07:00
const bar = e . target . getElementsByClassName ( 'rangeSliderBar' ) [ 0 ]
2018-06-14 03:09:09 -07:00
// Calculate effective total memory.
2018-06-12 00:48:36 -07:00
const max = ( os . totalmem ( ) - 1000000000 ) / 1000000000
2018-06-14 03:09:09 -07:00
// Change range bar color based on the selected value.
2018-06-12 00:48:36 -07:00
if ( sMaxV >= max / 2 ) {
bar . style . background = '#e86060'
} else if ( sMaxV >= max / 4 ) {
bar . style . background = '#e8e18b'
} else {
bar . style . background = null
}
2018-06-14 03:09:09 -07:00
// Decrease the minimum memory if the maximum value is less.
2018-06-11 19:11:05 -07:00
if ( sMaxV < sMinV ) {
2018-06-12 00:25:36 -07:00
const sliderMeta = calculateRangeSliderMeta ( settingsMaxRAMRange )
updateRangedSlider ( settingsMinRAMRange , sMaxV ,
2018-07-22 10:31:15 -07:00
( ( sMaxV - sliderMeta . min ) / sliderMeta . step ) * sliderMeta . inc )
2018-06-12 00:25:36 -07:00
settingsMinRAMLabel . innerHTML = sMaxV . toFixed ( 1 ) + 'G'
2018-06-11 19:11:05 -07:00
}
2018-06-12 00:25:36 -07:00
settingsMaxRAMLabel . innerHTML = sMaxV . toFixed ( 1 ) + 'G'
2018-06-11 19:11:05 -07:00
}
2018-06-14 03:09:09 -07:00
/ * *
* Calculate common values for a ranged slider .
*
* @ param { Element } v The range slider to calculate against .
* @ returns { Object } An object with meta values for the provided ranged slider .
* /
2018-06-11 19:11:05 -07:00
function calculateRangeSliderMeta ( v ) {
const val = {
max : Number ( v . getAttribute ( 'max' ) ) ,
min : Number ( v . getAttribute ( 'min' ) ) ,
step : Number ( v . getAttribute ( 'step' ) ) ,
}
2018-06-12 00:48:36 -07:00
val . ticks = ( val . max - val . min ) / val . step
2018-06-11 19:11:05 -07:00
val . inc = 100 / val . ticks
return val
}
2018-06-14 03:09:09 -07:00
/ * *
* Binds functionality to the ranged sliders . They ' re more than
* just divs now : ' ) .
* /
2018-06-11 19:11:05 -07:00
function bindRangeSlider ( ) {
Array . from ( document . getElementsByClassName ( 'rangeSlider' ) ) . map ( ( v ) => {
2018-06-14 03:09:09 -07:00
// Reference the track (thumb).
2018-06-11 19:11:05 -07:00
const track = v . getElementsByClassName ( 'rangeSliderTrack' ) [ 0 ]
2018-06-14 03:09:09 -07:00
// Set the initial slider value.
2018-06-11 19:11:05 -07:00
const value = v . getAttribute ( 'value' )
const sliderMeta = calculateRangeSliderMeta ( v )
2018-06-14 03:09:09 -07:00
updateRangedSlider ( v , value , ( ( value - sliderMeta . min ) / sliderMeta . step ) * sliderMeta . inc )
// The magic happens when we click on the track.
2018-06-11 19:11:05 -07:00
track . onmousedown = ( e ) => {
2018-06-14 03:09:09 -07:00
// Stop moving the track on mouse up.
2018-06-11 19:11:05 -07:00
document . onmouseup = ( e ) => {
document . onmousemove = null
document . onmouseup = null
}
2018-06-14 03:09:09 -07:00
// Move slider according to the mouse position.
2018-06-11 19:11:05 -07:00
document . onmousemove = ( e ) => {
2018-06-14 03:09:09 -07:00
// Distance from the beginning of the bar in pixels.
2018-06-11 19:11:05 -07:00
const diff = e . pageX - v . offsetLeft - track . offsetWidth / 2
2018-06-14 03:09:09 -07:00
// Don't move the track off the bar.
2018-06-11 19:11:05 -07:00
if ( diff >= 0 && diff <= v . offsetWidth - track . offsetWidth / 2 ) {
2018-06-14 03:09:09 -07:00
// Convert the difference to a percentage.
2018-06-11 19:11:05 -07:00
const perc = ( diff / v . offsetWidth ) * 100
2018-06-14 03:09:09 -07:00
// Calculate the percentage of the closest notch.
2018-06-11 19:11:05 -07:00
const notch = Number ( perc / sliderMeta . inc ) . toFixed ( 0 ) * sliderMeta . inc
2018-06-12 00:48:36 -07:00
2018-06-14 03:09:09 -07:00
// If we're close to that notch, stick to it.
2018-06-11 19:11:05 -07:00
if ( Math . abs ( perc - notch ) < sliderMeta . inc / 2 ) {
2018-06-12 00:48:36 -07:00
updateRangedSlider ( v , sliderMeta . min + ( sliderMeta . step * ( notch / sliderMeta . inc ) ) , notch )
2018-06-11 19:11:05 -07:00
}
}
}
}
} )
}
2018-06-14 03:09:09 -07:00
/ * *
* Update a ranged slider ' s value and position .
*
* @ param { Element } element The ranged slider to update .
* @ param { string | number } value The new value for the ranged slider .
* @ param { number } notch The notch that the slider should now be at .
* /
2018-06-11 19:11:05 -07:00
function updateRangedSlider ( element , value , notch ) {
const oldVal = element . getAttribute ( 'value' )
const bar = element . getElementsByClassName ( 'rangeSliderBar' ) [ 0 ]
const track = element . getElementsByClassName ( 'rangeSliderTrack' ) [ 0 ]
2018-06-14 03:09:09 -07:00
2018-06-11 19:11:05 -07:00
element . setAttribute ( 'value' , value )
2018-06-14 03:09:09 -07:00
2018-06-21 18:04:10 -07:00
if ( notch < 0 ) {
notch = 0
} else if ( notch > 100 ) {
notch = 100
}
2018-06-11 19:11:05 -07:00
const event = new MouseEvent ( 'change' , {
target : element ,
type : 'change' ,
bubbles : false ,
cancelable : true
} )
2018-06-14 03:09:09 -07:00
2018-06-11 19:11:05 -07:00
let cancelled = ! element . dispatchEvent ( event )
2018-06-14 03:09:09 -07:00
2018-06-11 19:11:05 -07:00
if ( ! cancelled ) {
track . style . left = notch + '%'
bar . style . width = notch + '%'
} else {
element . setAttribute ( 'value' , oldVal )
}
}
2018-06-14 03:09:09 -07:00
/ * *
* Display the total and available RAM .
* /
function populateMemoryStatus ( ) {
2018-06-12 00:25:36 -07:00
settingsMemoryTotal . innerHTML = Number ( ( os . totalmem ( ) - 1000000000 ) / 1000000000 ) . toFixed ( 1 ) + 'G'
settingsMemoryAvail . innerHTML = Number ( os . freemem ( ) / 1000000000 ) . toFixed ( 1 ) + 'G'
}
2018-06-14 03:09:09 -07:00
/ * *
* Validate the provided executable path and display the data on
* the UI .
*
* @ param { string } execPath The executable path to populate against .
* /
function populateJavaExecDetails ( execPath ) {
2019-03-05 20:05:01 -08:00
const jg = new JavaGuard ( DistroManager . getDistribution ( ) . getServer ( ConfigManager . getSelectedServer ( ) ) . getMinecraftVersion ( ) )
jg . _validateJavaBinary ( execPath ) . then ( v => {
2018-06-14 03:09:09 -07:00
if ( v . valid ) {
2020-09-08 18:25:19 -07:00
const vendor = v . vendor != null ? ` ( ${ v . vendor } ) ` : ''
2018-12-31 07:39:27 -08:00
if ( v . version . major < 9 ) {
2020-09-08 18:25:19 -07:00
settingsJavaExecDetails . innerHTML = ` Selected: Java ${ v . version . major } Update ${ v . version . update } (x ${ v . arch } ) ${ vendor } `
2018-12-31 07:39:27 -08:00
} else {
2020-09-08 18:25:19 -07:00
settingsJavaExecDetails . innerHTML = ` Selected: Java ${ v . version . major } . ${ v . version . minor } . ${ v . version . revision } (x ${ v . arch } ) ${ vendor } `
2018-12-31 07:39:27 -08:00
}
2018-06-14 03:09:09 -07:00
} else {
settingsJavaExecDetails . innerHTML = 'Invalid Selection'
}
} )
}
/ * *
* Prepare the Java tab for display .
* /
2018-06-11 19:11:05 -07:00
function prepareJavaTab ( ) {
bindRangeSlider ( )
2018-06-14 03:09:09 -07:00
populateMemoryStatus ( )
2018-06-11 19:11:05 -07:00
}
2018-06-20 03:15:10 -07:00
/ * *
* About Tab
* /
2018-08-13 03:13:13 -07:00
const settingsTabAbout = document . getElementById ( 'settingsTabAbout' )
const settingsAboutChangelogTitle = settingsTabAbout . getElementsByClassName ( 'settingsChangelogTitle' ) [ 0 ]
const settingsAboutChangelogText = settingsTabAbout . getElementsByClassName ( 'settingsChangelogText' ) [ 0 ]
const settingsAboutChangelogButton = settingsTabAbout . getElementsByClassName ( 'settingsChangelogButton' ) [ 0 ]
2018-06-20 03:15:10 -07:00
// Bind the devtools toggle button.
document . getElementById ( 'settingsAboutDevToolsButton' ) . onclick = ( e ) => {
let window = remote . getCurrentWindow ( )
window . toggleDevTools ( )
}
/ * *
2018-08-13 03:13:13 -07:00
* Return whether or not the provided version is a prerelease .
*
* @ param { string } version The semver version to test .
* @ returns { boolean } True if the version is a prerelease , otherwise false .
2018-06-20 03:15:10 -07:00
* /
2018-08-13 03:13:13 -07:00
function isPrerelease ( version ) {
2018-06-20 03:15:10 -07:00
const preRelComp = semver . prerelease ( version )
2018-08-13 03:13:13 -07:00
return preRelComp != null && preRelComp . length > 0
}
/ * *
* Utility method to display version information on the
* About and Update settings tabs .
*
* @ param { string } version The semver version to display .
* @ param { Element } valueElement The value element .
* @ param { Element } titleElement The title element .
* @ param { Element } checkElement The check mark element .
* /
function populateVersionInformation ( version , valueElement , titleElement , checkElement ) {
valueElement . innerHTML = version
if ( isPrerelease ( version ) ) {
titleElement . innerHTML = 'Pre-release'
titleElement . style . color = '#ff886d'
checkElement . style . background = '#ff886d'
2018-06-20 03:15:10 -07:00
} else {
2018-08-13 03:13:13 -07:00
titleElement . innerHTML = 'Stable Release'
titleElement . style . color = null
checkElement . style . background = null
2018-06-20 03:15:10 -07:00
}
}
2018-08-13 03:13:13 -07:00
/ * *
* Retrieve the version information and display it on the UI .
* /
function populateAboutVersionInformation ( ) {
populateVersionInformation ( remote . app . getVersion ( ) , document . getElementById ( 'settingsAboutCurrentVersionValue' ) , document . getElementById ( 'settingsAboutCurrentVersionTitle' ) , document . getElementById ( 'settingsAboutCurrentVersionCheck' ) )
}
2018-06-20 03:15:10 -07:00
/ * *
* Fetches the GitHub atom release feed and parses it for the release notes
* of the current version . This value is displayed on the UI .
* /
function populateReleaseNotes ( ) {
$ . ajax ( {
2019-09-05 13:08:47 -07:00
url : 'https://github.com/dscalzi/HeliosLauncher/releases.atom' ,
2018-06-20 03:15:10 -07:00
success : ( data ) => {
const version = 'v' + remote . app . getVersion ( )
const entries = $ ( data ) . find ( 'entry' )
for ( let i = 0 ; i < entries . length ; i ++ ) {
const entry = $ ( entries [ i ] )
let id = entry . find ( 'id' ) . text ( )
id = id . substring ( id . lastIndexOf ( '/' ) + 1 )
if ( id === version ) {
2018-08-13 03:13:13 -07:00
settingsAboutChangelogTitle . innerHTML = entry . find ( 'title' ) . text ( )
settingsAboutChangelogText . innerHTML = entry . find ( 'content' ) . text ( )
settingsAboutChangelogButton . href = entry . find ( 'link' ) . attr ( 'href' )
2018-06-20 03:15:10 -07:00
}
}
} ,
timeout : 2500
} ) . catch ( err => {
2018-08-13 03:13:13 -07:00
settingsAboutChangelogText . innerHTML = 'Failed to load release notes.'
2018-06-20 03:15:10 -07:00
} )
}
/ * *
* Prepare account tab for display .
* /
function prepareAboutTab ( ) {
2018-08-13 03:13:13 -07:00
populateAboutVersionInformation ( )
2018-06-20 03:15:10 -07:00
populateReleaseNotes ( )
}
2018-08-13 03:13:13 -07:00
/ * *
* Update Tab
* /
const settingsTabUpdate = document . getElementById ( 'settingsTabUpdate' )
const settingsUpdateTitle = document . getElementById ( 'settingsUpdateTitle' )
const settingsUpdateVersionCheck = document . getElementById ( 'settingsUpdateVersionCheck' )
const settingsUpdateVersionTitle = document . getElementById ( 'settingsUpdateVersionTitle' )
const settingsUpdateVersionValue = document . getElementById ( 'settingsUpdateVersionValue' )
const settingsUpdateChangelogTitle = settingsTabUpdate . getElementsByClassName ( 'settingsChangelogTitle' ) [ 0 ]
const settingsUpdateChangelogText = settingsTabUpdate . getElementsByClassName ( 'settingsChangelogText' ) [ 0 ]
const settingsUpdateChangelogCont = settingsTabUpdate . getElementsByClassName ( 'settingsChangelogContainer' ) [ 0 ]
const settingsUpdateActionButton = document . getElementById ( 'settingsUpdateActionButton' )
/ * *
* Update the properties of the update action button .
*
* @ param { string } text The new button text .
* @ param { boolean } disabled Optional . Disable or enable the button
* @ param { function } handler Optional . New button event handler .
* /
function settingsUpdateButtonStatus ( text , disabled = false , handler = null ) {
settingsUpdateActionButton . innerHTML = text
settingsUpdateActionButton . disabled = disabled
if ( handler != null ) {
settingsUpdateActionButton . onclick = handler
}
}
/ * *
* Populate the update tab with relevant information .
*
* @ param { Object } data The update data .
* /
function populateSettingsUpdateInformation ( data ) {
if ( data != null ) {
settingsUpdateTitle . innerHTML = ` New ${ isPrerelease ( data . version ) ? 'Pre-release' : 'Release' } Available `
settingsUpdateChangelogCont . style . display = null
settingsUpdateChangelogTitle . innerHTML = data . releaseName
settingsUpdateChangelogText . innerHTML = data . releaseNotes
populateVersionInformation ( data . version , settingsUpdateVersionValue , settingsUpdateVersionTitle , settingsUpdateVersionCheck )
2018-11-18 18:51:48 -08:00
if ( process . platform === 'darwin' ) {
2018-11-18 19:03:17 -08:00
settingsUpdateButtonStatus ( 'Download from GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Close the launcher and run the dmg to update.</span>' , false , ( ) => {
2018-11-18 18:51:48 -08:00
shell . openExternal ( data . darwindownload )
} )
} else {
settingsUpdateButtonStatus ( 'Downloading..' , true )
}
2018-08-13 03:13:13 -07:00
} else {
settingsUpdateTitle . innerHTML = 'You Are Running the Latest Version'
settingsUpdateChangelogCont . style . display = 'none'
populateVersionInformation ( remote . app . getVersion ( ) , settingsUpdateVersionValue , settingsUpdateVersionTitle , settingsUpdateVersionCheck )
settingsUpdateButtonStatus ( 'Check for Updates' , false , ( ) => {
2018-08-13 03:31:46 -07:00
if ( ! isDev ) {
ipcRenderer . send ( 'autoUpdateAction' , 'checkForUpdate' )
settingsUpdateButtonStatus ( 'Checking for Updates..' , true )
}
2018-08-13 03:13:13 -07:00
} )
}
}
/ * *
* Prepare update tab for display .
*
* @ param { Object } data The update data .
* /
function prepareUpdateTab ( data = null ) {
populateSettingsUpdateInformation ( data )
}
2018-06-11 19:11:05 -07:00
2018-05-30 19:22:17 -07:00
/ * *
* Settings preparation functions .
* /
2018-07-22 10:31:15 -07:00
/ * *
2018-05-30 19:22:17 -07:00
* Prepare the entire settings UI .
2018-06-04 13:28:17 -07:00
*
* @ param { boolean } first Whether or not it is the first load .
2018-05-30 19:22:17 -07:00
* /
2018-06-04 13:28:17 -07:00
function prepareSettings ( first = false ) {
if ( first ) {
setupSettingsTabs ( )
initSettingsValidators ( )
2018-08-13 03:13:13 -07:00
prepareUpdateTab ( )
2018-07-23 22:14:26 -07:00
} else {
prepareModsTab ( )
2018-06-04 13:28:17 -07:00
}
initSettingsValues ( )
2018-05-30 19:22:17 -07:00
prepareAccountsTab ( )
2018-06-11 19:11:05 -07:00
prepareJavaTab ( )
2018-06-20 03:15:10 -07:00
prepareAboutTab ( )
2018-05-30 19:22:17 -07:00
}
// Prepare the settings UI on startup.
2019-03-05 20:05:01 -08:00
//prepareSettings(true)