diff options
Diffstat (limited to 'src/app/js')
-rw-r--r-- | src/app/js/browser.js | 40 | ||||
-rw-r--r-- | src/app/js/dom_events.js | 42 | ||||
-rw-r--r-- | src/app/js/events.js | 2 | ||||
-rw-r--r-- | src/app/js/gamepath.js | 51 | ||||
-rw-r--r-- | src/app/js/is_running.js | 46 | ||||
-rw-r--r-- | src/app/js/kill.js | 8 | ||||
-rw-r--r-- | src/app/js/launch.js | 15 | ||||
-rw-r--r-- | src/app/js/launcher.js | 9 | ||||
-rw-r--r-- | src/app/js/mods.js | 95 | ||||
-rw-r--r-- | src/app/js/popups.js | 6 | ||||
-rw-r--r-- | src/app/js/process.js | 27 | ||||
-rw-r--r-- | src/app/js/request.js | 18 | ||||
-rw-r--r-- | src/app/js/set_buttons.js | 40 | ||||
-rw-r--r-- | src/app/js/set_dom_strings.js | 19 | ||||
-rw-r--r-- | src/app/js/settings.js | 529 | ||||
-rw-r--r-- | src/app/js/update.js | 145 |
16 files changed, 868 insertions, 224 deletions
diff --git a/src/app/js/browser.js b/src/app/js/browser.js index 06dc244..00a0933 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -98,7 +98,7 @@ var Browser = { } }, install: (package_obj, clear_queue = false) => { - return installFromURL( + return mods.install_from_url( package_obj.download || package_obj.versions[0].download_url, package_obj.dependencies || package_obj.versions[0].dependencies, clear_queue, @@ -119,9 +119,9 @@ var Browser = { let remote_version = packages[i].versions[0].version_number; remote_version = version.format(remote_version); - if (modsobj) { - for (let ii = 0; ii < modsobj.all.length; ii++) { - let mod = modsobj.all[ii]; + if (mods.list()) { + for (let ii = 0; ii < mods.list().all.length; ii++) { + let mod = mods.list().all[ii]; if (normalize(mod.name) !== normalized && ( ! mod.package || @@ -288,14 +288,14 @@ var Browser = { } setTimeout(() => { - for (let i = 0; i < modsobj.all.length; i++) { - let modname = normalize(modsobj.all[i].name); - let modfolder = normalize(modsobj.all[i].folder_name); + for (let i = 0; i < mods.list().all.length; i++) { + let modname = normalize(mods.list().all[i].name); + let modfolder = normalize(mods.list().all[i].folder_name); if (mod.includes(modname)) { if (! make(modname)) { - if (modsobj.all[i].manifest_name) { - make(normalize(modsobj.all[i].manifest_name)); + if (mods.list().all[i].manifest_name) { + make(normalize(mods.list().all[i].manifest_name)); } } } @@ -405,7 +405,7 @@ function BrowserEl(properties) { let installstr = lang("gui.browser.install"); let normalized_mods = []; - for (let i = 0; i < modsobj.all; i++) { + for (let i = 0; i < mods.list().all; i++) { normalized_mods.push(normalize(mods_list[i].name)); } @@ -467,7 +467,7 @@ function add_recent_toast(name, timeout = 3000) { } ipcRenderer.on("removed-mod", (event, mod) => { - setButtons(true); + set_buttons(true); Browser.setbutton(mod.name, lang("gui.browser.install"), "downloads"); if (mod.manifest_name) { @@ -479,7 +479,7 @@ ipcRenderer.on("failed-mod", (event, modname) => { if (recent_toasts["failed" + modname]) {return} add_recent_toast("failed" + modname); - setButtons(true); + set_buttons(true); new Toast({ timeout: 10000, scheme: "error", @@ -492,7 +492,7 @@ ipcRenderer.on("legacy-duped-mod", (event, modname) => { if (recent_toasts["duped" + modname]) {return} add_recent_toast("duped" + modname); - setButtons(true); + set_buttons(true); new Toast({ timeout: 10000, scheme: "warning", @@ -502,7 +502,7 @@ ipcRenderer.on("legacy-duped-mod", (event, modname) => { }) ipcRenderer.on("no-internet", (event, modname) => { - setButtons(true); + set_buttons(true); new Toast({ timeout: 10000, scheme: "error", @@ -517,7 +517,7 @@ ipcRenderer.on("installed-mod", (event, mod) => { let name = mod.fancy_name || mod.name; - setButtons(true); + set_buttons(true); Browser.setbutton(name, lang("gui.browser.reinstall"), "redo"); if (mod.malformed) { @@ -535,13 +535,13 @@ ipcRenderer.on("installed-mod", (event, mod) => { description: name + " " + lang("gui.toast.desc.installed") }) - if (installqueue.length != 0) { - installFromURL( - "https://thunderstore.io/package/download/" + installqueue[0].pkg, - false, false, installqueue[0].author, installqueue[0].package_name, installqueue[0].version + if (mods.install_queue.length != 0) { + mods.install_from_url( + "https://thunderstore.io/package/download/" + mods.install_queue[0].pkg, + false, false, mods.install_queue[0].author, mods.install_queue[0].package_name, mods.install_queue[0].version ) - installqueue.shift(); + mods.install_queue.shift(); } }) diff --git a/src/app/js/dom_events.js b/src/app/js/dom_events.js new file mode 100644 index 0000000..73e9998 --- /dev/null +++ b/src/app/js/dom_events.js @@ -0,0 +1,42 @@ +const settings = require("./settings"); + +let drag_timer; +document.addEventListener("dragover", (e) => { + e.preventDefault(); + e.stopPropagation(); + dragUI.classList.add("shown"); + + clearTimeout(drag_timer); + drag_timer = setTimeout(() => { + dragUI.classList.remove("shown"); + }, 5000) +}) + +document.addEventListener("mouseover", () => { + clearTimeout(drag_timer); + dragUI.classList.remove("shown"); +}) + +document.addEventListener("drop", (e) => { + e.preventDefault(); + e.stopPropagation(); + + dragUI.classList.remove("shown"); + mods.install_from_path(e.dataTransfer.files[0].path); +}) + +document.body.addEventListener("keyup", (e) => { + if (e.key == "Escape") { + Browser.toggle(false); + settings.popup.toggle(false); + } +}) + +document.body.addEventListener("click", (e) => { + if (e.target.tagName.toLowerCase() === "a" + && e.target.protocol != "file:") { + + e.preventDefault(); + shell.openExternal(e.target.href); + } +}) diff --git a/src/app/js/events.js b/src/app/js/events.js index cced4a7..040382e 100644 --- a/src/app/js/events.js +++ b/src/app/js/events.js @@ -1,3 +1,5 @@ const EventEmitter = require("events"); class Emitter extends EventEmitter {}; const events = new Emitter(); + +module.exports = events; diff --git a/src/app/js/gamepath.js b/src/app/js/gamepath.js new file mode 100644 index 0000000..80e3158 --- /dev/null +++ b/src/app/js/gamepath.js @@ -0,0 +1,51 @@ +const ipcRenderer = require("electron").ipcRenderer; + +const lang = require("../../lang"); +const process = require("./process"); +const settings = require("./settings"); + +// frontend part of settings a new game path +ipcRenderer.on("newpath", (_, newpath) => { + set_buttons(true); + + settings.set({gamepath: newpath}); + + ipcRenderer.send("gui-getmods"); + ipcRenderer.send("save-settings", settings.data()); +}) + +// a previously valid gamepath no longer exists, and is therefore lost +ipcRenderer.on("gamepath-lost", () => { + page(0); + set_buttons(false, true); + alert(lang("gui.gamepath.lost")); +}) + +// error out when no game path is set +ipcRenderer.on("no-path-selected", () => { + alert(lang("gui.gamepath.must")); + process.exit(); +}) + +// error out when game path is wrong +ipcRenderer.on("wrong-path", () => { + alert(lang("gui.gamepath.wrong")); + gamepath.set(false); +}) + +// reports to the main process about game path status. +module.exports = { + open: () => { + let gamepath = settings.data().gamepath; + + if (gamepath) { + require("electron").shell.openPath(gamepath); + } else { + alert(lang("gui.settings.miscbuttons.open_gamepath_alert")); + } + }, + + set: (value) => { + ipcRenderer.send("setpath", value); + } +} diff --git a/src/app/js/is_running.js b/src/app/js/is_running.js new file mode 100644 index 0000000..69efd5b --- /dev/null +++ b/src/app/js/is_running.js @@ -0,0 +1,46 @@ +const lang = require("../../lang"); + +// is the game running? +let is_running = false; + +// updates play buttons depending on whether the game is running +ipcRenderer.on("is-running", (event, running) => { + let set_playbtns = (text) => { + let playbtns = document.querySelectorAll(".playBtn"); + for (let i = 0; i < playbtns.length; i++) { + playbtns[i].innerHTML = text; + } + } + + if (running && is_running != running) { + set_buttons(false); + set_playbtns(lang("general.running")); + + is_running = running; + + // show force quit button in Titanfall tab + tfquit.style.display = "inline-block"; + + update.setAttribute("onclick", "kill('game')"); + update.innerHTML = "(" + lang("ns.menu.force_quit") + ")"; + return; + } + + if (is_running != running) { + set_buttons(true); + set_playbtns(lang("gui.launch")); + + is_running = running; + + // hide force quit button in Titanfall tab + tfquit.style.display = "none"; + + update.setAttribute("onclick", "update.ns()"); + update.innerHTML = "(" + lang("gui.update.check") + ")"; + } +}) + +// return whether the game is running +module.exports = () => { + return is_running; +} diff --git a/src/app/js/kill.js b/src/app/js/kill.js new file mode 100644 index 0000000..04f0a84 --- /dev/null +++ b/src/app/js/kill.js @@ -0,0 +1,8 @@ +const ipcRenderer = require("electron").ipcRenderer; + +// attempts to kill something using the main process' `modules/kill.js` +// functions, it simply attempts to run `kill[function_name]()`, if it +// doesn't exist, nothing happens +module.exports = (function_name) => { + ipcRenderer.send("kill", function_name); +} diff --git a/src/app/js/launch.js b/src/app/js/launch.js new file mode 100644 index 0000000..d14d2ee --- /dev/null +++ b/src/app/js/launch.js @@ -0,0 +1,15 @@ +const update = require("./update"); + +// tells the main process to launch `game_version` +module.exports = (game_version) => { + if (game_version == "vanilla") { + ipcRenderer.send("launch", game_version); + return; + } + + if (update.ns.should_install) { + update.ns(); + } else { + ipcRenderer.send("launch", game_version); + } +} diff --git a/src/app/js/launcher.js b/src/app/js/launcher.js index 1c383b4..6fe1686 100644 --- a/src/app/js/launcher.js +++ b/src/app/js/launcher.js @@ -4,8 +4,7 @@ var servercount; var playercount; var masterserver; -// Changes the main page -// This is the tabs in the sidebar +// changes the main page, this is the tabs in the sidebar function page(page) { let btns = document.querySelectorAll(".gamesContainer button"); let pages = document.querySelectorAll(".mainContainer .contentContainer"); @@ -71,7 +70,7 @@ let set_error_content = (div, lang_key) => { "</div>"; } -// Updates the Viper release notes +// updates the Viper release notes ipcRenderer.on("vp-notes", (event, response) => { if (! response) { return set_error_content( @@ -83,7 +82,7 @@ ipcRenderer.on("vp-notes", (event, response) => { vpReleaseNotes.innerHTML = formatRelease(response); }); -// Updates the Northstar release notes +// updates the Northstar release notes ipcRenderer.on("ns-notes", (event, response) => { if (! response) { return set_error_content( @@ -208,7 +207,7 @@ async function loadServers() { } }; loadServers() -// Refreshes every 5 minutes +// refreshes every 5 minutes setInterval(() => { loadServers(); }, 300000) diff --git a/src/app/js/mods.js b/src/app/js/mods.js index 182bddf..e105c7c 100644 --- a/src/app/js/mods.js +++ b/src/app/js/mods.js @@ -1,4 +1,15 @@ -var mods = {}; +let mods = {}; + +let mods_list = { + all: [], + enabled: [], + disabled: [] +} + +// returns the list of mods +mods.list = () => { + return mods_list; +} mods.load = (mods_obj) => { modcount.innerHTML = `${lang("gui.mods.count")} ${mods_obj.all.length}`; @@ -210,3 +221,85 @@ mods.toggle = (mod) => { ipcRenderer.send("toggle-mod", mod); } + +mods.install_queue = []; + +// tells the main process to install a mod through the file selector +mods.install_prompt = () => { + set_buttons(false); + ipcRenderer.send("install-mod"); +} + +// tells the main process to directly install a mod from this path +mods.install_from_path = (path) => { + set_buttons(false); + ipcRenderer.send("install-from-path", path); +} + +// tells the main process to install a mod from a URL +mods.install_from_url = (url, dependencies, clearqueue, author, package_name, version) => { + if (clearqueue) {mods.install_queue = []}; + + let prettydepends = []; + + if (dependencies) { + let newdepends = []; + for (let i = 0; i < dependencies.length; i++) { + let depend = dependencies[i].toLowerCase(); + if (! depend.match(/northstar-northstar-.*/)) { + depend = dependencies[i].replaceAll("-", "/"); + let pkg = depend.split("/"); + if (! mods.is_installed(pkg[1])) { + newdepends.push({ + pkg: depend, + author: pkg[0], + version: pkg[2], + package_name: pkg[1] + }); + + prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.made_by")} ${pkg[0]}`); + } + } + } + + dependencies = newdepends; + } + + if (dependencies && dependencies.length != 0) { + let confirminstall = confirm(lang("gui.mods.confirm_dependencies") + prettydepends.join("\n")); + if (! confirminstall) { + return; + } + } + + set_buttons(false); + ipcRenderer.send("install-from-url", url, author, package_name, version); + + if (dependencies) { + mods.install_queue = dependencies; + } +} + +mods.is_installed = (modname) => { + for (let i = 0; i < mods.list().all.length; i++) { + let mod = mods.list().all[i]; + if (mod.manifest_name) { + if (mod.manifest_name.match(modname)) { + return true; + } + } else if (mod.name.match(modname)) { + return true; + } + } + + return false; +} + + +// updates the installed mods +ipcRenderer.on("mods", (event, mods_obj) => { + mods_list = mods_obj; + if (! mods_obj) {return} + + mods.load(mods_obj); +}) diff --git a/src/app/js/popups.js b/src/app/js/popups.js index 7411180..10c6995 100644 --- a/src/app/js/popups.js +++ b/src/app/js/popups.js @@ -43,7 +43,7 @@ popups.list = () => { return document.querySelectorAll(".popup"); } -popups.set_all = (state, exclude_popup) => { +popups.set_all = (state = false, exclude_popup) => { let popups_list = document.querySelectorAll(".popup.shown"); for (let i = 0; i < popups_list.length; i++) { @@ -51,6 +51,8 @@ popups.set_all = (state, exclude_popup) => { continue; } - popups.set(popups_list[i], false, false); + popups.set(popups_list[i], state, false); } } + +module.exports = popups; diff --git a/src/app/js/process.js b/src/app/js/process.js new file mode 100644 index 0000000..a4aba6c --- /dev/null +++ b/src/app/js/process.js @@ -0,0 +1,27 @@ +const ipcRenderer = require("electron").ipcRenderer; + +ipcRenderer.on("log", (_, msg) => { + console.log(msg) +}) + +ipcRenderer.on("alert", (_, data) => { + alert(data.message); + ipcRenderer.send("alert-closed-" + data.id); +}) + +ipcRenderer.on("confirm", (_, data) => { + let confirmed = confirm(data.message); + ipcRenderer.send("confirm-closed-" + data.id, confirmed); +}) + +module.exports = { + // attempts to relaunch the process + relaunch: () => { + ipcRenderer.send("relaunch"); + }, + + // attempts to exit the process (closing Viper) + exit: () => { + ipcRenderer.send("exit") + } +} diff --git a/src/app/js/request.js b/src/app/js/request.js new file mode 100644 index 0000000..29b8883 --- /dev/null +++ b/src/app/js/request.js @@ -0,0 +1,18 @@ +const ipcRenderer = require("electron").ipcRenderer; + +// show a toast message if no Internet connection has been detected. +if (! navigator.onLine) { + ipcRenderer.send("no-internet"); +} + +// invokes `requests.get()` from `src/modules/requests.js` through the +// main process, and returns the output +let request = async (...args) => { + return await ipcRenderer.invoke("request", ...args); +} + +request.delete_cache = () => { + ipcRenderer.send("delete-request-cache"); +} + +module.exports = request; diff --git a/src/app/js/set_buttons.js b/src/app/js/set_buttons.js new file mode 100644 index 0000000..9cb9d3e --- /dev/null +++ b/src/app/js/set_buttons.js @@ -0,0 +1,40 @@ +const ipcRenderer = require("electron").ipcRenderer; + +ipcRenderer.on("set-buttons", (_, state) => { + set_buttons(state); +}) + +// disables or enables certain buttons when for example +// updating/installing Northstar. +module.exports = (state, enable_gamepath_btns) => { + playNsBtn.disabled = ! state; + + let disable_array = (array) => { + for (let i = 0; i < array.length; i++) { + array[i].disabled = ! state; + + if (state) { + array[i].classList.remove("disabled") + } else { + array[i].classList.add("disabled") + } + } + } + + disable_array(document.querySelectorAll([ + "#modsdiv .el button", + ".disable-when-installing", + ".playBtnContainer .playBtn", + "#nsMods .buttons.modbtns button", + "#browser #browserEntries .text button", + ])) + + if (enable_gamepath_btns) { + let gamepath_btns = query_all('*[onclick="gamepath.set()"]'); + + for (let i = 0; i < gamepath_btns.length; i++) { + gamepath_btns[i].disabled = false; + gamepath_btns[i].classList.remove("disabled"); + } + } +} diff --git a/src/app/js/set_dom_strings.js b/src/app/js/set_dom_strings.js new file mode 100644 index 0000000..000aba4 --- /dev/null +++ b/src/app/js/set_dom_strings.js @@ -0,0 +1,19 @@ +// replaces strings in the HTML will language strings properly. This +// searches for %%<string>%%, aka, %%gui.exit%% will be replaced with +// "Exit", this works without issues. +module.exports = () => { + // finds %%%% strings + html = document.body.innerHTML.split("%%"); + + for (let i = 0; i < html.length; i++) { + // simply checks to make sure it is actually a lang string. + if (html[i][0] != " " && + html[i][html[i].length - 1] != " ") { + // Replaces it with it's string + html[i] = lang(html[i]); + } + } + + // replaces the original HTML with the translated/replaced HTML + document.body.innerHTML = html.join(""); +} diff --git a/src/app/js/settings.js b/src/app/js/settings.js index 2d68c13..93d1c15 100644 --- a/src/app/js/settings.js +++ b/src/app/js/settings.js @@ -1,243 +1,375 @@ -var settings_fuse; +const fs = require("fs"); +const Fuse = require("fuse.js"); +const ipcRenderer = require("electron").ipcRenderer; + +const lang = require("../../lang"); + +const events = require("./events"); +const popups = require("./popups"); + +let settings_fuse; + +// base settings, and future set settings data +let settings_data = { + nsargs: "", + gamepath: "", + nsupdate: true, + autolang: true, + forcedlang: "en", + autoupdate: true, + originkill: false, + zip: "/northstar.zip", + lang: navigator.language, + excludes: [ + "ns_startup_args.txt", + "ns_startup_args_dedi.txt" + ] +} -var Settings = { - toggle: (state) => { - Settings.load(); - options.scrollTo(0, 0); +// loads the settings +if (fs.existsSync("viper.json")) { + let iteration = 0; + + // loads the config file, it's loaded in an interval like this in + // case the config file is currently being written to, if we were to + // read from the file during that, then it'd be empty or similar. + // + // and because of that, we check if the file is empty when loading + // it, if so we wait 100ms, then check again, and we keep doing that + // hoping it'll become normal again. unless we've checked it 50 + // times, then we simply give up, and force the user to re-select + // the gamepath, as this'll make the config file readable again. + // + // ideally it'll never have to check those 50 times, it's only in + // case something terrible has gone awry, like if a write got + // interrupted, or a user messed with the file. + // + // were it to truly be broken, then it'd take up to 5 seconds to + // then reset, this is basically nothing, especially considering + // this should only happen in very rare cases... hopefully! + let config_interval = setInterval(() => { + let gamepath = require("./gamepath"); + + iteration++; + + config = json("viper.json") || {}; + + // checks whether `settings_data.gamepath` is set, and if so, + // it'll attempt to load ns_startup_args.txt + let parse_settings = () => { + // if gamepath is not set, attempt to set it + if (settings_data.gamepath.length === 0) { + gamepath.set(false); + } else { + // if the gamepath is set, we'll simply tell the main + // process about it, and it'll then show the main + // renderer BrowserWindow + gamepath.set(true); + } - popups.set("#options", state); - }, - apply: () => { - settings = {...settings, ...Settings.get()}; - ipcRenderer.send("save-settings", Settings.get()); - }, - get: () => { - let opts = {}; - let options = document.querySelectorAll(".option"); - - for (let i = 0; i < options.length; i++) { - let optName = options[i].getAttribute("name"); - if (options[i].querySelector(".actions input")) { - let input = options[i].querySelector(".actions input").value; - if (options[i].getAttribute("type")) { - opts[optName] = input.split(" "); - } else { - opts[optName] = input; - } - } else if (options[i].querySelector(".actions select")) { - opts[optName] = options[i].querySelector(".actions select").value; - } else if (options[i].querySelector(".actions .switch")) { - if (options[i].querySelector(".actions .switch.on")) { - opts[optName] = true; - } else { - opts[optName] = false; - } + // filepath to Northstar's startup args file + let args = path.join(settings_data.gamepath, "ns_startup_args.txt"); + + // check file exists, and that no `nsargs` setting was set + if (! settings_data.nsargs && fs.existsSync(args)) { + // load arguments from file into `settings` + settings_data.nsargs = fs.readFileSync(args, "utf8") || ""; } } - return opts; + // make sure config isn't empty + if (Object.keys(config).length !== 0) { + // add `config` to `settings` + settings.set(config); + parse_settings(); + + clearInterval(config_interval); + return; + } + + // we've attempted to load the config 50 times now, give up + if (iteration >= 50) { + // request a new gamepath be set + gamepath.set(false); + clearInterval(config_interval); + } + }, 100) +} else { + require("./gamepath").set(); +} + +ipcRenderer.on("changed-settings", (e, new_settings) => { + // attempt to set `settings_data` to `new_settings` + try { + settings.set(new_settings); + }catch(e) {} +}) + +let settings = { + data: () => {return settings_data}, + + // asks the main process to reset the config/settings file + reset: () => { + ipcRenderer.send("reset-config"); }, - load: () => { - // re-opens any closed categories - let categories = document.querySelectorAll("#options details"); - for (let i = 0; i < categories.length; i++) { - categories[i].setAttribute("open", true); + + // merges `object` with `settings_data`, unless `no_merge` is set, + // then it replaces it entirely + set: (object, no_merge) => { + if (no_merge) { + settings_data = object; + return; } - let options = document.querySelectorAll(".option"); + settings_data = { + ...settings_data, + ...object + } + }, - for (let i = 0; i < options.length; i++) { - let optName = options[i].getAttribute("name"); - if (optName == "forcedlang") { - let div = options[i].querySelector("select"); + popup: {} +} - div.innerHTML = ""; - let lang_dir = __dirname + "/../lang"; - let langs = fs.readdirSync(lang_dir); - for (let i in langs) { - let lang_file = require(lang_dir + "/" + langs[i]); - let lang_no_extension = langs[i].replace(/\..*$/, ""); +settings.popup.toggle = (state) => { + settings.popup.load(); + options.scrollTo(0, 0); - if (! lang_file.lang || ! lang_file.lang.title) { - continue; - } + popups.set("#options", state); +} - let title = lang_file.lang.title; +settings.popup.apply = () => { + settings = {...settings, ...settings.popup.get()}; + ipcRenderer.send("save-settings", settings.popup.get()); +} - if (title) { - div.innerHTML += `<option value="${lang_no_extension}">${title}</option>` - } - - } +settings.popup.get = () => { + let opts = {}; + let options = document.querySelectorAll(".option"); - div.value = settings.forcedlang; - continue; + for (let i = 0; i < options.length; i++) { + let optName = options[i].getAttribute("name"); + if (options[i].querySelector(".actions input")) { + let input = options[i].querySelector(".actions input").value; + if (options[i].getAttribute("type")) { + opts[optName] = input.split(" "); + } else { + opts[optName] = input; } + } else if (options[i].querySelector(".actions select")) { + opts[optName] = options[i].querySelector(".actions select").value; + } else if (options[i].querySelector(".actions .switch")) { + if (options[i].querySelector(".actions .switch.on")) { + opts[optName] = true; + } else { + opts[optName] = false; + } + } + } + + return opts; +} + +settings.popup.load = () => { + // re-opens any closed categories + let categories = document.querySelectorAll("#options details"); + for (let i = 0; i < categories.length; i++) { + categories[i].setAttribute("open", true); + } + + let options = document.querySelectorAll(".option"); + + for (let i = 0; i < options.length; i++) { + let optName = options[i].getAttribute("name"); + if (optName == "forcedlang") { + let div = options[i].querySelector("select"); + + div.innerHTML = ""; + let lang_dir = __dirname + "/../../lang"; + let langs = fs.readdirSync(lang_dir); + for (let i in langs) { + let lang_file = require(lang_dir + "/" + langs[i]); + let lang_no_extension = langs[i].replace(/\..*$/, ""); + + if (! lang_file.lang || ! lang_file.lang.title) { + continue; + } - if (settings[optName] != undefined) { - switch(typeof settings[optName]) { - case "string": - options[i].querySelector(".actions input").value = settings[optName]; - break - case "object": - options[i].querySelector(".actions input").value = settings[optName].join(" "); - break - case "boolean": - let switchDiv = options[i].querySelector(".actions .switch"); - if (settings[optName]) { - switchDiv.classList.add("on"); - switchDiv.classList.remove("off"); - } else { - switchDiv.classList.add("off"); - switchDiv.classList.remove("on"); - } - break + let title = lang_file.lang.title; + if (title) { + div.innerHTML += `<option value="${lang_no_extension}">${title}</option>` } + } + + div.value = settings_data.forcedlang; + continue; } - // create Fuse based on options from `get_search_arr()` - settings_fuse = new Fuse(get_search_arr(), { - keys: ["text"], - threshold: 0.4, - ignoreLocation: true - }) - - // reset search - Settings.search(); - search_el.value = ""; - - ipcRenderer.send("can-autoupdate"); - ipcRenderer.on("cant-autoupdate", () => { - document.querySelector(".option[name=autoupdate]") - .style.display = "none"; - - document.querySelector(".option[name=autoupdate]") - .setAttribute("perma-hidden", true); - }) - }, - switch: (el, state) => { + if (settings[optName] != undefined) { + switch(typeof settings[optName]) { + case "string": + options[i].querySelector(".actions input").value = settings[optName]; + break + case "object": + options[i].querySelector(".actions input").value = settings[optName].join(" "); + break + case "boolean": + let switchDiv = options[i].querySelector(".actions .switch"); + if (settings[optName]) { + switchDiv.classList.add("on"); + switchDiv.classList.remove("off"); + } else { + switchDiv.classList.add("off"); + switchDiv.classList.remove("on"); + } + break + + } + } + } + + // create Fuse based on options from `get_search_arr()` + settings_fuse = new Fuse(get_search_arr(), { + keys: ["text"], + threshold: 0.4, + ignoreLocation: true + }) + + // reset search + settings.popup.search(); + search_el.value = ""; + + ipcRenderer.send("can-autoupdate"); + ipcRenderer.on("cant-autoupdate", () => { + document.querySelector(".option[name=autoupdate]") + .style.display = "none"; + + document.querySelector(".option[name=autoupdate]") + .setAttribute("perma-hidden", true); + }) +} + +settings.popup.switch = (el, state) => { + if (state) { + return el.classList.add("on"); + } else if (state === false) { + return el.classList.remove("on"); + } + + if (el.classList.contains("switch") && el.tagName == "BUTTON") { + el.classList.toggle("on"); + } +} + +// searches for `query` in the list of options, hides the options +// that dont match, and shows the one that do, if `query` is falsy, +// it'll simply reset everything back to being visible +settings.popup.search = (query = "") => { + // get list of elements that can be hidden + let search_els = [ + ...document.querySelectorAll(".options .title"), + ...document.querySelectorAll(".options .option"), + ...document.querySelectorAll(".options .buttons"), + ...document.querySelectorAll(".options .details"), + ] + + // this sets the visibility of all elements found in + // `search_els` unless the element has the `perma-hidden` + // attribute set + let set_all = (state) => { + // set `state` to the CSS equivalent if (state) { - return el.classList.add("on"); - } else if (state === false) { - return el.classList.remove("on"); + state = null; + } else { + state = "none"; } - if (el.classList.contains("switch") && el.tagName == "BUTTON") { - el.classList.toggle("on"); + // run through elements + for (let i = 0; i < search_els.length; i++) { + // check that the element shouldn't be perma hidden, and + // if so, hide it correctly. + if (search_els[i].hasAttribute("perma-hidden")) { + search_els[i].style.display = "none"; + continue; + } + + // set the visibility + search_els[i].style.display = state; } - }, + } + + // check if `query` is empty, and reset search if so + if (! query || ! query.trim()) { + set_all(true); + } else { + // hide everything + set_all(false); + } - // searches for `query` in the list of options, hides the options - // that dont match, and shows the one that do, if `query` is falsy, - // it'll simply reset everything back to being visible - search: (query = "") => { - // get list of elements that can be hidden - let search_els = [ - ...document.querySelectorAll(".options .title"), - ...document.querySelectorAll(".options .option"), - ...document.querySelectorAll(".options .buttons"), - ...document.querySelectorAll(".options .details"), + // unhides `el` and relevant elements related to it + let unhide = (el) => { + // list of elements that could be relevant through `el`'s + // `.closest()` function + let closest = [ + ".option", + "details", + ".buttons", ] - // this sets the visibility of all elements found in - // `search_els` unless the element has the `perma-hidden` - // attribute set - let set_all = (state) => { - // set `state` to the CSS equivalent - if (state) { - state = null; - } else { - state = "none"; - } + // run through `closest` + for (let i = 0; i < closest.length; i++) { + // shorthand + let closest_el = el.closest(closest[i]); - // run through elements - for (let i = 0; i < search_els.length; i++) { - // check that the element shouldn't be perma hidden, and - // if so, hide it correctly. - if (search_els[i].hasAttribute("perma-hidden")) { - search_els[i].style.display = "none"; - continue; + // was a relevant element found? + if (closest_el) { + // is it supposed to always be hidden? do nothing + if (el.hasAttribute("perma-hidden") + || closest_el.hasAttribute("perma-hidden")) { + + break; } - // set the visibility - search_els[i].style.display = state; - } - } + // reset visibility + closest_el.style.display = null; - // check if `query` is empty, and reset search if so - if (! query || ! query.trim()) { - set_all(true); - } else { - // hide everything - set_all(false); - } - - // unhides `el` and relevant elements related to it - let unhide = (el) => { - // list of elements that could be relevant through `el`'s - // `.closest()` function - let closest = [ - ".option", - "details", - ".buttons", - ] - - // run through `closest` - for (let i = 0; i < closest.length; i++) { - // shorthand - let closest_el = el.closest(closest[i]); - - // was a relevant element found? - if (closest_el) { - // is it supposed to always be hidden? do nothing - if (el.hasAttribute("perma-hidden") - || closest_el.hasAttribute("perma-hidden")) { - - break; - } + // are we at a `<details>`? + if (closest[i] == "details") { + // attempt to get a `.title` inside that + // `<details>` element + let title = closest_el.querySelector(".title"); - // reset visibility - closest_el.style.display = null; - - // are we at a `<details>`? - if (closest[i] == "details") { - // attempt to get a `.title` inside that - // `<details>` element - let title = closest_el.querySelector(".title"); - - // did we find a title? - if (title) { - // reset visibility of title and also reset - // open state of `<details>` - title.style.display = null; - closest_el.setAttribute("open", false); - } + // did we find a title? + if (title) { + // reset visibility of title and also reset + // open state of `<details>` + title.style.display = null; + closest_el.setAttribute("open", false); } } } } + } - // do a Fuse.js search with `query` - let res = settings_fuse.search(query); + // do a Fuse.js search with `query` + let res = settings_fuse.search(query); - // if nothing was found, reset all - if (res.length == 0) { - return set_all(true); - } + // if nothing was found, reset all + if (res.length == 0) { + return set_all(true); + } - // run through results and unhide all of them - for (let i = 0; i < res.length; i++) { - unhide(res[i].item.el); - } + // run through results and unhide all of them + for (let i = 0; i < res.length; i++) { + unhide(res[i].item.el); } } // search on key events in search input let search_el = document.body.querySelector("#options .search"); search_el.addEventListener("keyup", () => { - Settings.search(search_el.value); + settings.popup.search(search_el.value); }) // returns a Fuse.js compatible Array for searching through the settings @@ -337,7 +469,12 @@ events.on("popup-changed", () => { document.body.addEventListener("click", (e) => { let el = document.elementFromPoint(e.clientX, e.clientY); - Settings.switch(el); + settings.popup.switch(el); }) -Settings.load(); +settings.popup.load(); + +// sets the lang to the system default +ipcRenderer.send("setlang", settings_data.lang); + +module.exports = settings; diff --git a/src/app/js/update.js b/src/app/js/update.js new file mode 100644 index 0000000..034f3e8 --- /dev/null +++ b/src/app/js/update.js @@ -0,0 +1,145 @@ +const ipcRenderer = require("electron").ipcRenderer; + +const lang = require("../../lang"); + +// updates version numbers +ipcRenderer.on("version", (event, versions) => { + vpversion.innerText = versions.vp; + nsversion.innerText = versions.ns; + tf2Version.innerText = versions.tf2; + + if (versions.ns == "unknown") { + let buttons = document.querySelectorAll(".modbtns button"); + + for (let i = 0; i < buttons.length; i++) { + buttons[i].disabled = true; + } + + // since Northstar is not installed, we cannot launch it + update.ns.should_install = true; + playNsBtn.innerText = lang("gui.install"); + } +}); ipcRenderer.send("get-version"); + +// when an update is available it'll ask the user about it +ipcRenderer.on("update-available", () => { + if (confirm(lang("gui.update.available"))) { + ipcRenderer.send("update-now"); + } +}) + +// frontend part of updating Northstar +ipcRenderer.on("ns-update-event", (event, options) => { + let key = options.key; + if (typeof options == "string") { + key = options; + } + + // updates text in update button to `lang(key)` + let update_btn = () => { + document.getElementById("update") + .innerText = `(${lang(key)})`; + } + + switch(key) { + case "cli.update.uptodate_short": + case "cli.update.no_internet": + // initial value + let delay = 0; + + // get current time + let now = new Date().getTime(); + + // check if `update.ns.last_checked` was less than 500ms + // since now, this variable is set when `update.ns()` is + // called + if (now - update.ns.last_checked < 500) { + // if less than 500ms has passed, set `delay` to the + // amount of milliseconds missing until we've hit that + // 500ms threshold + delay = 500 - (now - update.ns.last_checked); + } + + // wait `delay`ms + setTimeout(() => { + // set buttons accordingly + update_btn(); + set_buttons(true); + update.ns.progress(false); + playNsBtn.innerText = lang("gui.launch"); + }, delay) + + break; + default: + update_btn(); + + if (options.progress) { + update.ns.progress(options.progress); + } + + if (options.btn_text) { + playNsBtn.innerText = options.btn_text; + } + + set_buttons(false); + break; + } +}) + +let update = { + // deletes install and update cache + delete_cache: () => { + ipcRenderer.send("delete-install-cache"); + }, + + // updates Northstar, `force_update` forcefully updates Northstar, + // causing it to update, even if its already up-to-date + ns: (force_update) => { + update.ns.last_checked = new Date().getTime(); + ipcRenderer.send("update-northstar", force_update); + update.ns.should_install = false; + } +} + +// should Northstar be updated? +update.ns.should_install = false; + +// when was the last time we checked for a Northstar update +update.ns.last_checked = new Date().getTime(); + +// `percent` should be a number between 0 to 100, if it's `false` it'll +// reset it back to nothing instantly, with no animation +update.ns.progress = (percent) => { + // reset button progress + if (percent === false) { + document.querySelector(".contentContainer #nsMain .playBtn") + .style.setProperty("--progress", "unset"); + + return; + } + + percent = parseInt(percent); + + // make sure we're dealing with a number + if (isNaN(percent) || typeof percent !== "number") { + return false; + } + + // limit percent, while this barely has a difference, if you were to + // set a very high number, the CSS would then use a very high + // number, not great. + if (percent > 100) { + percent = 100; + } else if (percent < 0) { + percent = 0; + } + + // invert number to it works in the CSS + percent = 100 - percent; + + // set the CSS progress variable + document.querySelector(".contentContainer #nsMain .playBtn") + .style.setProperty("--progress", percent + "%"); +} + +module.exports = update; |