diff options
author | 0neGal <mail@0negal.com> | 2024-06-08 18:02:48 +0200 |
---|---|---|
committer | 0neGal <mail@0negal.com> | 2024-06-08 18:02:48 +0200 |
commit | dbd8c6b152acc1188d2edd288488aa2da8f6310b (patch) | |
tree | ef5a1752e845a274c889ee18207c3a25e8290b23 /src/app | |
parent | 3904a4492f72ef9a9fd531c0b81f3711541c97e0 (diff) | |
download | Viper-dbd8c6b152acc1188d2edd288488aa2da8f6310b.tar.gz Viper-dbd8c6b152acc1188d2edd288488aa2da8f6310b.zip |
initial commit to better modularize frontend
Far from done, but this pretty much splits everything inside
`src/app/main.js` into separate files.
Diffstat (limited to 'src/app')
-rw-r--r-- | src/app/css/theming.css | 2 | ||||
-rw-r--r-- | src/app/index.html | 42 | ||||
-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 (renamed from src/app/lang.js) | 10 | ||||
-rw-r--r-- | src/app/js/settings.js | 529 | ||||
-rw-r--r-- | src/app/js/update.js | 145 | ||||
-rw-r--r-- | src/app/main.js | 557 |
19 files changed, 890 insertions, 794 deletions
diff --git a/src/app/css/theming.css b/src/app/css/theming.css index aff3782..fb8fbf7 100644 --- a/src/app/css/theming.css +++ b/src/app/css/theming.css @@ -37,7 +37,7 @@ a { transition: filter 0.2s ease-in !important; } -a.disabled:not("[onclick='kill_game()']") { +a.disabled:not("[onclick='kill('game')']") { opacity: 0.5; pointer-events: none; } diff --git a/src/app/index.html b/src/app/index.html index 016b708..87a1029 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -12,7 +12,7 @@ <div id="toasts"></div> <div id="winbtns"> - <div id="settings" tooltip="%%tooltip.settings%%" tooltip-position="horizontal" onclick="Settings.toggle()"> + <div id="settings" tooltip="%%tooltip.settings%%" tooltip-position="horizontal" onclick="settings.popup.toggle()"> <img src="icons/settings.png"> </div> <div id="minimize" tooltip="%%tooltip.minimize%%" tooltip-position="horizontal" onclick="ipcRenderer.send('minimize')"> @@ -34,11 +34,11 @@ <div class="popup" id="options"> <div class="misc"> <input class="search" placeholder="%%gui.search%%"> - <button id="apply" onclick="Settings.apply();Settings.toggle(false)"> + <button id="apply" onclick="settings.popup.apply();settings.popup.toggle(false)"> <img src="icons/apply.png"> %%gui.settings.save%% </button> - <button id="close" onclick="Settings.toggle(false);Settings.load()"> + <button id="close" onclick="settings.popup.toggle(false);settings.popup.load()"> <img src="icons/close.png"> %%gui.settings.discard%% </button> @@ -91,7 +91,7 @@ </div> </div> <div class="actions"> - <select onchange="Settings.switch(document.querySelector(`.option[name='autolang'] button`), false)"> + <select onchange="settings.popup.switch(document.querySelector(`.option[name='autolang'] button`), false)"> <option></option> </select> </div> @@ -147,9 +147,9 @@ </div> </div> <div class="actions"> - <button onclick="delete_request_cache()">%%gui.settings.updatebuttons.buttons.reset_cached_api_requests%%</button> - <button onclick="force_update_ns()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_northstar_reinstall%%</button> - <button onclick="delete_install_cache()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_delete_install_cache%%</button> + <button onclick="request.delete_cache()">%%gui.settings.updatebuttons.buttons.reset_cached_api_requests%%</button> + <button onclick="update.ns(true)" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_northstar_reinstall%%</button> + <button onclick="update.delete_cache()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_delete_install_cache%%</button> </div> </div> </details> @@ -181,12 +181,12 @@ </div> </div> <div class="actions"> - <button onclick="relaunch()">%%gui.settings.miscbuttons.buttons.restart_viper%%</button> - <button onclick="reset_config()">%%gui.settings.miscbuttons.buttons.reset_config%%</button> - <button onclick="open_gamepath()">%%gui.settings.miscbuttons.buttons.open_gamepath%%</button> - <button onclick="kill_game()">%%gui.settings.miscbuttons.buttons.force_quit_game%%</button> - <button onclick="kill_origin()">%%gui.settings.miscbuttons.buttons.force_quit_origin%%</button> - <button onclick="setpath()" class="disable-when-installing">%%gui.settings.miscbuttons.buttons.change_gamepath%%</button> + <button onclick="process.relaunch()">%%gui.settings.miscbuttons.buttons.restart_viper%%</button> + <button onclick="settings.reset()">%%gui.settings.miscbuttons.buttons.reset_config%%</button> + <button onclick="gamepath.open()">%%gui.settings.miscbuttons.buttons.open_gamepath%%</button> + <button onclick="kill('game')">%%gui.settings.miscbuttons.buttons.force_quit_game%%</button> + <button onclick="kill('origin')">%%gui.settings.miscbuttons.buttons.force_quit_origin%%</button> + <button onclick="gamepath.set()" class="disable-when-installing">%%gui.settings.miscbuttons.buttons.change_gamepath%%</button> </div> </div> </details> @@ -247,7 +247,7 @@ <img src="icons/viper.png"/> <div class="inline" style="margin-top: 20px;"> <div id="vpversion"></div> | - <a id="setpath" href="#" onclick="setpath()" class="disable-when-installing">%%gui.setpath%%</a> + <a id="setpath" href="#" onclick="gamepath.set()" class="disable-when-installing">%%gui.setpath%%</a> </div> </div> <div id="vpReleaseNotes" class="hidden section"></div> @@ -277,10 +277,10 @@ <div id="nsMain" class="section"> <div class="img"><img src="../assets/ns.png"></div> <div class="playBtnContainer"> - <button id="playNsBtn" class="playBtn" onclick="launch()">%%gui.launch%%</button> + <button id="playNsBtn" class="playBtn" onclick="launch('northstar')">%%gui.launch%%</button> <div class="inline"> <div id="nsversion"></div> - <a id="update" href="#" onclick="updateNorthstar()" class="disable-when-installing">(%%gui.update.check%%)</a> + <a id="update" href="#" onclick="update.ns()" class="disable-when-installing">(%%gui.update.check%%)</a> <div id="serverstatus" class="checking"></div> </div> </div> @@ -298,7 +298,7 @@ <img src="icons/toggles.png"> %%gui.mods.toggle_all%% </button> - <button id="installmod" class="bg-blue" onclick="installmod()"> + <button id="installmod" class="bg-blue" onclick="mods.install_prompt()"> <img src="icons/downloads.png"> %%gui.mods.install%% </button> @@ -320,10 +320,11 @@ <div class="section"> <div class="img"><img src="../assets/vanilla.png"></div> <div class="playBtnContainer"> - <button class="playBtn" onclick="launchVanilla()">%%gui.launch%%</button> + <button class="playBtn" onclick="launch('vanilla')">%%gui.launch%%</button> <div class="inline"> <div id="tf2Version"></div> - <a id="tfquit" style="display: none" href="#" onclick="kill_game()">(%%ns.menu.force_quit%%)</a> + <a id="tfquit" style="display: none" + href="#" onclick="kill('game')">(%%ns.menu.force_quit%%)</a> </div> </div> </div> @@ -331,16 +332,13 @@ </div> </div> - <script src="lang.js"></script> <script src="main.js"></script> <script src="js/misc.js"></script> <script src="js/mods.js"></script> <script src="js/toast.js"></script> - <script src="js/events.js"></script> <script src="js/popups.js"></script> <script src="js/tooltip.js"></script> <script src="js/browser.js"></script> - <script src="js/settings.js"></script> <script src="js/launcher.js"></script> </body> </html> 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/lang.js b/src/app/js/set_dom_strings.js index f1c31d3..000aba4 100644 --- a/src/app/lang.js +++ b/src/app/js/set_dom_strings.js @@ -1,12 +1,12 @@ -// Replaces strings in the HTML will language strings properly. This +// 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. -function setlang() { - // Finds %%%% strings +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. + // 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 @@ -14,6 +14,6 @@ function setlang() { } } - // Replaces the original HTML with the translated/replaced HTML + // 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; diff --git a/src/app/main.js b/src/app/main.js index c3f89d2..2ed3053 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -3,317 +3,7 @@ const path = require("path"); const Fuse = require("fuse.js"); const { app, ipcRenderer, shell } = require("electron"); -const json = require("../modules/json"); - const lang = require("../lang"); -var modsobj = { - all: [], - enabled: [], - disabled: [] -} - -let shouldInstallNorthstar = false; - -// Base settings -var settings = { - 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" - ] -} - -// invokes `requests.get()` from `src/modules/requests.js` through the -// main process, and returns the output -async function request(...args) { - return await ipcRenderer.invoke("request", ...args); -} - -// Sets the lang to the system default -ipcRenderer.send("setlang", settings.lang); - -// 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(() => { - iteration++; - - config = json("viper.json") || {}; - - // checks whether `settings.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.gamepath.length === 0) { - setpath(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 - setpath(true); - } - - // filepath to Northstar's startup args file - let args = path.join(settings.gamepath, "ns_startup_args.txt"); - - // check file exists, and that no `nsargs` setting was set - if (! settings.nsargs && fs.existsSync(args)) { - // load arguments from file into `settings` - settings.nsargs = fs.readFileSync(args, "utf8"); - } - } - - // make sure config isn't empty - if (Object.keys(config).length !== 0) { - // add `config` to `settings` - settings = { - ...settings, - ...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 - setpath(false); - clearInterval(config_interval); - } - }, 100) -} else { - setpath(); -} - -ipcRenderer.on("changed-settings", (e, new_settings) => { - // attempt to set `settings` to `new_settings` - try { - settings = { - ...settings, - ...new_settings - } - }catch(e) {} -}) - -// Show a toast message if no Internet connection has been detected. -if (! navigator.onLine) { - ipcRenderer.send("no-internet"); -} - -function exit() {ipcRenderer.send("exit")} - -let checked_for_ns_update = new Date().getTime(); - -function updateNorthstar() { - checked_for_ns_update = new Date().getTime(); - ipcRenderer.send("update-northstar") -} - -function force_update_ns() { - ipcRenderer.send("update-northstar", true); -} - -function reset_config() { - ipcRenderer.send("reset-config"); -} - -function open_gamepath() { - let open_path = require("electron").shell.openPath; - if (settings.gamepath) { - open_path(settings.gamepath); - } else { - alert(lang("gui.settings.miscbuttons.open_gamepath_alert")); - } -} - -function relaunch() { - ipcRenderer.send("relaunch"); -} - -// Reports to the main process about game path status. -// @param {boolean} value is game path loaded -function setpath(value = false) { - ipcRenderer.send("setpath", value); -} - -// Tells the main process to launch or install Northstar -function launch() { - if (shouldInstallNorthstar) { - updateNorthstar(); - shouldInstallNorthstar = false; - } else { - ipcRenderer.send("launch-ns"); - } -} - -// Tells the main process to launch the vanilla game -function launchVanilla() {ipcRenderer.send("launch-vanilla")} - -let log = console.log; - -// Disables or enables certain buttons when for example -// updating/installing Northstar. -function setButtons(state, enable_gamepath_btns) { - playNsBtn.disabled = !state; - - let disablearray = (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") - } - } - } - - disablearray(document.querySelectorAll("#modsdiv .el button")); - disablearray(document.querySelectorAll(".disable-when-installing")); - disablearray(document.querySelectorAll(".playBtnContainer .playBtn")); - disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button")); - disablearray(document.querySelectorAll("#browser #browserEntries .text button")); - - if (enable_gamepath_btns) { - let gamepath_btns = - document.querySelectorAll('*[onclick="setpath()"]'); - - for (let i = 0; i < gamepath_btns.length; i++) { - gamepath_btns[i].disabled = false; - gamepath_btns[i].classList.remove("disabled"); - } - } -} - -// `percent` should be a number between 0 to 100, if it's `false` it'll -// reset it back to nothing instantly, with no animatino -function set_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 + "%"); -} - -ipcRenderer.on("set-buttons", (event, state) => { - setButtons(state); -}) - -ipcRenderer.on("gamepath-lost", (event, state) => { - page(0); - setButtons(false, true); - alert(lang("gui.gamepath.lost")); -}) - -// 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 `checked_for_ns_update` was less than 500ms - // since now, this variable is set when `updateNorthstar()` - // is called - if (now - checked_for_ns_update < 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 - checked_for_ns_update); - } - - // wait `delay`ms - setTimeout(() => { - // set buttons accordingly - update_btn(); - setButtons(true); - set_ns_progress(false); - playNsBtn.innerText = lang("gui.launch"); - }, delay) - - break; - default: - update_btn(); - - if (options.progress) { - set_ns_progress(options.progress); - } - - if (options.btn_text) { - playNsBtn.innerText = options.btn_text; - } - - setButtons(false); - break; - } -}); ipcRenderer.on("unknown-error", (event, err) => { new Toast({ @@ -331,238 +21,21 @@ ipcRenderer.on("unknown-error", (event, err) => { } }) - console.error(err.stack) -}) - -let installqueue = []; - -// Tells the main process to install a mod through the file selector -function installmod() { - setButtons(false); - ipcRenderer.send("install-mod"); -} - -// Tells the main process to directly install a mod from this path -function installFromPath(path) { - setButtons(false); - ipcRenderer.send("install-from-path", path); -} - -// Tells the main process to install a mod from a URL -function installFromURL(url, dependencies, clearqueue, author, package_name, version) { - if (clearqueue) {installqueue = []}; - - 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 (! isModInstalled(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 - } - } - - setButtons(false); - ipcRenderer.send("install-from-url", url, author, package_name, version); - - if (dependencies) { - installqueue = dependencies; - } -} - -function isModInstalled(modname) { - for (let i = 0; i < modsobj.all.length; i++) { - let mod = modsobj.all[i]; - if (mod.manifest_name) { - if (mod.manifest_name.match(modname)) { - return true; - } - } else if (mod.name.match(modname)) { - return true; - } - } - - return false; -} - -// Frontend part of settings a new game path -ipcRenderer.on("newpath", (event, newpath) => { - setButtons(true); - settings.gamepath = newpath; - ipcRenderer.send("gui-getmods"); - ipcRenderer.send("save-settings", settings); -}) - -// Continuation of log() -ipcRenderer.on("log", (event, msg) => {log(msg)}) -ipcRenderer.on("alert", (event, data) => { - alert(data.message); - ipcRenderer.send("alert-closed-" + data.id); + console.error(err.stack); }) -ipcRenderer.on("confirm", (event, data) => { - let confirmed = confirm(data.message); - ipcRenderer.send("confirm-closed-" + data.id, confirmed); -}) - -let is_running = false; -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) { - setButtons(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) { - setButtons(true); - set_playbtns(lang("gui.launch")); - - is_running = running; - - // hide force quit button in Titanfall tab - tfquit.style.display = "none"; - - update.setAttribute("onclick", "updateNorthstar()"); - update.innerHTML = "(" + lang("gui.update.check") + ")"; - } -}) - -function kill_game() { - ipcRenderer.send("kill-game"); -} - -function kill_origin() { - ipcRenderer.send("kill-origin"); -} - -function delete_request_cache() { - ipcRenderer.send("delete-request-cache"); -} - -function delete_install_cache() { - ipcRenderer.send("delete-install-cache"); -} - -// Updates the installed mods -ipcRenderer.on("mods", (event, mods_obj) => { - modsobj = mods_obj; - if (! mods_obj) {return} - - mods.load(mods_obj); -}) - -// 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 - shouldInstallNorthstar = 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"); - } -}) - -// Error out when no game path is set -ipcRenderer.on("no-path-selected", () => { - alert(lang("gui.gamepath.must")); - exit(); -}); - -// Error out when game path is wrong -ipcRenderer.on("wrong-path", () => { - alert(lang("gui.gamepath.wrong")); - setpath(false); -}); - -setlang(); - -let dragtimer; -document.addEventListener("dragover", (e) => { - e.preventDefault(); - e.stopPropagation(); - dragUI.classList.add("shown"); - - clearTimeout(dragtimer); - dragtimer = setTimeout(() => { - dragUI.classList.remove("shown"); - }, 5000) -}); - -document.addEventListener("mouseover", (e) => { - clearTimeout(dragtimer); - dragUI.classList.remove("shown"); -}); - -document.addEventListener("drop", (e) => { - event.preventDefault(); - event.stopPropagation(); - - dragUI.classList.remove("shown"); - installFromPath(event.dataTransfer.files[0].path); -}); - -document.body.addEventListener("keyup", (e) => { - if (e.key == "Escape") { - Browser.toggle(false); - Settings.toggle(false); - } -}) +const json = require("../modules/json"); -document.body.addEventListener("click", event => { - if (event.target.tagName.toLowerCase() === "a" && event.target.protocol != "file:") { - event.preventDefault(); - shell.openExternal(event.target.href); - } -}); +const kill = require("./js/kill"); +const update = require("./js/update"); +const events = require("./js/events"); +const launch = require("./js/launch"); +const request = require("./js/request"); +const process = require("./js/process"); +const settings = require("./js/settings"); +const gamepath = require("./js/gamepath"); +const is_running = require("./js/is_running"); +const set_buttons = require("./js/set_buttons"); + +require("./js/dom_events"); +require("./js/set_dom_strings")(); |