aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2024-06-09 18:15:45 +0200
committer0neGal <mail@0negal.com>2024-06-09 18:23:29 +0200
commit760031c079ce830755ba4fea029e149f4140e00b (patch)
tree8dbb9f8d5bb4e19cdaffb40e8a091b457a597769
parent1c1e8fb730b9974cd9b8060499773b0f37ff28d2 (diff)
parent04b0e9fcea6c60257d7bc68994103eacb340a82b (diff)
downloadViper-linux-launch-v2.tar.gz
Viper-linux-launch-v2.zip
Merge branch 'main' into linux-launch-v2linux-launch-v2
-rw-r--r--src/app/css/theming.css2
-rw-r--r--src/app/index.html75
-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.js589
-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
-rw-r--r--src/app/lang.js19
-rw-r--r--src/app/main.js576
-rw-r--r--src/modules/kill.js10
-rw-r--r--src/modules/launch.js5
-rw-r--r--src/modules/mods.js4
26 files changed, 1183 insertions, 1005 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 c6001f4..ca80c86 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>
@@ -138,7 +138,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>
@@ -194,9 +194,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>
@@ -228,12 +228,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>
@@ -252,10 +252,10 @@
<div class="misc">
<input class="search" placeholder="%%gui.search%%">
- <button id="filter" onclick="Browser.filters.toggle()">
+ <button id="filter" onclick="browser.filters.toggle()">
<img src="icons/filter.png">
</button>
- <button id="close" onclick="Browser.toggle(false)">
+ <button id="close" onclick="browser.toggle(false)">
<img src="icons/close.png">
</button>
</div>
@@ -265,7 +265,7 @@
</div>
<div class="popup small blur" id="preview">
<div class="misc fixed vertical">
- <button id="close" onclick="Preview.hide()">
+ <button id="close" onclick="browser.preview.hide()">
<img src="icons/close.png">
</button>
<button id="external" onclick="">
@@ -277,24 +277,24 @@
</div>
<nav class="gamesContainer">
- <button id="vpBtn" tooltip="%%tooltip.pages.viper%%" tooltip-position="horizontal" onclick="page(0)"></button>
- <button id="nsBtn" tooltip="%%tooltip.pages.northstar%%" tooltip-position="horizontal" onclick="page(1)"></button>
- <button id="tfBtn" tooltip="%%tooltip.pages.titanfall%%" tooltip-position="horizontal" onclick="page(2)"></button>
+ <button id="vpBtn" tooltip="%%tooltip.pages.viper%%" tooltip-position="horizontal" onclick="launcher.change_page(0)"></button>
+ <button id="nsBtn" tooltip="%%tooltip.pages.northstar%%" tooltip-position="horizontal" onclick="launcher.change_page(1)"></button>
+ <button id="tfBtn" tooltip="%%tooltip.pages.titanfall%%" tooltip-position="horizontal" onclick="launcher.change_page(2)"></button>
</nav>
<div class="mainContainer">
<div id="vpContent" class="contentContainer">
<ul class="contentMenu">
- <li id="vpMainBtn" active onclick="showVpSection('main')">%%viper.menu.main%%</li>
- <li id="vpReleaseBtn" onclick="showVpSection('release')">%%viper.menu.release%%</li>
- <li id="vpInfoBtn" onclick="showVpSection('info')">%%viper.menu.info%%</li>
+ <li id="vpMainBtn" active onclick="launcher.show_vp('main')">%%viper.menu.main%%</li>
+ <li id="vpReleaseBtn" onclick="launcher.show_vp('release')">%%viper.menu.release%%</li>
+ <li id="vpInfoBtn" onclick="launcher.show_vp('info')">%%viper.menu.info%%</li>
</ul>
<div class="contentBody">
<div id="vpMain" class="section">
<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>
@@ -316,18 +316,18 @@
<div id="nsContent" class="contentContainer">
<ul class="contentMenu">
- <li id="nsMainBtn" active onclick="showNsSection('main')">%%ns.menu.main%%</li>
- <li id="nsModsBtn" onclick="showNsSection('mods')">%%ns.menu.mods%%</li>
- <li id="nsReleaseBtn" onclick="showNsSection('release')">%%ns.menu.release%%</li>
+ <li id="nsMainBtn" active onclick="launcher.show_ns('main')">%%ns.menu.main%%</li>
+ <li id="nsModsBtn" onclick="launcher.show_ns('mods')">%%ns.menu.mods%%</li>
+ <li id="nsReleaseBtn" onclick="launcher.show_ns('release')">%%ns.menu.release%%</li>
</ul>
<div class="contentBody">
<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>
@@ -345,11 +345,11 @@
<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>
- <button id="findmod" class="bg-blue2" onclick="Browser.toggle(true)">
+ <button id="findmod" class="bg-blue2" onclick="browser.toggle(true)">
<img src="icons/search.png">
%%gui.mods.find%%
</button>
@@ -367,10 +367,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>
@@ -378,16 +379,6 @@
</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..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 9eb6c76..3aa9c43 100644
--- a/src/app/js/settings.js
+++ b/src/app/js/settings.js
@@ -1,276 +1,410 @@
-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 = {
- default: {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 = {
+ default: {...settings_data},
+
+ 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);
-
- // hide categories that aren't for the current platform
- let for_platform = categories[i].getAttribute("platform");
- if (for_platform && process.platform != for_platform) {
- categories[i].style.display = "none";
- categories[i].setAttribute("perma-hidden", 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
+ }
+ },
+
+ popup: {}
+}
+
+settings.popup.toggle = (state) => {
+ settings.popup.load();
+ options.scrollTo(0, 0);
+
+ popups.set("#options", state);
+}
+
+settings.popup.apply = () => {
+ settings.set(settings.popup.get());
+ ipcRenderer.send("save-settings", settings.popup.get());
+}
- for (let i = 0; i < options.length; i++) {
- // hide options that aren't for the current platform
- let for_platform = options[i].getAttribute("platform");
- if (for_platform && process.platform != for_platform) {
- options[i].style.display = "none";
- options[i].setAttribute("perma-hidden", true);
+settings.popup.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;
}
+ }
+ }
- let optName = options[i].getAttribute("name");
- if (optName == "forcedlang") {
- let div = options[i].querySelector("select");
+ return opts;
+}
- 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.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);
+
+ // hide categories that aren't for the current platform
+ let for_platform = categories[i].getAttribute("platform");
+ if (for_platform && process.platform != for_platform) {
+ categories[i].style.display = "none";
+ categories[i].setAttribute("perma-hidden", true);
+ }
+ }
- if (! lang_file.lang || ! lang_file.lang.title) {
- continue;
- }
+ let options = document.querySelectorAll(".option");
- let title = lang_file.lang.title;
+ for (let i = 0; i < options.length; i++) {
+ // hide options that aren't for the current platform
+ let for_platform = options[i].getAttribute("platform");
+ if (for_platform && process.platform != for_platform) {
+ options[i].style.display = "none";
+ options[i].setAttribute("perma-hidden", true);
+ }
- if (title) {
- div.innerHTML += `<option value="${lang_no_extension}">${title}</option>`
- }
-
+ 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;
}
- div.value = settings.forcedlang;
- continue;
+ let title = lang_file.lang.title;
+
+ if (title) {
+ div.innerHTML += `<option value="${lang_no_extension}">${title}</option>`
+ }
+
}
- if (settings[optName] != undefined) {
- // check if setting has a `<select>`
- let select_el = options[i].querySelector(".actions select");
- if (select_el) {
- // get `<option>` for settings value, if it exists
- let option = select_el.querySelector(
- `option[value="${settings[optName]}"]`
- )
-
- // check if it exists
- if (option) {
- // set the `<select>` to the settings value
- select_el.value = settings[optName];
- } else { // use the default value
- select_el.value = Settings.default[optName];
- }
+ div.value = settings_data.forcedlang;
+ continue;
+ }
- continue;
+ if (settings_data[optName] != undefined) {
+ // check if setting has a `<select>`
+ let select_el = options[i].querySelector(".actions select");
+ if (select_el) {
+ // get `<option>` for settings value, if it exists
+ let option = select_el.querySelector(
+ `option[value="${settings_data[optName]}"]`
+ )
+
+ // check if it exists
+ if (option) {
+ // set the `<select>` to the settings value
+ select_el.value = settings_data[optName];
+ } else { // use the default value
+ select_el.value = settings.default[optName];
}
- 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
- }
+ continue;
+ }
+
+ 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.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) => {
+ // 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")) {
- // set the visibility
- search_els[i].style.display = state;
- }
- }
+ break;
+ }
- // check if `query` is empty, and reset search if so
- if (! query || ! query.trim()) {
- set_all(true);
- } else {
- // hide everything
- set_all(false);
- }
+ // reset visibility
+ closest_el.style.display = null;
- // 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
@@ -370,7 +504,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 501bf42..83ddf6a 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(() => {
@@ -73,3 +77,5 @@ function dismissToast(id) {
ipcRenderer.on("toast", (_, properties) => {
Toast(properties);
})
+
+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(".");
diff --git a/src/app/lang.js b/src/app/lang.js
deleted file mode 100644
index f1c31d3..0000000
--- a/src/app/lang.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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
- html = document.body.innerHTML.split("%%");
-
- for (let i = 0; i < html.length; i++) {
- // Simply checks to make sure it is actually a lang string.
- if (html[i][0] != " " &&
- html[i][html[i].length - 1] != " ") {
- // Replaces it with it's string
- html[i] = lang(html[i]);
- }
- }
-
- // Replaces the original HTML with the translated/replaced HTML
- document.body.innerHTML = html.join("");
-}
diff --git a/src/app/main.js b/src/app/main.js
index f4c1624..3adb96d 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -3,334 +3,16 @@ 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
-//
-// TODO: dont duplicate this, instead just use the one in
-// `src/modules/settings.js` automatically
-var settings = {
- nsargs: "",
- gamepath: "",
- nsupdate: true,
- autolang: true,
- forcedlang: "en",
- autoupdate: true,
- originkill: false,
- zip: "/northstar.zip",
- lang: navigator.language,
-
- linux_launch_cmd_ns: "",
- linux_launch_cmd_vanilla: "",
- linux_launch_method: "steam_auto",
-
- 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({
+ toasts.show({
timeout: 10000,
scheme: "error",
title: lang("gui.toast.title.unknown_error"),
description: lang("gui.toast.desc.unknown_error"),
callback: () => {
- new Toast({
+ toasts.show({
timeout: 15000,
scheme: "error",
title: "",
@@ -339,238 +21,28 @@ 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);
-})
-
-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");
- }
+ console.error(err.stack);
})
-// 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 mods = require("./js/mods");
+const toasts = require("./js/toasts");
+const update = require("./js/update");
+const events = require("./js/events");
+const launch = require("./js/launch");
+const popups = require("./js/popups");
+const browser = require("./js/browser");
+const tooltip = require("./js/tooltip");
+const version = require("./js/version");
+const request = require("./js/request");
+const process = require("./js/process");
+const settings = require("./js/settings");
+const gamepath = require("./js/gamepath");
+const launcher = require("./js/launcher");
+const is_running = require("./js/is_running");
+const set_buttons = require("./js/set_buttons");
+
+require("./js/dom_events");
+require("./js/localize")();
diff --git a/src/modules/kill.js b/src/modules/kill.js
index 5d03218..b0c4f97 100644
--- a/src/modules/kill.js
+++ b/src/modules/kill.js
@@ -1,12 +1,10 @@
const exec = require("child_process").exec;
const ipcMain = require("electron").ipcMain;
-ipcMain.on("kill-game", () => {
- kill.game();
-})
-
-ipcMain.on("kill-origin", () => {
- kill.origin();
+ipcMain.on("kill", (function_name) => {
+ if (typeof kill[function_name] == "function") {
+ kill[function_name]();
+ }
})
// a simple function to kill processes with a certain name
diff --git a/src/modules/launch.js b/src/modules/launch.js
index 544e155..fab80c7 100644
--- a/src/modules/launch.js
+++ b/src/modules/launch.js
@@ -10,9 +10,8 @@ const settings = require("./settings");
console = require("./console");
-ipcMain.on("launch-ns", () => {launch()});
-ipcMain.on("launch-vanilla", () => {
- launch("vanilla");
+ipcMain.on("launch", (_, game_version) => {
+ launch(game_version)
})
// launches the game
diff --git a/src/modules/mods.js b/src/modules/mods.js
index 169e7b1..6214098 100644
--- a/src/modules/mods.js
+++ b/src/modules/mods.js
@@ -350,6 +350,10 @@ mods.modfile.get = (mod) => {
// read enabledmods.json
let data = json(mods.modfile.file);
+ if (! data || typeof data !== "object") {
+ return true;
+ }
+
if (data[mod]) { // enabled
return true;
} else if (data[mod] === false) { // disabled