aboutsummaryrefslogtreecommitdiff
path: root/src/app/js
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2024-06-09 18:09:26 +0200
committerGitHub <noreply@github.com>2024-06-09 18:09:26 +0200
commit04b0e9fcea6c60257d7bc68994103eacb340a82b (patch)
treecd46c3c09f11f839eebb0e4dc682ca6c85689bf6 /src/app/js
parent3904a4492f72ef9a9fd531c0b81f3711541c97e0 (diff)
parentaa53f7a5fd5408596d6cc1ea579d68d1ae897698 (diff)
downloadViper-04b0e9fcea6c60257d7bc68994103eacb340a82b.tar.gz
Viper-04b0e9fcea6c60257d7bc68994103eacb340a82b.zip
Merge pull request #237 from 0neGal/modularized-frontend
chore: Modularize the frontend code Generally this should be fine, there might still be some uncaught bugs, but it hopefully isn't anything too major.
Diffstat (limited to 'src/app/js')
-rw-r--r--src/app/js/browser.js225
-rw-r--r--src/app/js/dom_events.js42
-rw-r--r--src/app/js/events.js2
-rw-r--r--src/app/js/gamepath.js52
-rw-r--r--src/app/js/is_running.js46
-rw-r--r--src/app/js/kill.js8
-rw-r--r--src/app/js/launch.js15
-rw-r--r--src/app/js/launcher.js49
-rw-r--r--src/app/js/localize.js69
-rw-r--r--src/app/js/mods.js141
-rw-r--r--src/app/js/popups.js6
-rw-r--r--src/app/js/process.js27
-rw-r--r--src/app/js/request.js18
-rw-r--r--src/app/js/set_buttons.js40
-rw-r--r--src/app/js/settings.js529
-rw-r--r--src/app/js/toasts.js (renamed from src/app/js/toast.js)16
-rw-r--r--src/app/js/tooltip.js2
-rw-r--r--src/app/js/update.js148
-rw-r--r--src/app/js/version.js (renamed from src/app/js/misc.js)2
19 files changed, 1084 insertions, 353 deletions
diff --git a/src/app/js/browser.js b/src/app/js/browser.js
index 06dc244..21bdf3f 100644
--- a/src/app/js/browser.js
+++ b/src/app/js/browser.js
@@ -1,18 +1,28 @@
+const Fuse = require("fuse.js");
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+
+const popups = require("./popups");
+const toasts = require("./toasts");
+const request = require("./request");
+
var browser_fuse;
var packages = [];
var packagecount = 0;
-var mod_versions = {};
+var browser_el = document.getElementById("browser")
-var Browser = {
+var browser = {
maxentries: 50,
+ mod_versions: {},
filters: {
getpkgs: () => {
let pkgs = [];
let other = [];
for (let i in packages) {
- if (! Browser.filters.isfiltered(packages[i].categories)) {
+ if (! browser.filters.isfiltered(packages[i].categories)) {
pkgs.push(packages[i]);
} else {
other.push(packages[i]);
@@ -24,7 +34,7 @@ var Browser = {
get: () => {
let filtered = [];
let unfiltered = [];
- let checks = browser.querySelectorAll("#filters .check");
+ let checks = browser_el.querySelectorAll("#filters .check");
for (let i = 0; i < checks.length; i++) {
if (! checks[i].classList.contains("checked")) {
@@ -40,8 +50,8 @@ var Browser = {
};
},
isfiltered: (categories) => {
- let filtered = Browser.filters.get().filtered;
- let unfiltered = Browser.filters.get().unfiltered;
+ let filtered = browser.filters.get().filtered;
+ let unfiltered = browser.filters.get().unfiltered;
let state = false;
let filters = [
@@ -86,19 +96,19 @@ var Browser = {
},
},
toggle: (state) => {
- browser.scrollTo(0, 0);
+ browser_el.scrollTo(0, 0);
popups.set("#browser", state);
if (state) {
if (browserEntries.querySelectorAll(".el").length == 0) {
- Browser.loadfront();
+ browser.loadfront();
}
} else if (state === false) {
- Browser.filters.toggle(false);
+ browser.filters.toggle(false);
}
},
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,
@@ -111,7 +121,7 @@ var Browser = {
add_pkg_properties: () => {
for (let i = 0; i < packages.length; i++) {
let properties = packages[i];
- let normalized = normalize(packages[i].name);
+ let normalized = mods.normalize(packages[i].name);
let has_update = false;
let local_name = false;
@@ -119,11 +129,11 @@ 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 && (
+ if (mods.normalize(mod.name) !== normalized && (
! mod.package ||
mod.package.author + "-" + mod.package.package_name !==
packages[i].full_name
@@ -140,7 +150,7 @@ var Browser = {
}
let install = () => {
- return Browser.install({...properties});
+ return browser.install({...properties});
}
packages[i].install = install;
@@ -148,7 +158,7 @@ var Browser = {
packages[i].local_version = local_version;
if (local_version) {
- mod_versions[normalized] = {
+ browser.mod_versions[normalized] = {
install: install,
has_update: has_update,
local_name: local_name,
@@ -160,7 +170,7 @@ var Browser = {
}
},
loadfront: async () => {
- Browser.loading();
+ browser.loading();
packagecount = 0;
@@ -181,26 +191,26 @@ var Browser = {
console.error(err)
}
- Browser.add_pkg_properties();
+ browser.add_pkg_properties();
browser_fuse = new Fuse(packages, {
keys: ["full_name"]
})
}
- let pkgs = Browser.filters.getpkgs();
+ let pkgs = browser.filters.getpkgs();
for (let i in pkgs) {
- if (packagecount >= Browser.maxentries) {
- Browser.endoflist();
+ if (packagecount >= browser.maxentries) {
+ browser.endoflist();
break
}
- new BrowserElFromObj(pkgs[i]);
+ browser.mod_el_from_obj(pkgs[i]);
packagecount++;
}
},
loading: (string) => {
- if (Browser.filters.get().unfiltered.length == 0) {
+ if (browser.filters.get().unfiltered.length == 0) {
string = lang("gui.browser.no_results");
}
@@ -214,7 +224,7 @@ var Browser = {
},
endoflist: (is_end) => {
let pkgs = [];
- let filtered = Browser.filters.getpkgs();
+ let filtered = browser.filters.getpkgs();
for (let i = 0; i < filtered.length; i++) {
if (filtered[packagecount + i]) {
pkgs.push(filtered[packagecount + i]);
@@ -228,26 +238,26 @@ var Browser = {
}
if (pkgs.length == 0 || is_end) {
- Browser.msg(`${lang('gui.browser.endoflist')}`);
+ browser.msg(`${lang('gui.browser.endoflist')}`);
return
}
- Browser.msg(`<button id="loadmore">` +
+ browser.msg(`<button id="loadmore">` +
`<img src="icons/down.png">` +
`<span>${lang("gui.browser.load_more")}</span>` +
`</button>`);
loadmore.addEventListener("click", () => {
- Browser.loadpkgs(pkgs);
- Browser.endoflist(! pkgs.length);
+ browser.loadpkgs(pkgs);
+ browser.endoflist(! pkgs.length);
})
},
search: (string) => {
- Browser.loading();
+ browser.loading();
let res = browser_fuse.search(string);
if (res.length < 1) {
- Browser.loading(lang("gui.browser.no_results"));
+ browser.loading(lang("gui.browser.no_results"));
return
}
@@ -255,18 +265,18 @@ var Browser = {
let count = 0;
for (let i = 0; i < res.length; i++) {
- if (count >= Browser.maxentries) {break}
- if (Browser.filters.isfiltered(res[i].item.categories)) {continue}
- new BrowserElFromObj(res[i].item);
+ if (count >= browser.maxentries) {break}
+ if (browser.filters.isfiltered(res[i].item.categories)) {continue}
+ browser.mod_el_from_obj(res[i].item);
count++;
}
if (count < 1) {
- Browser.loading(lang("gui.browser.no_results"));
+ browser.loading(lang("gui.browser.no_results"));
}
},
setbutton: (mod, string, icon) => {
- mod = normalize(mod);
+ mod = mods.normalize(mod);
if (browserEntries.querySelector(`#mod-${mod}`)) {
let elems = browserEntries.querySelectorAll(`.el#mod-${mod}`);
@@ -281,21 +291,21 @@ var Browser = {
} else {
let make = (str) => {
if (browserEntries.querySelector(`#mod-${str}`)) {
- return Browser.setbutton(str, string, icon);
+ return browser.setbutton(str, string, icon);
} else {
return false;
}
}
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 = mods.normalize(mods.list().all[i].name);
+ let modfolder = mods.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(mods.normalize(mods.list().all[i].manifest_name));
}
}
}
@@ -313,17 +323,17 @@ var Browser = {
let count = 0;
for (let i in pkgs) {
- if (count >= Browser.maxentries) {
+ if (count >= browser.maxentries) {
if (pkgs[i] === undefined) {
- Browser.endoflist(true);
+ browser.endoflist(true);
}
- Browser.endoflist();
+ browser.endoflist();
break
}
try {
- new BrowserElFromObj(pkgs[i]);
+ browser.mod_el_from_obj(pkgs[i]);
}catch(e) {}
count++;
@@ -339,18 +349,14 @@ var Browser = {
}
}
-setInterval(Browser.add_pkg_properties, 1500);
+setInterval(browser.add_pkg_properties, 1500);
if (navigator.onLine) {
- Browser.loadfront();
-}
-
-function openExternal(url) {
- require("electron").shell.openExternal(url);
+ browser.loadfront();
}
var view = document.querySelector(".popup#preview webview");
-var Preview = {
+browser.preview = {
show: () => {
preview.classList.add("shown");
},
@@ -358,16 +364,21 @@ var Preview = {
preview.classList.remove("shown");
},
set: (url, autoshow) => {
- if (autoshow != false) {Preview.show()}
+ if (autoshow != false) {browser.preview.show()}
+
view.src = url;
- document.querySelector("#preview #external").setAttribute("onclick", `openExternal("${url}")`);
+
+ document.querySelector("#preview #external").setAttribute(
+ "onclick",
+ `require("electron").shell.openExternal("${url}")`
+ )
}
}
-function BrowserElFromObj(obj) {
+browser.mod_el_from_obj = (obj) => {
let pkg = {...obj, ...obj.versions[0]};
- new BrowserEl({
+ browser.mod_el({
pkg: pkg,
title: pkg.name,
image: pkg.icon,
@@ -381,8 +392,8 @@ function BrowserElFromObj(obj) {
})
}
-function BrowserEl(properties) {
- if (Browser.filters.isfiltered(properties.categories)) {return}
+browser.mod_el = (properties) => {
+ if (browser.filters.isfiltered(properties.categories)) {return}
properties = {
title: "No name",
@@ -405,8 +416,8 @@ function BrowserEl(properties) {
let installstr = lang("gui.browser.install");
let normalized_mods = [];
- for (let i = 0; i < modsobj.all; i++) {
- normalized_mods.push(normalize(mods_list[i].name));
+ for (let i = 0; i < mods.list().all; i++) {
+ normalized_mods.push(mods.normalize(mods_list[i].name));
}
if (properties.pkg.local_version) {
@@ -421,7 +432,7 @@ function BrowserEl(properties) {
let entry = document.createElement("div");
entry.classList.add("el");
- entry.id = `mod-${normalize(properties.title)}`;
+ entry.id = `mod-${mods.normalize(properties.title)}`;
entry.innerHTML = `
<div class="image">
@@ -435,7 +446,7 @@ function BrowserEl(properties) {
<img src="icons/${installicon}.png">
<span>${installstr}</span>
</button>
- <button class="info" onclick="Preview.set('${properties.url}')">
+ <button class="info" onclick="browser.preview.set('${properties.url}')">
<img src="icons/open.png">
<span>${lang('gui.browser.view')}</span>
</button>
@@ -449,7 +460,7 @@ function BrowserEl(properties) {
`
entry.querySelector("button.install").addEventListener("click", () => {
- Browser.install(properties);
+ browser.install(properties);
})
browserEntries.appendChild(entry);
@@ -466,21 +477,21 @@ function add_recent_toast(name, timeout = 3000) {
}, timeout)
}
-ipcRenderer.on("removed-mod", (event, mod) => {
- setButtons(true);
- Browser.setbutton(mod.name, lang("gui.browser.install"), "downloads");
+ipcRenderer.on("removed-mod", (_, mod) => {
+ set_buttons(true);
+ browser.setbutton(mod.name, lang("gui.browser.install"), "downloads");
if (mod.manifest_name) {
- Browser.setbutton(mod.manifest_name, lang("gui.browser.install"), "downloads");
+ browser.setbutton(mod.manifest_name, lang("gui.browser.install"), "downloads");
}
})
-ipcRenderer.on("failed-mod", (event, modname) => {
+ipcRenderer.on("failed-mod", (_, modname) => {
if (recent_toasts["failed" + modname]) {return}
add_recent_toast("failed" + modname);
- setButtons(true);
- new Toast({
+ set_buttons(true);
+ toasts.show({
timeout: 10000,
scheme: "error",
title: lang("gui.toast.title.failed"),
@@ -488,12 +499,12 @@ ipcRenderer.on("failed-mod", (event, modname) => {
})
})
-ipcRenderer.on("legacy-duped-mod", (event, modname) => {
+ipcRenderer.on("legacy-duped-mod", (_, modname) => {
if (recent_toasts["duped" + modname]) {return}
add_recent_toast("duped" + modname);
- setButtons(true);
- new Toast({
+ set_buttons(true);
+ toasts.show({
timeout: 10000,
scheme: "warning",
title: lang("gui.toast.title.duped"),
@@ -501,9 +512,9 @@ ipcRenderer.on("legacy-duped-mod", (event, modname) => {
})
})
-ipcRenderer.on("no-internet", (event, modname) => {
- setButtons(true);
- new Toast({
+ipcRenderer.on("no-internet", () => {
+ set_buttons(true);
+ toasts.show({
timeout: 10000,
scheme: "error",
title: lang("gui.toast.title.no_internet"),
@@ -511,17 +522,17 @@ ipcRenderer.on("no-internet", (event, modname) => {
})
})
-ipcRenderer.on("installed-mod", (event, mod) => {
+ipcRenderer.on("installed-mod", (_, mod) => {
if (recent_toasts["installed" + mod.name]) {return}
add_recent_toast("installed" + mod.name);
let name = mod.fancy_name || mod.name;
- setButtons(true);
- Browser.setbutton(name, lang("gui.browser.reinstall"), "redo");
+ set_buttons(true);
+ browser.setbutton(name, lang("gui.browser.reinstall"), "redo");
if (mod.malformed) {
- new Toast({
+ toasts.show({
timeout: 8000,
scheme: "warning",
title: lang("gui.toast.title.malformed"),
@@ -529,56 +540,38 @@ ipcRenderer.on("installed-mod", (event, mod) => {
})
}
- new Toast({
+ toasts.show({
scheme: "success",
title: lang("gui.toast.title.installed"),
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();
}
})
-function normalize(items) {
- let main = (string) => {
- return string.replaceAll(" ", "")
- .replaceAll(".", "").replaceAll("-", "")
- .replaceAll("_", "").toLowerCase();
- }
- if (typeof items == "string") {
- return main(items);
- } else {
- let newArray = [];
- for (let i = 0; i < items.length; i++) {
- newArray.push(main(items[i]));
- }
-
- return newArray;
- }
-}
-
let searchtimeout;
let searchstr = "";
let search = document.querySelector("#browser .search");
search.addEventListener("keyup", () => {
- Browser.filters.toggle(false);
+ browser.filters.toggle(false);
clearTimeout(searchtimeout);
if (searchstr != search.value) {
if (search.value.replaceAll(" ", "") == "") {
searchstr = "";
- Browser.loadfront();
+ browser.loadfront();
return
}
searchtimeout = setTimeout(() => {
- Browser.search(search.value);
+ browser.search(search.value);
searchstr = search.value;
}, 500)
}
@@ -586,24 +579,24 @@ search.addEventListener("keyup", () => {
let mouse_events = ["scroll", "mousedown", "touchdown"];
mouse_events.forEach((event) => {
- document.body.addEventListener(event, (e) => {
+ document.body.addEventListener(event, () => {
let mouse_at = document.elementsFromPoint(mouseX, mouseY);
if (! mouse_at.includes(document.querySelector("#preview"))) {
- Preview.hide();
+ browser.preview.hide();
}
if (! mouse_at.includes(document.querySelector("#filter"))
&& ! mouse_at.includes(document.querySelector(".overlay"))) {
- Browser.filters.toggle(false);
+ browser.filters.toggle(false);
}
})
});
view.addEventListener("dom-ready", () => {
let css = [
- fs.readFileSync(__dirname + "/css/theming.css", "utf8"),
- fs.readFileSync(__dirname + "/css/webview.css", "utf8")
+ fs.readFileSync(__dirname + "/../css/theming.css", "utf8"),
+ fs.readFileSync(__dirname + "/../css/webview.css", "utf8")
]
view.insertCSS(css.join(" "));
@@ -623,7 +616,7 @@ view.addEventListener("did-start-loading", () => {
let mouseY = 0;
let mouseX = 0;
-browser.addEventListener("mousemove", (event) => {
+browser_el.addEventListener("mousemove", (event) => {
mouseY = event.clientY;
mouseX = event.clientX;
})
@@ -631,6 +624,8 @@ browser.addEventListener("mousemove", (event) => {
let checks = document.querySelectorAll(".check");
for (let i = 0; i < checks.length; i++) {
checks[i].setAttribute("onclick",
- "this.classList.toggle('checked');Browser.loadfront();search.value = ''"
+ "this.classList.toggle('checked');browser.loadfront();search.value = ''"
)
}
+
+module.exports = browser;
diff --git a/src/app/js/dom_events.js b/src/app/js/dom_events.js
new file mode 100644
index 0000000..c16a838
--- /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..c3e5c2b
--- /dev/null
+++ b/src/app/js/gamepath.js
@@ -0,0 +1,52 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+const process = require("./process");
+const launcher = require("./launcher");
+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", () => {
+ launcher.change_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..913762b 100644
--- a/src/app/js/launcher.js
+++ b/src/app/js/launcher.js
@@ -1,12 +1,13 @@
const markdown = require("marked").parse;
+let launcher = {};
+
var servercount;
var playercount;
var masterserver;
-// Changes the main page
-// This is the tabs in the sidebar
-function page(page) {
+// changes the main page, this is the tabs in the sidebar
+launcher.change_page = (page) => {
let btns = document.querySelectorAll(".gamesContainer button");
let pages = document.querySelectorAll(".mainContainer .contentContainer");
@@ -21,9 +22,9 @@ function page(page) {
pages[page].classList.remove("hidden");
btns[page].classList.remove("inactive");
bgHolder.setAttribute("bg", page);
-}; page(1)
+}; launcher.change_page(1)
-function formatRelease(notes) {
+launcher.format_release = (notes) => {
if (! notes) {return ""}
let content = "";
@@ -62,7 +63,7 @@ function formatRelease(notes) {
// sets content of `div` to a single release block with centered text
// inside it, the text being `lang(lang_key)`
-let set_error_content = (div, lang_key) => {
+launcher.error = (div, lang_key) => {
div.innerHTML =
"<div class='release-block'>" +
"<p><center>" +
@@ -71,42 +72,42 @@ 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(
+ return launcher.error(
vpReleaseNotes,
"request.no_vp_release_notes"
)
}
- vpReleaseNotes.innerHTML = formatRelease(response);
+ vpReleaseNotes.innerHTML = launcher.format_release(response);
});
-// Updates the Northstar release notes
+// updates the Northstar release notes
ipcRenderer.on("ns-notes", (event, response) => {
if (! response) {
- return set_error_content(
+ return launcher.error(
nsRelease,
"request.no_ns_release_notes"
)
}
- nsRelease.innerHTML = formatRelease(response);
+ nsRelease.innerHTML = launcher.format_release(response);
});
-async function loadVpReleases() {
+launcher.load_vp_notes = async () => {
ipcRenderer.send("get-vp-notes");
-}; loadVpReleases();
+}; launcher.load_vp_notes();
-async function loadNsReleases() {
+launcher.load_ns_notes = async () => {
ipcRenderer.send("get-ns-notes");
-}; loadNsReleases();
+}; launcher.load_ns_notes();
// TODO: We gotta make this more automatic instead of switch statements
// it's both not pretty, but adding more sections requires way too much
// effort, compared to how it should be.
-function showVpSection(section) {
+launcher.show_vp = (section) => {
if (!["main", "release", "info", "credits"].includes(section)) throw new Error("unknown vp section");
vpMainBtn.removeAttribute("active");
vpReleaseBtn.removeAttribute("active");
@@ -132,8 +133,8 @@ function showVpSection(section) {
}
}
-function showNsSection(section) {
- if (!["main", "release", "mods"].includes(section)) {
+launcher.show_ns = (section) => {
+ if (! ["main", "release", "mods"].includes(section)) {
throw new Error("unknown ns section");
}
@@ -162,7 +163,7 @@ function showNsSection(section) {
}
}
-async function loadServers() {
+launcher.check_servers = async () => {
serverstatus.classList.add("checking");
try {
@@ -206,9 +207,11 @@ async function loadServers() {
serverstatus.innerHTML = lang("gui.server.offline");
}
-}; loadServers()
+}; launcher.check_servers()
-// Refreshes every 5 minutes
+// refreshes every 5 minutes
setInterval(() => {
- loadServers();
+ launcher.check_servers();
}, 300000)
+
+module.exports = launcher;
diff --git a/src/app/js/localize.js b/src/app/js/localize.js
new file mode 100644
index 0000000..6776566
--- /dev/null
+++ b/src/app/js/localize.js
@@ -0,0 +1,69 @@
+// localizes `string`, removing instances of `%%string%%` with
+// `lang("string")` and so forth
+function localize_string(string) {
+ let parts = string.split("%%");
+
+ // basic checks to make sure `string` has lang strings
+ if (parts.length == 0
+ || string.trim() == "" || ! string.match("%%")) {
+ return string;
+ }
+
+ for (let i = 0; i < parts.length; i++) {
+ // simply checks to make sure it is actually a lang string.
+ if (parts[i][0] != " " &&
+ parts[i][parts[i].length - 1] != " ") {
+
+ // get string
+ let lang_str = lang(parts[i]);
+
+ // make sure we got a string back, and if not, do nothing
+ if (typeof lang_str !== "string") {
+ continue;
+ }
+
+ // replace this part with the lang string
+ parts[i] = lang_str;
+ }
+ }
+
+ // return finalized formatted string
+ return parts.join("");
+}
+
+// runs `localize_string()` on `el`'s attributes, text nodes and children
+function localize_el(el) {
+ // we don't want to mess with script tags
+ if (el.tagName == "SCRIPT") {return}
+
+ let attributes = el.getAttributeNames();
+
+ // run through child nodes
+ for (let i = 0; i < el.childNodes.length; i++) {
+ // if the node isn't a text node, we do nothing
+ if (el.childNodes[i].nodeType != Node.TEXT_NODE) {
+ continue;
+ }
+
+ // the node is a text node, so we set its `.textContent` by
+ // running `format_string()` on it
+ el.childNodes[i].textContent =
+ localize_string(el.childNodes[i].textContent)
+ }
+
+ // run through attributes and run `format_string()` on their values
+ for (let i = 0; i < attributes.length; i++) {
+ let attr = el.getAttribute(attributes[i]);
+ el.setAttribute(attributes[i], localize_string(attr))
+ }
+
+ // run `replace_in_el()` on `el`'s children
+ for (let i = 0; i < el.children.length; i++) {
+ localize_el(el.children[i]);
+ }
+}
+
+// localizes lang strings on (almost) all the elements inside `<body>`
+module.exports = () => {
+ localize_el(document.body);
+}
diff --git a/src/app/js/mods.js b/src/app/js/mods.js
index 182bddf..f463ddb 100644
--- a/src/app/js/mods.js
+++ b/src/app/js/mods.js
@@ -1,17 +1,35 @@
-var mods = {};
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+
+const version = require("./version");
+const set_buttons = require("./set_buttons");
+
+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}`;
let normalized_names = [];
-
+
let set_mod = (mod) => {
let name = mod.name;
if (mod.package) {
name = mod.package.package_name;
}
- let normalized_name = "mod-list-" + normalize(name);
+ let normalized_name = "mod-list-" + mods.normalize(name);
normalized_names.push(normalized_name);
@@ -133,8 +151,8 @@ mods.load = (mods_obj) => {
let image_el = image_container.querySelector("img")
let image_blur_el = image_container.querySelector("img.blur")
- if (mod_versions[mod]) {
- image_el.src = mod_versions[mod].package.versions[0].icon;
+ if (browser.mod_versions[mod]) {
+ image_el.src = browser.mod_versions[mod].package.versions[0].icon;
}
if (image_el.getAttribute("src") &&
@@ -148,13 +166,13 @@ mods.load = (mods_obj) => {
image_container.parentElement.classList.add("has-icon");
}
- if (mod_versions[mod]
- && mod_versions[mod].has_update) {
+ if (browser.mod_versions[mod]
+ && browser.mod_versions[mod].has_update) {
mod_els[i].querySelector(".update").style.display = null;
mod_els[i].querySelector(".update").setAttribute(
- "onclick", `mod_versions["${mod}"].install()`
+ "onclick", `browser.mod_versions["${mod}"].install()`
)
if (mod_update_els.includes(mod_els[i].id)) {
@@ -170,11 +188,11 @@ mods.load = (mods_obj) => {
mod_el.classList.add("no-animation");
mod_el.querySelector(".switch").addEventListener("click", () => {
- if (mod_versions[mod].local_name) {
- mods.toggle(mod_versions[mod].local_name);
+ if (browser.mod_versions[mod].local_name) {
+ mods.toggle(browser.mod_versions[mod].local_name);
}
})
-
+
mod_els[i].remove();
modsdiv.querySelector(".line").after(mod_el);
} else {
@@ -210,3 +228,104 @@ 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;
+}
+
+mods.normalize = (items) => {
+ let main = (string) => {
+ return string.replaceAll(" ", "")
+ .replaceAll(".", "").replaceAll("-", "")
+ .replaceAll("_", "").toLowerCase();
+ }
+ if (typeof items == "string") {
+ return main(items);
+ } else {
+ let newArray = [];
+ for (let i = 0; i < items.length; i++) {
+ newArray.push(main(items[i]));
+ }
+
+ return newArray;
+ }
+}
+
+// updates the installed mods
+ipcRenderer.on("mods", (event, mods_obj) => {
+ mods_list = mods_obj;
+ if (! mods_obj) {return}
+
+ mods.load(mods_obj);
+})
+
+module.exports = mods;
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/settings.js b/src/app/js/settings.js
index 2d68c13..3addf0e 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_data`
+ 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_data[optName] != undefined) {
+ switch(typeof settings_data[optName]) {
+ case "string":
+ options[i].querySelector(".actions input").value = settings_data[optName];
+ break
+ case "object":
+ options[i].querySelector(".actions input").value = settings_data[optName].join(" ");
+ break
+ case "boolean":
+ let switchDiv = options[i].querySelector(".actions .switch");
+ if (settings_data[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/toast.js b/src/app/js/toasts.js
index e0aba6b..c3bba99 100644
--- a/src/app/js/toast.js
+++ b/src/app/js/toasts.js
@@ -1,4 +1,6 @@
-function Toast(properties) {
+let toasts = {};
+
+toasts.show = (properties) => {
let toast = {
timeout: 3000,
fg: "#FFFFFF",
@@ -36,7 +38,7 @@ function Toast(properties) {
el.id = id;
el.addEventListener("click", () => {
- dismissToast(id);
+ toasts.dismiss(id);
toast.callback();
})
@@ -53,15 +55,17 @@ function Toast(properties) {
el.querySelector(".description").remove();
}
- toasts.appendChild(el);
+ document.getElementById("toasts").appendChild(el);
setTimeout(() => {
- dismissToast(id);
+ toasts.dismiss(id);
}, toast.timeout)
}
-function dismissToast(id) {
+// dismissed/closes toasts with `id` as their ID
+toasts.dismiss = (id) => {
id = document.getElementById(id);
+
if (id) {
id.classList.add("hidden");
setTimeout(() => {
@@ -69,3 +73,5 @@ function dismissToast(id) {
}, 500)
}
}
+
+module.exports = toasts;
diff --git a/src/app/js/tooltip.js b/src/app/js/tooltip.js
index 13dcd67..f2d97a0 100644
--- a/src/app/js/tooltip.js
+++ b/src/app/js/tooltip.js
@@ -128,3 +128,5 @@ document.addEventListener("mousedown", tooltip_event);
document.addEventListener("mousemove", tooltip_event);
document.addEventListener("mouseenter", tooltip_event);
document.addEventListener("mouseleave", tooltip_event);
+
+module.exports = tooltip;
diff --git a/src/app/js/update.js b/src/app/js/update.js
new file mode 100644
index 0000000..6aa1b6d
--- /dev/null
+++ b/src/app/js/update.js
@@ -0,0 +1,148 @@
+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);
+ }
+
+ // request up-to-date version numbers
+ ipcRenderer.send("get-version");
+
+ // 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/js/misc.js b/src/app/js/version.js
index b35f239..97223f9 100644
--- a/src/app/js/misc.js
+++ b/src/app/js/version.js
@@ -1,4 +1,4 @@
-version = {
+module.exports = {
is_newer: (version1, version2) => {
version1 = version.format(version1, true).split(".");
version2 = version.format(version2, true).split(".");