mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-22 19:52:14 -08:00
Added support for drop-in mods on the UI.
This commit is contained in:
parent
ff3f2bfb8d
commit
556199aa55
@ -1042,6 +1042,11 @@ body, button {
|
|||||||
margin-top: 5%;
|
margin-top: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add spacing to the bottom of each settings tab. */
|
||||||
|
.settingsTab > *:last-child {
|
||||||
|
margin-bottom: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Tab header shared styles. */
|
/* Tab header shared styles. */
|
||||||
.settingsTabHeader {
|
.settingsTabHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1364,7 +1369,8 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
* * */
|
* * */
|
||||||
|
|
||||||
#settingsReqModsContent,
|
#settingsReqModsContent,
|
||||||
#settingsOptModsContent {
|
#settingsOptModsContent,
|
||||||
|
#settingsDropinModsContent {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: rgba(0, 0, 0, 0.25);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -1382,11 +1388,13 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#settingsReqModsContainer,
|
#settingsReqModsContainer,
|
||||||
#settingsOptModsContainer {
|
#settingsOptModsContainer,
|
||||||
|
#settingsDropinModsContainer {
|
||||||
padding-bottom: 25px;
|
padding-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsMod {
|
.settingsMod,
|
||||||
|
.settingsDropinMod {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1432,13 +1440,11 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsMod[enabled] > .settingsModContent > .settingsModMainWrapper > .settingsModStatus,
|
.settingsBaseMod[enabled] > .settingsModContent > .settingsModMainWrapper > .settingsModStatus {
|
||||||
.settingsSubMod[enabled] > .settingsModContent > .settingsModMainWrapper > .settingsModStatus {
|
|
||||||
background-color: rgb(165, 195, 37);
|
background-color: rgb(165, 195, 37);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settingsMod:not([enabled]) > .settingsSubModContainer .settingsModContent,
|
.settingsBaseMod:not([enabled]) > .settingsSubModContainer .settingsModContent {
|
||||||
.settingsSubMod:not([enabled]) > .settingsSubModContainer .settingsModContent {
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1508,6 +1514,50 @@ settingsSubModContainer > .settingsSubMod:only-child {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsDropinRemoveButton {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px;
|
||||||
|
color: #c32625;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
transition: 0.25s ease;
|
||||||
|
}
|
||||||
|
.settingsDropinRemoveButton:hover,
|
||||||
|
.settingsDropinRemoveButton:focus {
|
||||||
|
text-shadow: 0px 0px 20px #c32625, 0px 0px 20px #c32625, 0px 0px 20px #c32625;
|
||||||
|
}
|
||||||
|
.settingsDropinRemoveButton:active {
|
||||||
|
color: #9b1f1f;
|
||||||
|
text-shadow: 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsDropinFileSystemButton {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
border: 1px solid rgba(126, 126, 126, 0.57);
|
||||||
|
border-radius: 3px;
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
transition: 0.25s ease;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
#settingsDropinFileSystemButton:hover,
|
||||||
|
#settingsDropinFileSystemButton:focus {
|
||||||
|
background: rgba(54, 54, 54, 0.25);
|
||||||
|
text-shadow: 0px 0px 20px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settingsDropinRefreshNote {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
/* * *
|
/* * *
|
||||||
* Settings View (Java Tab)
|
* Settings View (Java Tab)
|
||||||
* * */
|
* * */
|
||||||
|
109
app/assets/js/dropinmodutil.js
Normal file
109
app/assets/js/dropinmodutil.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const { shell } = require('electron')
|
||||||
|
|
||||||
|
// Group #1: File Name (without .disabled, if any)
|
||||||
|
// Group #2: File Extension (jar, zip, or litemod)
|
||||||
|
// Group #3: If it is disabled (if string 'disabled' is present)
|
||||||
|
const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/
|
||||||
|
const DISABLED_EXT = '.disabled'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for drop-in mods in both the mods folder and version
|
||||||
|
* safe mods folder.
|
||||||
|
*
|
||||||
|
* @param {string} modsDir The path to the mods directory.
|
||||||
|
* @param {string} version The minecraft version of the server configuration.
|
||||||
|
*
|
||||||
|
* @returns {{fullName: string, name: string, ext: string, disabled: boolean}[]}
|
||||||
|
* An array of objects storing metadata about each discovered mod.
|
||||||
|
*/
|
||||||
|
exports.scanForDropinMods = function(modsDir, version) {
|
||||||
|
const modsDiscovered = []
|
||||||
|
if(fs.existsSync(modsDir)){
|
||||||
|
let modCandidates = fs.readdirSync(modsDir)
|
||||||
|
let verCandidates = []
|
||||||
|
const versionDir = path.join(modsDir, version)
|
||||||
|
if(fs.existsSync(versionDir)){
|
||||||
|
verCandidates = fs.readdirSync(versionDir)
|
||||||
|
}
|
||||||
|
for(file of modCandidates){
|
||||||
|
const match = MOD_REGEX.exec(file)
|
||||||
|
if(match != null){
|
||||||
|
modsDiscovered.push({
|
||||||
|
fullName: match[0],
|
||||||
|
name: match[1],
|
||||||
|
ext: match[2],
|
||||||
|
disabled: match[3] != null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(file of verCandidates){
|
||||||
|
const match = MOD_REGEX.exec(file)
|
||||||
|
if(match != null){
|
||||||
|
modsDiscovered.push({
|
||||||
|
fullName: path.join(version, match[0]),
|
||||||
|
name: match[1],
|
||||||
|
ext: match[2],
|
||||||
|
disabled: match[3] != null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modsDiscovered
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a drop-in mod from the file system.
|
||||||
|
*
|
||||||
|
* @param {string} modsDir The path to the mods directory.
|
||||||
|
* @param {string} fullName The fullName of the discovered mod to delete.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if the mod was deleted, otherwise false.
|
||||||
|
*/
|
||||||
|
exports.deleteDropinMod = function(modsDir, fullName){
|
||||||
|
/*return new Promise((resolve, reject) => {
|
||||||
|
fs.unlink(path.join(modsDir, fullName), (err) => {
|
||||||
|
if(err){
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})*/
|
||||||
|
const res = shell.moveItemToTrash(path.join(modsDir, fullName))
|
||||||
|
if(!res){
|
||||||
|
shell.beep()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle a discovered mod on or off. This is achieved by either
|
||||||
|
* adding or disabling the .disabled extension to the local file.
|
||||||
|
*
|
||||||
|
* @param {string} modsDir The path to the mods directory.
|
||||||
|
* @param {string} fullName The fullName of the discovered mod to toggle.
|
||||||
|
* @param {boolean} enable Whether to toggle on or off the mod.
|
||||||
|
*
|
||||||
|
* @returns {Promise.<void>} A promise which resolves when the mod has
|
||||||
|
* been toggled. If an IO error occurs the promise will be rejected.
|
||||||
|
*/
|
||||||
|
exports.toggleDropinMod = function(modsDir, fullName, enable){
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const oldPath = path.join(modsDir, fullName)
|
||||||
|
const newPath = path.join(modsDir, enable ? fullName.substring(0, fullName.indexOf(DISABLED_EXT)) : fullName + DISABLED_EXT)
|
||||||
|
|
||||||
|
fs.rename(oldPath, newPath, (err) => {
|
||||||
|
if(err){
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.isDropinModEnabled = function(fullName){
|
||||||
|
return !fullName.endsWith(DISABLED_EXT)
|
||||||
|
}
|
@ -4,6 +4,15 @@
|
|||||||
|
|
||||||
/* Overlay Wrapper Functions */
|
/* Overlay Wrapper Functions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the overlay is visible.
|
||||||
|
*
|
||||||
|
* @returns {boolean} Whether or not the overlay is visible.
|
||||||
|
*/
|
||||||
|
function isOverlayVisible(){
|
||||||
|
return document.getElementById('main').hasAttribute('overlay');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the visibility of the overlay.
|
* Toggle the visibility of the overlay.
|
||||||
*
|
*
|
||||||
|
@ -3,6 +3,7 @@ const os = require('os')
|
|||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
|
|
||||||
const { AssetGuard } = require('./assets/js/assetguard')
|
const { AssetGuard } = require('./assets/js/assetguard')
|
||||||
|
const DropinModUtil = require('./assets/js/dropinmodutil')
|
||||||
|
|
||||||
const settingsState = {
|
const settingsState = {
|
||||||
invalid: new Set()
|
invalid: new Set()
|
||||||
@ -233,6 +234,7 @@ settingsNavDone.onclick = () => {
|
|||||||
saveSettingsValues()
|
saveSettingsValues()
|
||||||
saveModConfiguration()
|
saveModConfiguration()
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
|
saveDropinModConfiguration()
|
||||||
switchView(getCurrentView(), VIEWS.landing)
|
switchView(getCurrentView(), VIEWS.landing)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,7 +452,7 @@ function parseModulesForUI(mdls, submodules, servConf){
|
|||||||
|
|
||||||
if(mdl.getRequired().isRequired()){
|
if(mdl.getRequired().isRequired()){
|
||||||
|
|
||||||
reqMods += `<div id="${mdl.getVersionlessID()}" class="settings${submodules ? 'Sub' : ''}Mod" enabled>
|
reqMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled>
|
||||||
<div class="settingsModContent">
|
<div class="settingsModContent">
|
||||||
<div class="settingsModMainWrapper">
|
<div class="settingsModMainWrapper">
|
||||||
<div class="settingsModStatus"></div>
|
<div class="settingsModStatus"></div>
|
||||||
@ -474,7 +476,7 @@ function parseModulesForUI(mdls, submodules, servConf){
|
|||||||
const conf = servConf[mdl.getVersionlessID()]
|
const conf = servConf[mdl.getVersionlessID()]
|
||||||
const val = typeof conf === 'object' ? conf.value : conf
|
const val = typeof conf === 'object' ? conf.value : conf
|
||||||
|
|
||||||
optMods += `<div id="${mdl.getVersionlessID()}" class="settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}>
|
optMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}>
|
||||||
<div class="settingsModContent">
|
<div class="settingsModContent">
|
||||||
<div class="settingsModMainWrapper">
|
<div class="settingsModMainWrapper">
|
||||||
<div class="settingsModStatus"></div>
|
<div class="settingsModStatus"></div>
|
||||||
@ -542,6 +544,7 @@ function saveModConfiguration(){
|
|||||||
function _saveModConfiguration(modConf){
|
function _saveModConfiguration(modConf){
|
||||||
for(m of Object.entries(modConf)){
|
for(m of Object.entries(modConf)){
|
||||||
const tSwitch = settingsModsContainer.querySelectorAll(`[formod='${m[0]}']`)
|
const tSwitch = settingsModsContainer.querySelectorAll(`[formod='${m[0]}']`)
|
||||||
|
if(!tSwitch[0].hasAttribute('dropin')){
|
||||||
if(typeof m[1] === 'boolean'){
|
if(typeof m[1] === 'boolean'){
|
||||||
modConf[m[0]] = tSwitch[0].checked
|
modConf[m[0]] = tSwitch[0].checked
|
||||||
} else {
|
} else {
|
||||||
@ -553,11 +556,11 @@ function _saveModConfiguration(modConf){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return modConf
|
return modConf
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSelectedServerOnModsTab(){
|
function loadSelectedServerOnModsTab(){
|
||||||
|
|
||||||
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
|
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer())
|
||||||
|
|
||||||
document.getElementById('settingsSelServContent').innerHTML = `
|
document.getElementById('settingsSelServContent').innerHTML = `
|
||||||
@ -583,13 +586,110 @@ function loadSelectedServerOnModsTab(){
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("settingsSwitchServerButton").addEventListener('click', (e) => {
|
let CACHE_SETTINGS_MODS_DIR
|
||||||
|
let CACHE_DROPIN_MODS
|
||||||
|
|
||||||
|
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"></div>
|
||||||
|
<div class="settingsModDetails">
|
||||||
|
<span class="settingsModName">${dropin.name}</span>
|
||||||
|
<div class="settingsDropinRemoveWrapper">
|
||||||
|
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">Remove</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="toggleSwitch">
|
||||||
|
<input type="checkbox" formod="${dropin.fullName}" dropin ${!dropin.disabled ? 'checked' : ''}>
|
||||||
|
<span class="toggleSwitchSlider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('settingsDropinModsContent').innerHTML = dropinMods
|
||||||
|
}
|
||||||
|
|
||||||
|
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<br>Drop-in Mod ${fullName}`,
|
||||||
|
'Make sure the file is not in use and try again.',
|
||||||
|
'Okay'
|
||||||
|
)
|
||||||
|
setOverlayHandler(null)
|
||||||
|
toggleOverlay(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindDropinModFileSystemButton(){
|
||||||
|
const fsBtn = document.getElementById('settingsDropinFileSystemButton')
|
||||||
|
fsBtn.onclick = () => {
|
||||||
|
shell.openItem(CACHE_SETTINGS_MODS_DIR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('settingsSwitchServerButton').addEventListener('click', (e) => {
|
||||||
e.target.blur()
|
e.target.blur()
|
||||||
toggleServerSelection(true)
|
toggleServerSelection(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){
|
||||||
|
if(e.key === 'F5'){
|
||||||
|
resolveDropinModsForUI()
|
||||||
|
bindDropinModsRemoveButton()
|
||||||
|
bindDropinModFileSystemButton()
|
||||||
|
bindModsToggleSwitch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function animateModsTabRefresh(){
|
function animateModsTabRefresh(){
|
||||||
$('#settingsTabMods').fadeOut(500, () => {
|
$('#settingsTabMods').fadeOut(500, () => {
|
||||||
|
saveModConfiguration()
|
||||||
|
ConfigManager.save()
|
||||||
|
saveDropinModConfiguration()
|
||||||
prepareModsTab()
|
prepareModsTab()
|
||||||
$('#settingsTabMods').fadeIn(500)
|
$('#settingsTabMods').fadeIn(500)
|
||||||
})
|
})
|
||||||
@ -600,6 +700,9 @@ function animateModsTabRefresh(){
|
|||||||
*/
|
*/
|
||||||
function prepareModsTab(first){
|
function prepareModsTab(first){
|
||||||
resolveModsForUI()
|
resolveModsForUI()
|
||||||
|
resolveDropinModsForUI()
|
||||||
|
bindDropinModsRemoveButton()
|
||||||
|
bindDropinModFileSystemButton()
|
||||||
bindModsToggleSwitch()
|
bindModsToggleSwitch()
|
||||||
loadSelectedServerOnModsTab()
|
loadSelectedServerOnModsTab()
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,13 @@
|
|||||||
<div class="settingsModsHeader">Optional Mods</div>
|
<div class="settingsModsHeader">Optional Mods</div>
|
||||||
<div id="settingsOptModsContent">
|
<div id="settingsOptModsContent">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="settingsDropinModsContainer">
|
||||||
|
<div class="settingsModsHeader">Drop-in Mods</div>
|
||||||
|
<button id="settingsDropinFileSystemButton">+ Add Mods <span id="settingsDropinRefreshNote">(F5 to Refresh)</span></button>
|
||||||
|
<div id="settingsDropinModsContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user