`
}
}
}
return {
reqMods,
optMods
}
}
/**
* 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) {
for (let m of Object.entries(modConf)) {
const tSwitch = settingsModsContainer.querySelectorAll(`[formod='${m[0]}']`)
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)
}
}
}
}
return modConf
}
// Drop-in mod elements.
let CACHE_SETTINGS_MODS_DIR
let CACHE_DROPIN_MODS
/**
* Resolve any located drop-in mods for this server and
* populate the results onto the UI.
*/
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 += `
${dropin.name}
`
}
document.getElementById('settingsDropinModsContent').innerHTML = dropinMods
}
function resolveServerCodesForUI(){
/* Server Codes */
let servCodes = ''
for(let servCode of ConfigManager.getServerCodes()){
const servs = DistroManager.getDistribution().getServersFromCode(servCode)
const valid = servs && servs.length
servCodes +=
`
${servCode}
`
}
document.getElementById('settingsServerCodesListContent').innerHTML = servCodes
/* Server Names List */
for(let ele of document.getElementsByClassName('settingsServerCodeServerNamesContent')){
servNames = ''
const code = ele.getAttribute('code')
const servs = DistroManager.getDistribution().getServersFromCode(code)
const valid = servs && servs.length
if(valid){
for(let serv of servs){
loggerSettings.log('server: ' + serv.getName())
servNames +=
`
${serv.getName()}
`
}
} else {
servNames =
`
Invalid Code
`
}
ele.innerHTML = servNames
}
}
/**
* Bind the remove button for each loaded drop-in mod.
*/
function bindDropinModsRemoveButton() {
const sEls = settingsModsContainer.querySelectorAll('[remmod]')
Array.from(sEls).map((v, index, arr) => {
v.onclick = () => {
const fullName = v.getAttribute('remmod')
const res = DropinModUtil.deleteDropinMod(CACHE_SETTINGS_MODS_DIR, fullName)
if (res) {
document.getElementById(fullName).remove()
} else {
setOverlayContent(
`Failed to Delete Drop-in Mod ${fullName}`,
'Make sure the file is not in use and try again.',
'Okay'
)
setOverlayHandler(null)
toggleOverlay(true)
}
}
})
}
/**
* Bind functionality to the file system button for the selected
* server configuration.
*/
function bindDropinModFileSystemButton() {
const fsBtn = document.getElementById('settingsDropinFileSystemButton')
fsBtn.onclick = () => {
DropinModUtil.validateDir(CACHE_SETTINGS_MODS_DIR)
shell.openPath(CACHE_SETTINGS_MODS_DIR)
}
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()
}
}
/**
* Save drop-in mod states. Enabling and disabling is just a matter
* of adding/removing the .disabled extension.
*/
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 One or More Drop-in Mods',
err.message,
'Okay'
)
setOverlayHandler(null)
toggleOverlay(true)
}
})
}
}
}
}
// Refresh the drop-in mods when F5 is pressed.
// Only active on the mods tab.
document.addEventListener('keydown', (e) => {
if (getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods') {
if (e.key === 'F5') {
reloadDropinMods()
resolveShaderpacksForUI()
}
}
})
function reloadDropinMods() {
resolveDropinModsForUI()
bindDropinModsRemoveButton()
bindDropinModFileSystemButton()
bindModsToggleSwitch()
}
// 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)
}
}
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)
shell.openPath(p)
}
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)
saveShaderpackSettings()
resolveShaderpacksForUI()
}
}
// 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 = `
${serv.getName()}${serv.getDescription()}
${serv.getVersion()}
`
}
// Bind functionality to the server switch button.
document.getElementById('settingsSwitchServerButton').addEventListener('click', (e) => {
e.target.blur()
toggleServerSelection(true)
})
/**
* Save mod configuration for the current selected server.
*/
function saveAllModConfigurations() {
saveModConfiguration()
ConfigManager.save()
saveDropinModConfiguration()
}
/**
* Function to refresh the mods tab whenever the selected
* server is changed.
*/
function animateModsTabRefresh(){
$('#settingsTabMods').fadeOut(150, () => {
prepareModsTab()
$('#settingsTabMods').fadeIn(150)
})
}
/**
* Prepare the Mods tab for display.
*/
function prepareModsTab(first) {
resolveModsForUI()
resolveDropinModsForUI()
resolveShaderpacksForUI()
bindDropinModsRemoveButton()
bindDropinModFileSystemButton()
bindShaderpackButton()
bindModsToggleSwitch()
loadSelectedServerOnModsTab()
}
/**
* Java Tab
*/
// 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')
// 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)
// Bind on change event for min memory container.
settingsMinRAMRange.onchange = (e) => {
// Current range values
const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
// Get reference to range bar.
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
// Calculate effective total memory.
const max = (os.totalmem() - 1000000000) / 1000000000
// Change range bar color based on the selected value.
if(sMinV >= max/1.25){
bar.style.background = '#e86060'
} else if(sMinV >= max/2) {
bar.style.background = '#e8e18b'
} else {
bar.style.background = null
}
// Increase maximum memory if the minimum exceeds its value.
if (sMaxV < sMinV) {
const sliderMeta = calculateRangeSliderMeta(settingsMaxRAMRange)
updateRangedSlider(settingsMaxRAMRange, sMinV,
((sMinV - sliderMeta.min) / sliderMeta.step) * sliderMeta.inc)
settingsMaxRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
}
// Update label
settingsMinRAMLabel.innerHTML = sMinV.toFixed(1) + 'G'
}
// Bind on change event for max memory container.
settingsMaxRAMRange.onchange = (e) => {
// Current range values
const sMaxV = Number(settingsMaxRAMRange.getAttribute('value'))
const sMinV = Number(settingsMinRAMRange.getAttribute('value'))
// Get reference to range bar.
const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
// Calculate effective total memory.
const max = (os.totalmem() - 1000000000) / 1000000000
// Change range bar color based on the selected value.
if(sMaxV >= max/1.25){
bar.style.background = '#e86060'
} else if(sMaxV >= max/2) {
bar.style.background = '#e8e18b'
} else {
bar.style.background = null
}
// Decrease the minimum memory if the maximum value is less.
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'
}
/**
* 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.
*/
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
}
/**
* Binds functionality to the ranged sliders. They're more than
* just divs now :').
*/
function bindRangeSlider() {
Array.from(document.getElementsByClassName('rangeSlider')).map((v) => {
// Reference the track (thumb).
const track = v.getElementsByClassName('rangeSliderTrack')[0]
// Set the initial slider value.
const value = v.getAttribute('value')
const sliderMeta = calculateRangeSliderMeta(v)
updateRangedSlider(v, value, ((value - sliderMeta.min) / sliderMeta.step) * sliderMeta.inc)
// The magic happens when we click on the track.
track.onmousedown = (e) => {
// Stop moving the track on mouse up.
document.onmouseup = (e) => {
document.onmousemove = null
document.onmouseup = null
}
// Move slider according to the mouse position.
document.onmousemove = (e) => {
// Distance from the beginning of the bar in pixels.
const diff = e.pageX - v.offsetLeft - track.offsetWidth / 2
// Don't move the track off the bar.
if (diff >= 0 && diff <= v.offsetWidth - track.offsetWidth / 2) {
// Convert the difference to a percentage.
const perc = (diff / v.offsetWidth) * 100
// Calculate the percentage of the closest notch.
const notch = Number(perc / sliderMeta.inc).toFixed(0) * sliderMeta.inc
// If we're close to that notch, stick to it.
if (Math.abs(perc - notch) < sliderMeta.inc / 2) {
updateRangedSlider(v, sliderMeta.min + (sliderMeta.step * (notch / sliderMeta.inc)), notch)
}
}
}
}
})
}
/**
* 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.
*/
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)
if (notch < 0) {
notch = 0
} else if (notch > 100) {
notch = 100
}
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)
}
}
/**
* Display the total and available RAM.
*/
function populateMemoryStatus() {
settingsMemoryTotal.innerHTML = Number((os.totalmem() - 1000000000) / 1000000000).toFixed(1) + 'G'
settingsMemoryAvail.innerHTML = Number(os.freemem() / 1000000000).toFixed(1) + 'G'
}
/**
* Validate the provided executable path and display the data on
* the UI.
*
* @param {string} execPath The executable path to populate against.
*/
function populateJavaExecDetails(execPath) {
const jg = new JavaGuard(DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion())
jg._validateJavaBinary(execPath).then(v => {
if (v.valid) {
const vendor = v.vendor != null ? ` (${v.vendor})` : ''
if (v.version.major < 9) {
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major} Update ${v.version.update} (x${v.arch})${vendor}`
} else {
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major}.${v.version.minor}.${v.version.revision} (x${v.arch})${vendor}`
}
} else {
settingsJavaExecDetails.innerHTML = 'Invalid Selection'
}
})
}
/**
* Prepare the Java tab for display.
*/
function prepareJavaTab() {
bindRangeSlider()
populateMemoryStatus()
}
/**
* About Tab
*/
const settingsTabAbout = document.getElementById('settingsTabAbout')
const settingsAboutChangelogTitle = settingsTabAbout.getElementsByClassName('settingsChangelogTitle')[0]
const settingsAboutChangelogText = settingsTabAbout.getElementsByClassName('settingsChangelogText')[0]
const settingsAboutChangelogButton = settingsTabAbout.getElementsByClassName('settingsChangelogButton')[0]
// Bind the devtools toggle button.
document.getElementById('settingsAboutDevToolsButton').onclick = (e) => {
let window = remote.getCurrentWindow()
window.toggleDevTools()
}
/**
* 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.
*/
function isPrerelease(version) {
const preRelComp = semver.prerelease(version)
if(preRelComp != null && preRelComp.includes('release')) {
return false
}
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'
} else {
titleElement.innerHTML = 'Stable Release'
titleElement.style.color = null
checkElement.style.background = null
}
}
/**
* 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'))
}
/**
* 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({
url: 'https://github.com/ModRealms-Network/HeliosLauncher/releases.atom',
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) {
settingsAboutChangelogTitle.innerHTML = entry.find('title').text()
settingsAboutChangelogText.innerHTML = entry.find('content').text()
settingsAboutChangelogButton.href = entry.find('link').attr('href')
}
}
},
timeout: 10000
}).catch(err => {
settingsAboutChangelogText.innerHTML = 'Failed to load release notes.'
})
}
/**
* Prepare account tab for display.
*/
function prepareAboutTab() {
populateAboutVersionInformation()
populateReleaseNotes()
}
/**
* 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)
if (process.platform === 'darwin') {
settingsUpdateButtonStatus('Download from GitHubClose the launcher and run the dmg to update.', false, () => {
shell.openExternal(data.darwindownload)
})
} else {
settingsUpdateButtonStatus('Downloading...', true)
}
} 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, () => {
if (!isDev) {
ipcRenderer.send('autoUpdateAction', 'checkForUpdate')
settingsUpdateButtonStatus('Checking for Updates..', true)
}
})
}
}
/**
* Prepare update tab for display.
*
* @param {Object} data The update data.
*/
function prepareUpdateTab(data = null) {
populateSettingsUpdateInformation(data)
}
/**
* 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()
prepareUpdateTab()
} else {
prepareModsTab()
}
initSettingsValues()
prepareAccountsTab()
prepareJavaTab()
prepareLauncherTab()
prepareAboutTab()
}
// Prepare the settings UI on startup.
//prepareSettings(true)