diff --git a/app/assets/css/launcher.css b/app/assets/css/launcher.css index 203159d5..56a06360 100644 --- a/app/assets/css/launcher.css +++ b/app/assets/css/launcher.css @@ -1578,6 +1578,77 @@ input:checked + .toggleSwitchSlider:before { text-shadow: 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f, 0px 0px 20px #9b1f1f; } +/* Main select container. */ +.settingsSelectContainer { + position: relative; + width: 50%; +} + +/* Div which displays the selected option. */ +.settingsSelectSelected { + border-radius: 3px; + border-width: 1px; + font-size: 14px; + padding: 6px 16px; +} + +/* Style the arrow inside the select element. */ +.settingsSelectSelected:after { + position: absolute; + content: ""; + top: calc(50% - 3px); + right: 10px; + width: 0; + height: 0; + border: 6px solid transparent; + border-color: rgba(126, 126, 126, 0.57) transparent transparent transparent; +} + +/* Point the arrow upwards when the select box is open (active). */ +.settingsSelectSelected.select-arrow-active:after { + border-color: transparent transparent rgba(126, 126, 126, 0.57) transparent; + top: 7px; +} +.settingsSelectSelected.select-arrow-active { + border-radius: 3px 3px 0px 0px; +} + +/* Options content container. */ +.settingsSelectOptions { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 99; +} +/* Hide the items when the select box is closed. */ +.settingsSelectOptions[hidden] { + display: none; +} + +/* Shared styles between options and selection div. */ +.settingsSelectOptions div, +.settingsSelectSelected { + background: rgba(0, 0, 0, 0.25); + border-style: solid; + border-color: rgba(126, 126, 126, 0.57); + color: #ffffff; + cursor: pointer; +} +.settingsSelectOptions div { + border-width: 0px 1px 1px 1px; + font-size: 12px; + padding: 4px 16px; +} +.settingsSelectOptions .settingsSelectOption:last-child { + border-radius: 0px 0px 3px 3px; +} + +/* Hover + selected styles. */ +.settingsSelectOptions div:hover, .settingsSelectOptions div[selected] { + background-color: rgba(255, 255, 255, 0.25) !important; +} + /* * * * Settings View (Java Tab) * * */ diff --git a/app/assets/js/dropinmodutil.js b/app/assets/js/dropinmodutil.js index eef42a98..9d4cbaf5 100644 --- a/app/assets/js/dropinmodutil.js +++ b/app/assets/js/dropinmodutil.js @@ -8,14 +8,17 @@ const { shell } = require('electron') const MOD_REGEX = /^(.+(jar|zip|litemod))(?:\.(disabled))?$/ const DISABLED_EXT = '.disabled' +const SHADER_REGEX = /^(.+)\.zip$/ +const SHADER_OPTION = /shaderPack=(.+)/ + /** - * Validate that the given mods directory exists. If not, - * it is created. + * Validate that the given directory exists. If not, it is + * created. * * @param {string} modsDir The path to the mods directory. */ -exports.validateModsDir = function(modsDir) { - fs.ensureDirSync(modsDir) +exports.validateDir = function(dir) { + fs.ensureDirSync(dir) } /** @@ -71,7 +74,7 @@ exports.scanForDropinMods = function(modsDir, version) { */ exports.addDropinMods = function(files, modsdir) { - exports.validateModsDir(modsdir) + exports.validateDir(modsdir) for(let f of files) { if(MOD_REGEX.exec(f.name) != null) { @@ -131,4 +134,77 @@ exports.toggleDropinMod = function(modsDir, fullName, enable){ */ exports.isDropinModEnabled = function(fullName){ return !fullName.endsWith(DISABLED_EXT) -} \ No newline at end of file +} + +/** + * Scan for shaderpacks inside the shaderpacks folder. + * + * @param {string} instanceDir The path to the server instance directory. + * + * @returns {{fullName: string, name: string}[]} + * An array of objects storing metadata about each discovered shaderpack. + */ +exports.scanForShaderpacks = function(instanceDir){ + const shaderDir = path.join(instanceDir, 'shaderpacks') + const packsDiscovered = [{ + fullName: 'OFF', + name: 'No Shaderpack' + }] + if(fs.existsSync(shaderDir)){ + let modCandidates = fs.readdirSync(shaderDir) + for(let file of modCandidates){ + const match = SHADER_REGEX.exec(file) + if(match != null){ + packsDiscovered.push({ + fullName: match[0], + name: match[1] + }) + } + } + } + return packsDiscovered +} + +/** + * Read the optionsshaders.txt file to locate the current + * enabled pack. If the file does not exist, OFF is returned. + * + * @param {string} instanceDir The path to the server instance directory. + * + * @returns {string} The file name of the enabled shaderpack. + */ +exports.getEnabledShaderpack = function(instanceDir){ + exports.validateDir(instanceDir) + + const optionsShaders = path.join(instanceDir, 'optionsshaders.txt') + if(fs.existsSync(optionsShaders)){ + const buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) + const match = SHADER_OPTION.exec(buf) + if(match != null){ + return match[1] + } else { + console.warn('WARNING: Shaderpack regex failed.') + } + } + return 'OFF' +} + +/** + * Set the enabled shaderpack. + * + * @param {string} instanceDir The path to the server instance directory. + * @param {string} pack the file name of the shaderpack. + */ +exports.setEnabledShaderpack = function(instanceDir, pack){ + exports.validateDir(instanceDir) + + const optionsShaders = path.join(instanceDir, 'optionsshaders.txt') + let buf + if(fs.existsSync(optionsShaders)){ + buf = fs.readFileSync(optionsShaders, {encoding: 'utf-8'}) + buf = buf.replace(SHADER_OPTION, `shaderPack=${pack}`) + } else { + buf = `shaderPack=${pack}` + } + fs.writeFileSync(optionsShaders, buf, {encoding: 'utf-8'}) +} diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 651ab4f2..f3694e6a 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -9,6 +9,38 @@ const settingsState = { invalid: new Set() } +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', '') + } + } +} + +/* If the user clicks anywhere outside the select box, +then close all select boxes: */ +document.addEventListener('click', closeSettingsSelect) + +bindSettingsSelect() + + /** * General Settings Functions */ @@ -640,7 +672,7 @@ function bindDropinModsRemoveButton(){ function bindDropinModFileSystemButton(){ const fsBtn = document.getElementById('settingsDropinFileSystemButton') fsBtn.onclick = () => { - DropinModUtil.validateModsDir(CACHE_SETTINGS_MODS_DIR) + DropinModUtil.validateDir(CACHE_SETTINGS_MODS_DIR) shell.openItem(CACHE_SETTINGS_MODS_DIR) } fsBtn.ondragenter = e => { @@ -707,6 +739,47 @@ function reloadDropinMods(){ 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) + } +} + // Server status bar functions. /** @@ -770,6 +843,7 @@ function animateModsTabRefresh(){ function prepareModsTab(first){ resolveModsForUI() resolveDropinModsForUI() + resolveShaderpacksForUI() bindDropinModsRemoveButton() bindDropinModFileSystemButton() bindModsToggleSwitch() diff --git a/app/settings.ejs b/app/settings.ejs index e14ec2a2..e5fd6d19 100644 --- a/app/settings.ejs +++ b/app/settings.ejs @@ -126,9 +126,15 @@ - +