aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2022-05-02 21:49:20 +0200
committerGitHub <noreply@github.com>2022-05-02 21:49:20 +0200
commitc40e331bdc4b2375d3802a515c9b7a032118dea7 (patch)
tree77c8d84c0fc9d0fbb742cfeeb80efa4762784a9d /src
parent9f2f77558238c28ceb8ff4fca2096602671779e5 (diff)
parent847a2178e7823749e3096daf24dfcd3df8b236cb (diff)
downloadViper-c40e331bdc4b2375d3802a515c9b7a032118dea7.tar.gz
Viper-c40e331bdc4b2375d3802a515c9b7a032118dea7.zip
Merge branch 'main' into enabledmods
Diffstat (limited to 'src')
-rw-r--r--src/app/browser.js95
-rw-r--r--src/app/icons/apply.pngbin0 -> 2555 bytes
-rw-r--r--src/app/icons/check.pngbin0 -> 373 bytes
-rw-r--r--src/app/icons/download.pngbin0 -> 2200 bytes
-rw-r--r--src/app/icons/filter.pngbin0 -> 1074 bytes
-rw-r--r--src/app/icons/settings.pngbin0 -> 2496 bytes
-rw-r--r--src/app/index.html115
-rw-r--r--src/app/launcher.js6
-rw-r--r--src/app/main.css288
-rw-r--r--src/app/main.js78
-rw-r--r--src/app/settings.js137
-rw-r--r--src/app/toast.js2
-rw-r--r--src/cli.js50
-rw-r--r--src/extras/findgame.js36
-rw-r--r--src/index.js35
-rw-r--r--src/lang.js75
-rw-r--r--src/lang/en.json33
-rw-r--r--src/lang/es.json30
-rw-r--r--src/lang/fr.json30
-rw-r--r--src/lang/maintainers.json4
-rw-r--r--src/utils.js167
21 files changed, 1018 insertions, 163 deletions
diff --git a/src/app/browser.js b/src/app/browser.js
index ded12fa..e22ab3c 100644
--- a/src/app/browser.js
+++ b/src/app/browser.js
@@ -4,6 +4,58 @@ var packages = [];
var Browser = {
maxentries: 50,
+ filters: {
+ get: () => {
+ let filtered = [];
+ let unfiltered = [];
+ let checks = browser.querySelectorAll("#filters .check");
+
+ for (let i = 0; i < checks.length; i++) {
+ if (! checks[i].classList.contains("checked")) {
+ filtered.push(checks[i].getAttribute("value"));
+ } else {
+ unfiltered.push(checks[i].getAttribute("value"));
+ }
+ }
+
+ return {
+ filtered,
+ unfiltered
+ };
+ },
+ isfiltered: (categories) => {
+ let filtered = Browser.filters.get().filtered;
+ let unfiltered = Browser.filters.get().unfiltered;
+ let state = false;
+ if (categories.length == 0) {return true}
+ for (let i = 0; i < categories.length; i++) {
+ if (filtered.includes(categories[i])) {
+ state = true;
+ continue
+ } else if (unfiltered.includes(categories[i])) {
+ state = false;
+ continue
+ }
+
+ state = true;
+ }
+
+ return state;
+ },
+ toggle: (state) => {
+ if (state == false) {
+ filters.classList.remove("shown");
+ return
+ }
+
+ filters.classList.toggle("shown");
+ let filterRect = filter.getBoundingClientRect();
+ let spacing = parseInt(getComputedStyle(filters).getPropertyValue("--spacing"));
+
+ filters.style.top = filterRect.bottom - spacing;
+ filters.style.right = filterRect.right - filterRect.left + filterRect.width - (spacing / 2);
+ },
+ },
toggle: (state) => {
if (state) {
browser.scrollTo(0, 0);
@@ -16,6 +68,7 @@ var Browser = {
return
} else if (! state) {
if (state != undefined) {
+ Browser.filters.toggle(false);
overlay.classList.remove("shown")
browser.classList.remove("shown")
return
@@ -38,11 +91,14 @@ var Browser = {
}
for (let i in packages) {
- if (i == Browser.maxentries) {Browser.endoflist();break}
new BrowserElFromObj(packages[i]);
}
},
loading: (string) => {
+ if (Browser.filters.get().unfiltered.length == 0) {
+ string = lang("gui.browser.noresults");
+ }
+
if (string) {
browserEntries.innerHTML = `<div class="loading">${string}</div>`;
}
@@ -52,6 +108,7 @@ var Browser = {
}
},
endoflist: () => {
+ if (browserEntries.querySelector(".message")) {return}
browserEntries.innerHTML += `<div class="message">${lang('gui.browser.endoflist')}</div>`
},
search: (string) => {
@@ -59,26 +116,25 @@ var Browser = {
let res = fuse.search(string);
if (res.length < 1) {
- Browser.loading("No results...")
+ Browser.loading(lang("gui.browser.noresults"))
return
}
for (let i = 0; i < res.length; i++) {
- if (i == Browser.maxentries) {Browser.endoflist();break}
new BrowserElFromObj(res[i].item);
}
},
setbutton: (mod, string) => {
mod = normalize(mod);
- if (document.getElementById(mod)) {
- let elems = document.querySelectorAll(`#${mod}`);
+ if (browserEntries.querySelector(`#mod-${mod}`)) {
+ let elems = browserEntries.querySelectorAll(`.el#mod-${mod}`);
for (let i = 0; i < elems.length; i++) {
elems[i].querySelector(".text button").innerHTML = string;
}
} else {
let make = (str) => {
- if (document.getElementById(str)) {
+ if (browserEntries.querySelector(`#mod-${str}`)) {
return Browser.setbutton(str, string);
} else {
return false;
@@ -104,10 +160,6 @@ var Browser = {
}
}
-document.body.addEventListener("keyup", (e) => {
- if (e.key == "Escape") {Browser.toggle(false)}
-})
-
function BrowserElFromObj(obj) {
let pkg = {...obj, ...obj.versions[0]};
@@ -118,11 +170,17 @@ function BrowserElFromObj(obj) {
url: pkg.package_url,
download: pkg.download_url,
version: pkg.version_number,
+ categories: pkg.categories,
description: pkg.description
})
}
function BrowserEl(properties) {
+ if (Browser.filters.isfiltered(properties.categories)) {return}
+
+ let entries = browser.querySelectorAll(".el").length;
+ if (entries == Browser.maxentries) {Browser.endoflist();return}
+
properties = {
title: "No name",
version: "1.0.0",
@@ -174,15 +232,16 @@ function BrowserEl(properties) {
}
browserEntries.innerHTML += `
- <div class="el" id="${normalize(properties.title)}">
+ <div class="el" id="mod-${normalize(properties.title)}">
<div class="image">
<img src="${properties.image}">
+ <img class="blur" src="${properties.image}">
</div>
<div class="text">
<div class="title">${properties.title}</div>
<div class="description">${properties.description}</div>
- <button onclick="installFromURL('${properties.download}')">${installstr}</button>
- <button onclick="require('electron').shell.openExternal('${properties.url}')">${lang('gui.browser.info')}</button>
+ <button class="install" onclick="installFromURL('${properties.download}')">${installstr}</button>
+ <button class="info" onclick="require('electron').shell.openExternal('${properties.url}')">${lang('gui.browser.info')}</button>
<button class="visual">${properties.version}</button>
<button class="visual">${lang("gui.browser.madeby")} ${properties.author}</button>
</div>
@@ -247,6 +306,7 @@ function normalize(items) {
let searchtimeout;
let searchstr = "";
search.addEventListener("keyup", () => {
+ Browser.filters.toggle(false);
clearTimeout(searchtimeout);
if (searchstr != search.value) {
@@ -262,3 +322,12 @@ search.addEventListener("keyup", () => {
}, 500)
}
})
+
+browser.addEventListener("scroll", () => {
+ Browser.filters.toggle(false);
+})
+
+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 = ''")
+}
diff --git a/src/app/icons/apply.png b/src/app/icons/apply.png
new file mode 100644
index 0000000..915f809
--- /dev/null
+++ b/src/app/icons/apply.png
Binary files differ
diff --git a/src/app/icons/check.png b/src/app/icons/check.png
new file mode 100644
index 0000000..6c7f43f
--- /dev/null
+++ b/src/app/icons/check.png
Binary files differ
diff --git a/src/app/icons/download.png b/src/app/icons/download.png
new file mode 100644
index 0000000..189c50d
--- /dev/null
+++ b/src/app/icons/download.png
Binary files differ
diff --git a/src/app/icons/filter.png b/src/app/icons/filter.png
new file mode 100644
index 0000000..ade45ef
--- /dev/null
+++ b/src/app/icons/filter.png
Binary files differ
diff --git a/src/app/icons/settings.png b/src/app/icons/settings.png
new file mode 100644
index 0000000..3f7715a
--- /dev/null
+++ b/src/app/icons/settings.png
Binary files differ
diff --git a/src/app/index.html b/src/app/index.html
index 8222416..02a150f 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -10,15 +10,121 @@
<div id="toasts"></div>
<div id="winbtns">
+ <div id="settings" onclick="Settings.toggle(true)"></div>
<div id="minimize" onclick="ipcRenderer.send('minimize')"></div>
<div id="close" onclick="ipcRenderer.send('exit')"></div>
</div>
+ <div id="dragUI">
+ <div id="dragitems">
+ <div id="icon"></div>
+ <div id="text">%%gui.mods.dragdrop%%</div>
+ </div>
+ </div>
- <div id="overlay" onclick="Browser.toggle(false)"></div>
- <div id="browser">
- <div id="misc">
+ <div id="overlay" onclick="Browser.toggle(false);Settings.toggle(false)"></div>
+ <div class="popup" id="options">
+ <div class="misc">
+ <div style="width:100%"></div>
+ <button id="apply" onclick="Settings.toggle(false);Settings.apply()">
+ <img src="icons/apply.png">
+ %%gui.settings.save%%
+ </button>
+ <button id="close" onclick="Settings.toggle(false);Settings.load()">
+ <img src="icons/close.png">
+ %%gui.settings.discard%%
+ </button>
+ </div>
+ <div class="options">
+ <h2>%%gui.settings.title.ns%%</h2>
+ <div class="option" name="nsargs">
+ <div class="text">
+ %%gui.settings.nsargs.title%%
+ <div class="desc">
+ %%gui.settings.nsargs.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <input>
+ </div>
+ </div>
+ <h2>%%gui.settings.title.language%%</h2>
+ <div class="option" name="autolang">
+ <div class="text">
+ %%gui.settings.autolang.title%%
+ <div class="desc">
+ %%gui.settings.autolang.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <button class="switch off"></button>
+ </div>
+ </div>
+ <div class="option" name="forcedlang">
+ <div class="text">
+ %%gui.settings.forcedlang.title%%
+ <div class="desc">
+ %%gui.settings.forcedlang.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <select onchange="Settings.switch(document.querySelector(`.option[name='autolang'] button`), false)">
+ <option></option>
+ </select>
+ </div>
+ </div>
+ <h2>%%gui.settings.title.updates%%</h2>
+ <div class="option" name="autoupdate">
+ <div class="text">
+ %%gui.settings.autoupdate.title%%
+ <div class="desc">
+ %%gui.settings.autoupdate.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <button class="switch on"></button>
+ </div>
+ </div>
+ <div class="option" name="nsupdate">
+ <div class="text">
+ %%gui.settings.nsupdate.title%%
+ <div class="desc">
+ %%gui.settings.nsupdate.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <button class="switch on"></button>
+ </div>
+ </div>
+ <div class="option" name="excludes" type="array">
+ <div class="text">
+ %%gui.settings.excludes.title%%
+ <div class="desc">
+ %%gui.settings.excludes.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <input type="text">
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="popup" id="browser">
+ <div class="overlay" id="filters">
+ <div class="checks">
+ <div class="check checked" value="Mods">%%gui.browser.filter.mods%%</div>
+ <div class="check checked" value="Skins">%%gui.browser.filter.skins%%</div>
+ <div class="check checked" value="Client-side">%%gui.browser.filter.client%%</div>
+ <div class="check" value="Server-side">%%gui.browser.filter.server%%</div>
+ </div>
+ </div>
+
+ <div class="misc">
<input id="search" placeholder="%%gui.browser.search%%">
+ <button id="filter" onclick="Browser.filters.toggle()">
+ <img src="icons/filter.png">
+ </button>
<button id="close" onclick="Browser.toggle(false)">
<img src="icons/close.png">
</button>
@@ -94,7 +200,7 @@
<button id="togglemod" onclick="selected().toggle()">%%gui.mods.toggle%%</button>
<button id="toggleall" onclick="selected(true).toggle(true)">%%gui.mods.toggleall%%</button>
<button id="installmod" onclick="installmod()">%%gui.mods.install%%</button>
- <button id="installmod" onclick="Browser.toggle(true)">%%gui.mods.find%%</button>
+ <button id="findmod" onclick="Browser.toggle(true)">%%gui.mods.find%%</button>
</div>
</div>
</div>
@@ -120,6 +226,7 @@
<script src="main.js"></script>
<script src="toast.js"></script>
<script src="browser.js"></script>
+ <script src="settings.js"></script>
<script src="launcher.js"></script>
</body>
</html>
diff --git a/src/app/launcher.js b/src/app/launcher.js
index 60c0d18..7b49dc4 100644
--- a/src/app/launcher.js
+++ b/src/app/launcher.js
@@ -17,7 +17,7 @@ function page(page) {
pages[page].classList.remove("hidden");
btns[page].classList.remove("inactive");
bgHolder.setAttribute("bg", page);
-}; page(0)
+}; page(1)
// Updates the Viper release notes
@@ -25,6 +25,8 @@ ipcRenderer.on("vp-notes", (event, response) => {
let content = "";
for (const release of response) {
+ if (release.prerelease) {continue}
+
content += "# " + release.name + "\n\n"
+ release.body.replaceAll("\r\n", "\n") + "\n\n\n";
}
@@ -42,6 +44,8 @@ ipcRenderer.on("ns-notes", (event, response) => {
let content = "";
for (let release of response) {
+ if (release.prerelease) {continue}
+
content += "# " + release.name + "\n\n"
+ release.body.replaceAll("\r\n", "\nhtmlbreak") + "\n\n\n";
}
diff --git a/src/app/main.css b/src/app/main.css
index a532490..99d806c 100644
--- a/src/app/main.css
+++ b/src/app/main.css
@@ -1,17 +1,23 @@
:root {
- --red: #C7777F;
- --blue: #81A1C1;
- --orange: #D59783;
+ --red: 199, 119, 127;
+ --red2: 181 97 105;
+
+ --blue: 129, 161, 193;
+ --blue2: 139, 143, 185;
+
+ --orange: 213, 151, 131;
+ --orange2: 197 129 107;
+
--padding: 25px;
--bg: rgba(0, 0, 0, 0.5);
--selbg: rgba(80, 80, 80, 0.5);
- --redbg: linear-gradient(45deg, var(--red), #FA4343);
- --bluebg: linear-gradient(45deg, var(--blue), #7380ED);
+ --redbg: linear-gradient(45deg, rgb(var(--red)), #FA4343);
+ --bluebg: linear-gradient(45deg, rgb(var(--blue)), #7380ED);
}
-#browser, #modsdiv {
+.popup, #modsdiv {
outline: 1px solid #444444;
border: 3px solid var(--bg);
}
@@ -27,22 +33,26 @@
::-webkit-scrollbar-thumb {
border-radius: 10px;
- background: var(--red);
+ background: rgb(var(--red));
}
::selection {
color: black;
- background: var(--red);
+ background: rgb(var(--red));
}
body {
margin: 0;
overflow: hidden;
- user-select: none;
}
body, button, input {font-family: "Roboto", sans-serif}
+body, button, img, a {
+ -webkit-user-drag: none;
+ user-select: none;
+}
+
button {outline: none}
b, strong {font-weight: 700}
body, input, button {font-weight: 500}
@@ -62,7 +72,7 @@ button {
cursor: pointer;
}
-#browser {
+.popup {
--spacing: var(--padding);
z-index: 2;
@@ -81,7 +91,7 @@ button {
transition: opacity 0.15s ease-in-out, transform 0.15s ease-in-out;
}
-#browser.shown {
+.popup.shown {
opacity: 1.0;
pointer-events: all;
transform: scale(1.0);
@@ -110,7 +120,7 @@ button {
100% {opacity: 1.0}
}
-#browser .el, #browser #misc, #browser .loading {
+.popup .el, .popup .misc, .popup .loading {
--spacing: calc(var(--padding) / 2);
--height: calc(var(--padding) * 3);
--mischeight: calc(var(--padding) * 1.5);
@@ -125,7 +135,7 @@ button {
transition: 0.15s ease-in-out;
}
-#browser .el, #browser #search, #browser #close {
+.popup .el, .popup #search, .option .actions select, .option .actions input, .popup #close, .popup .misc button {
color: white;
display: flex;
align-items: center;
@@ -137,41 +147,52 @@ button {
width: calc(100% - var(--spacing) * 4);
}
-#browser #misc, #browser #search {
+.popup .misc, .popup #search, .option .actions input {
--height: var(--mischeight);
}
-#browser #misc {
+.popup .misc {
display: flex;
}
-#browser #search {
+.popup #search, .option .actions input, .option .actions select {
border: none;
outline: none;
transition: filter 0.15s ease-in-out;
width: calc(100% - var(--spacing) * 2);
}
-#browser #search:focus {
+.popup #search:focus, .option .actions input:focus, .option .actions button:active {
filter: brightness(1.5);
}
-#browser #close {
+.popup .misc button {
--height: calc(var(--padding) * 1.5);
padding: 0px;
margin-left: 0px;
- width: var(--height);
+ padding: 0px !important;
+ width: var(--height) !important;
}
-#browser #close img {
+.popup .misc button img {
opacity: 0.6;
width: var(--height);
- height: var(--height);
- transform: scale(0.6);
+ transform: scale(0.5);
+ height: var(--height) !important;
+}
+
+#options.popup .misc button {
+ margin-left: 0px;
+ width: auto !important;
+ padding-right: calc(var(--padding) / 2) !important;
}
-#browser .loading {
+.popup .misc button:last-child {
+ margin-left: 0px !important;
+}
+
+.popup .loading {
width: 100%;
color: white;
display: flex;
@@ -181,38 +202,166 @@ button {
height: calc(100% - var(--mischeight) - var(--height));
}
-#browser .message {
+.popup .message {
color: white;
text-align: center;
margin: var(--padding);
width: calc(100% - var(--padding));
}
-#browser .el .image, #browser .el .image img {
+.popup .el .image, .popup .el .image img {
width: var(--height);
height: var(--height);
margin-right: var(--spacing);
border-radius: var(--spacing);
}
-#browser .el .text {
+.popup .el .image img.blur {
+ z-index: -1;
+ position: relative;
+ filter: blur(10px);
+ top: calc(var(--height) * -1 + 5px);
+}
+
+.popup .el .text {
overflow: hidden;
}
-#browser .el .title, #browser .el .description {
+.popup .el .title, .popup .el .description {
height: 1.2em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
-#browser .el .title {font-size: 1.2em}
-#browser .el .description {font-size: 0.8em}
-#browser .el button {
- background: var(--blue);
+.popup .el .title {
+ font-size: 1.2em;
+ font-weight: 700;
+}
+
+.popup .el .description {font-size: 0.8em}
+.popup .el button {
+ background: rgb(var(--blue));
margin-top: var(--spacing);
}
+.popup .el button.info {
+ background: rgb(var(--blue2));
+}
+
+.popup .options {
+ color: white;
+ margin: calc(var(--padding) / 2);
+}
+
+.popup .options .option {
+ width: 100%;
+ display: flex;
+ margin-bottom: var(--padding);
+ justify-content: space-between;
+}
+
+.popup .overlay {
+ z-index: 1;
+ color: white;
+ opacity: 0.0;
+ position: fixed;
+ pointer-events: none;
+ transform: scale(0.9);
+ background: var(--selbg);
+ backdrop-filter: blur(15px);
+ transition: 0.15s ease-in-out;
+ padding: calc(var(--spacing) / 2);
+ border-radius: calc(var(--spacing) / 2);
+}
+
+.popup .overlay.shown {
+ opacity: 1.0;
+ pointer-events: all;
+ transform: scale(1.0);
+}
+
+.checks {
+}
+
+.check {
+ display:flex;
+ cursor: pointer;
+}
+
+.check::before {
+ width: 1em;
+ height: 1em;
+ content: " ";
+ background-size: 75%;
+ filter: brightness(1.3);
+ background-position: center;
+ background-repeat: no-repeat;
+ transition: 0.15s ease-in-out;
+ background-color: var(--selbg);
+ margin-right: calc(var(--spacing) / 3);
+ border-radius: calc(var(--spacing) / 4);
+}
+
+.check.checked::before {
+ background-color: rgb(var(--red));
+ background-image: url(icons/check.png);
+}
+
+.option .text {font-weight: 600}
+.option .text .desc {
+ opacity: 0.8;
+ font-weight: 500;
+ font-size: 0.9em;
+ max-width: 400px;
+ margin-top: calc(var(--padding) / 3);
+}
+
+.option .actions input, .option .actions select {
+ width: 100%;
+ margin: 0px;
+ --spacing: calc(var(--padding) / 3);
+}
+
+.option[type=array] .actions input {
+ word-spacing: 15px;
+ margin-right: 15vw;
+}
+
+.option .actions button {
+ background: var(--selbg);
+}
+
+.switch {
+ width: 50px;
+ height: 25px;
+ border-radius: 50px;
+}
+
+.switch.on {
+ background: rgba(var(--red), 0.2) !important;
+}
+
+.switch::after {
+ left: -5px;
+ width: 15px;
+ height: 15px;
+ content: " ";
+ display: block;
+ background: red;
+ position: relative;
+ border-radius: 50px;
+ background: var(--bg);
+ transition: 0.2s ease-in-out;
+}
+
+.switch.on::after {
+ left: 15px;
+ width: 20px;
+ opacity: 0.5;
+ background: rgb(var(--red));
+}
+
#winbtns {
z-index: 2;
display: flex;
@@ -233,9 +382,10 @@ button {
#winbtns #close {background-image: url("icons/close.png")}
#winbtns #minimize {background-image: url("icons/minimize.png")}
+#winbtns #settings {background-image: url("icons/settings.png")}
#winbtns div:hover {opacity: 1.0}
-#winbtns div:active {transform: scale(0.98)}
+#winbtns div:active {transform: scale(0.95)}
button:hover {filter: brightness(110%)}
button:active {filter: brightness(90%)}
@@ -252,7 +402,7 @@ img {pointer-events: none}
background-size: cover;
background-position: center;
background-repeat: no-repeat;
- transition: background-image 0.5s ease-in-out;
+ transition: background-image 0.1s ease-in-out;
filter: brightness(0.4) blur(2px) grayscale(0.6);
}
@@ -384,7 +534,7 @@ img {pointer-events: none}
text-align: center;
position: relative;
border-radius: 50px;
- background: var(--red);
+ background: rgb(var(--red));
left: calc(50% - 15px);
transition: 0.2s ease-in-out;
}
@@ -430,7 +580,7 @@ img {pointer-events: none}
margin-bottom: 10px;
border-radius: 10px;
background: var(--redbg);
- transition: 0.3s ease-in-out;
+ transition: 0.2s ease-in-out;
filter: drop-shadow(0px 8px 5px rgba(0, 0, 0, 0.1));
}
@@ -449,7 +599,7 @@ img {pointer-events: none}
}
a {
- color: var(--red);
+ color: rgb(var(--red));
text-decoration: none;
transition: filter 0.2s ease-in;
}
@@ -496,12 +646,17 @@ a:hover {
}
.simplebar-scrollbar:before {
- background: var(--red) !important;
+ background: rgb(var(--red)) !important;
}
-#installmod {background: var(--blue)}
-#togglemod, #toggleall {background: var(--orange)}
-#northstar, #removeall, #removemod {background: var(--red)}
+#installmod {background: rgb(var(--blue))}
+#findmod {background: rgb(var(--blue2))}
+
+#togglemod {background: rgb(var(--orange))}
+#toggleall {background: rgb(var(--orange2))}
+
+#removemod {background: rgb(var(--red))}
+#removeall {background: rgb(var(--red2))}
button:disabled {
opacity: 0.5;
pointer-events: none;
@@ -509,6 +664,7 @@ button:disabled {
button.visual {
opacity: 1.0;
+ padding-right: 0px;
pointer-events: none;
background: transparent !important;
}
@@ -612,6 +768,58 @@ code {
font-weight: 600;
}
+#dragUI {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ color: white;
+ opacity: 0.0;
+ position: fixed;
+ z-index: 1000000;
+ pointer-events: none;
+ background: var(--bg);
+ backdrop-filter: blur(15px);
+ transition: 0.1s ease-in-out;
+}
+
+#dragUI.shown {
+ opacity: 1.0;
+ pointer-events: all;
+}
+
+#dragUI #dragitems {
+ --size: 25vw;
+ top: 50%;
+ left: 50%;
+ opacity: 0.6;
+ position: absolute;
+ text-align: center;
+ width: var(--size);
+ height: var(--size);
+ margin-top: calc(var(--size) / 2 * -1);
+ margin-left: calc(var(--size) / 2 * -1);
+}
+
+#dragUI #dragitems #icon {
+ width: 100%;
+ height: 100%;
+ filter: invert(1);
+ transform: scale(0.45);
+ background-size: cover;
+ background-image: url("icons/download.png");
+ transition: 0.1s ease-in-out;
+}
+
+#dragUI.shown #dragitems #icon {
+ transform: scale(0.5);
+}
+
+#dragUI #dragitems #text {
+ top: -5vw;
+ position: relative;
+}
+
/* drag control */
#bgHolder,
diff --git a/src/app/main.js b/src/app/main.js
index 169f86f..95b6f4c 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -8,7 +8,11 @@ let shouldInstallNorthstar = false;
// Base settings
var settings = {
+ nsargs: "",
gamepath: "",
+ nsupdate: true,
+ autolang: true,
+ forcedlang: "en",
autoupdate: true,
zip: "/northstar.zip",
lang: navigator.language,
@@ -23,7 +27,24 @@ ipcRenderer.send("setlang", settings.lang);
// Loads the settings
if (fs.existsSync("viper.json")) {
- settings = {...settings, ...JSON.parse(fs.readFileSync("viper.json", "utf8"))};
+ let conf = fs.readFileSync("viper.json", "utf8");
+ let json = {};
+
+ // Validates viper.json
+ try {
+ json = JSON.parse(conf);
+ }catch (e) {
+ let reset = confirm(lang("general.invalidconfig", navigator.language) + e);
+ if (! reset) {
+ ipcRenderer.send("exit")
+ } else {
+ fs.writeFileSync("viper.json", "{}")
+ ipcRenderer.send("relaunch");
+ }
+
+ }
+
+ settings = {...settings, ...json};
settings.zip = path.join(settings.gamepath + "/northstar.zip");
if (settings.gamepath.length === 0) {
@@ -31,6 +52,11 @@ if (fs.existsSync("viper.json")) {
} else {
setpath(true);
}
+
+ let args = path.join(settings.gamepath, "ns_startup_args.txt");
+ if (fs.existsSync(args)) {
+ settings.nsargs = fs.readFileSync(args, "utf8");
+ }
} else {
setpath();
}
@@ -74,14 +100,22 @@ function setButtons(state) {
}
}
+ disablearray(document.querySelectorAll(".playBtnContainer .playBtn"))
disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button"))
disablearray(document.querySelectorAll("#browser #browserEntries .text button"))
}
+ipcRenderer.on("setbuttons", (event, state) => {setButtons(state)})
+ipcRenderer.on("gamepathlost", (event, state) => {
+ page(0);
+ setButtons(false);
+ alert(lang("gui.gamepath.lost"));
+})
+
// Frontend part of updating Northstar
ipcRenderer.on("ns-update-event", (event, key) => {
document.getElementById("update").innerText = `(${lang(key)})`;
- console.log(key);
+ console.log(lang(key));
switch(key) {
case "cli.update.uptodate.short":
setButtons(true);
@@ -156,12 +190,18 @@ function selected(all) {
}
}
-// Tells the main process to install a mod
+// Tells the main process to install a mod through the file selector
function installmod() {
setButtons(false);
ipcRenderer.send("installmod")
}
+// Tells the main process to directly install a mod from this path
+function installFromPath(path) {
+ setButtons(false);
+ ipcRenderer.send("installfrompath", path)
+}
+
// Tells the main process to install a mod from a URL
function installFromURL(url) {
setButtons(false);
@@ -242,6 +282,38 @@ ipcRenderer.on("wrongpath", () => {
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);
+ }
+})
+
document.body.addEventListener("click", event => {
if (event.target.tagName.toLowerCase() === "a" && event.target.protocol != "file:") {
event.preventDefault();
diff --git a/src/app/settings.js b/src/app/settings.js
new file mode 100644
index 0000000..23b38c9
--- /dev/null
+++ b/src/app/settings.js
@@ -0,0 +1,137 @@
+var Settings = {
+ toggle: (state) => {
+ if (state) {
+ Settings.load();
+ options.scrollTo(0, 0);
+ overlay.classList.add("shown")
+ options.classList.add("shown")
+
+ return
+ } else if (! state) {
+ if (state != undefined) {
+ overlay.classList.remove("shown")
+ options.classList.remove("shown")
+ return
+ }
+ }
+
+ Settings.load();
+ options.scrollTo(0, 0);
+ overlay.classList.toggle("shown")
+ options.classList.toggle("shown")
+ },
+ apply: () => {
+ settings = {...settings, ...Settings.get()};
+ ipcRenderer.send("savesettings", Settings.get());
+ },
+ reloadSwitches: () => {
+ let switches = document.querySelectorAll(".switch");
+
+ for (let i = 0; i < switches.length; i++) {
+ switches[i].setAttribute("onclick", `Settings.switch(${i})`);
+ }
+ },
+ switch: (element, state) => {
+ let switches = document.querySelectorAll(".switch");
+ if (switches[element]) {
+ element = switches[element];
+ }
+
+ let on = () => {
+ element.classList.add("on");
+ element.classList.remove("off");
+ }
+
+ let off = () => {
+ element.classList.add("off");
+ element.classList.remove("on");
+ }
+
+ if (state != undefined) {
+ if (state) {on()} else {off()}
+ } else {
+ if (element.classList.contains("on")) {off()} else {on()}
+ }
+
+ Settings.reloadSwitches();
+ },
+ 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;
+ }
+ }
+ }
+
+ return opts;
+ },
+ load: () => {
+ 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 langs = fs.readdirSync(__dirname + "/../lang");
+ for (let i in langs) {
+ title = JSON.parse(fs.readFileSync(__dirname + `/../lang/${langs[i]}`, "utf8"))["lang.title"];
+ if (title) {
+ div.innerHTML += `<option value="${langs[i].replace(/\..*$/, '')}">${title}</option>`
+ }
+
+ }
+
+ div.value = settings.forcedlang;
+ 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
+
+ }
+ }
+ }
+
+ ipcRenderer.send("can-autoupdate");
+ ipcRenderer.on("cant-autoupdate", () => {
+ document.querySelector(".option[name=autoupdate]").style.display = "none";
+ })
+ }
+}
+
+Settings.reloadSwitches();
+Settings.load();
diff --git a/src/app/toast.js b/src/app/toast.js
index 2a8555e..9cb8996 100644
--- a/src/app/toast.js
+++ b/src/app/toast.js
@@ -11,7 +11,7 @@ function Toast(properties) {
switch(toast.scheme) {
case "error":
toast.fg = "#FFFFFF";
- toast.bg = "var(--red)";
+ toast.bg = "rgb(var(--red))";
break
case "success":
toast.fg = "#FFFFFF";
diff --git a/src/cli.js b/src/cli.js
index cef2295..58385f4 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -34,6 +34,21 @@ function exit(code) {
if (hasArgs()) {process.exit(code)}
}
+// Ensures the gamepath exists, it's called by options that require the
+// gamepath to be able to work.
+function gamepath() {
+ if (fs.existsSync("viper.json")) {
+ gamepath = JSON.parse(fs.readFileSync("viper.json", "utf8")).gamepath;
+
+ if (! fs.existsSync(gamepath)) {
+ console.error(`error: ${lang("cli.gamepath.lost")}`);
+ exit(1);
+ } else {
+ return true;
+ }
+ }
+}
+
// General CLI initialization
//
// A lot of the CLI is handled through events sent back to the main
@@ -43,27 +58,28 @@ async function init() {
// --help menu/argument
if (cli.hasSwitch("help")) {
console.log(`options:
- --help ${lang("cli.help.help")}
- --debug ${lang("cli.help.debug")}
- --version ${lang("cli.help.version")}
+ --help ${lang("cli.help.help")}
+ --debug ${lang("cli.help.debug")}
+ --version ${lang("cli.help.version")}
- --cli ${lang("cli.help.cli")}
- --update ${lang("cli.help.update")}
- --updatevp ${lang("cli.help.updatevp")}
- --setpath ${lang("cli.help.setpath")}
+ --cli ${lang("cli.help.cli")}
+ --update ${lang("cli.help.update")}
+ --updatevp ${lang("cli.help.updatevp")}
+ --setpath ${lang("cli.help.setpath")}
+ --no-vp-updates ${lang("cli.help.novpupdates")}
- --installmod ${lang("cli.help.installmod")}
- --removemod ${lang("cli.help.removemod")}
- --togglemod ${lang("cli.help.togglemod")}`)
+ --installmod ${lang("cli.help.installmod")}
+ --removemod ${lang("cli.help.removemod")}
+ --togglemod ${lang("cli.help.togglemod")}`)
// In the future --setpath should be able to understand
// relative paths, instead of just absolute ones.
exit();
}
// --update
- if (cli.hasSwitch("update")) {ipcMain.emit("update")}
+ if (gamepath() && cli.hasSwitch("update")) {ipcMain.emit("update")}
// --version
- if (cli.hasSwitch("version")) {ipcMain.emit("versioncli")}
+ if (gamepath() && cli.hasSwitch("version")) {ipcMain.emit("versioncli")}
// --setpath
if (cli.hasSwitch("setpath")) {
@@ -77,7 +93,7 @@ async function init() {
}
// --launch
- if (cli.hasSwitch("launch")) {
+ if (gamepath() && cli.hasSwitch("launch")) {
switch(cli.getSwitchValue("launch")) {
case "vanilla":
ipcMain.emit("launchVanilla");
@@ -89,12 +105,12 @@ async function init() {
}
// Mod related args, --installmod, --removemod, --togglemod
- if (cli.hasSwitch("installmod")) {ipcMain.emit("installmod")}
- if (cli.hasSwitch("removemod")) {ipcMain.emit("removemod", "", cli.getSwitchValue("removemod"))}
- if (cli.hasSwitch("togglemod")) {ipcMain.emit("togglemod", "", cli.getSwitchValue("togglemod"))}
+ if (gamepath() && cli.hasSwitch("installmod")) {ipcMain.emit("installmod")}
+ if (gamepath() && cli.hasSwitch("removemod")) {ipcMain.emit("removemod", "", cli.getSwitchValue("removemod"))}
+ if (gamepath() && cli.hasSwitch("togglemod")) {ipcMain.emit("togglemod", "", cli.getSwitchValue("togglemod"))}
// Prints out the list of mods
- if (cli.hasSwitch("mods")) {ipcMain.emit("getmods")}
+ if (gamepath() && cli.hasSwitch("mods")) {ipcMain.emit("getmods")}
}
module.exports = {
diff --git a/src/extras/findgame.js b/src/extras/findgame.js
index 42c9b85..3beca23 100644
--- a/src/extras/findgame.js
+++ b/src/extras/findgame.js
@@ -31,32 +31,50 @@ module.exports = async () => {
// Parse read_data
data = vdf.parse(data);
+ let values = Object.values(data["libraryfolders"]);
+ if (typeof values[values.length - 1] != "object") {
+ values.pop(1);
+ }
+
// `.length - 1` This is because the last value is `contentstatsid`
- for (let pathIterator = 0; pathIterator < Object.values(data["libraryfolders"]).length - 1; pathIterator++) {
- let data_array = Object.values(data["libraryfolders"][pathIterator])
+ for (let i = 0; i < values.length; i++) {
+ let data_array = Object.values(values[i])
if (fs.existsSync(data_array[0] + "/steamapps/common/Titanfall2/Titanfall2.exe")) {
+ console.log("Found game in:", data_array[0])
return data_array[0] + "/steamapps/common/Titanfall2";
+ } else {
+ console.log("Game not in:", data_array[0])
}
}
}
- let folder = null;
+ let folders = [];
switch (process.platform) {
case "win32":
- folder = "C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf";
+ folders = ["C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf"];
break
case "linux":
case "openbsd":
case "freebsd":
- folder = path.join(app.getPath("home"), "/.steam/steam/steamapps/libraryfolders.vdf");
+ let home = app.getPath("home");
+ folders = [
+ path.join(home, "/.steam/steam/steamapps/libraryfolders.vdf"),
+ path.join(home, ".var/app/com.valvesoftware.Steam/.steam/steam/steamapps/libraryfolders.vdf"),
+ path.join(home, ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/libraryfolders.vdf")
+ ]
break
}
- if (fs.existsSync(folder) && folder) {
- let data = fs.readFileSync(folder)
- let read_vdf = readvdf(data.toString())
- if (read_vdf ) {return read_vdf}
+ if (folders.length > 0) {
+ for (let i = 0; i < folders.length; i++) {
+ if (! fs.existsSync(folders[i])) {continue}
+ console.log("Searching VDF file at:", folders[i])
+
+ let data = fs.readFileSync(folders[i])
+ let read_vdf = readvdf(data.toString())
+ if (read_vdf) {return read_vdf}
+ }
}
if (gamepath) {
diff --git a/src/index.js b/src/index.js
index a06cf69..6c3c79c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -41,8 +41,11 @@ function start() {
win.removeMenu();
win.loadFile(__dirname + "/app/index.html");
+
ipcMain.on("exit", () => {process.exit(0)})
ipcMain.on("minimize", () => {win.minimize()})
+ ipcMain.on("relaunch", () => {app.relaunch();app.exit()})
+ ipcMain.on("installfrompath", (event, path) => {utils.mods.install(path)})
ipcMain.on("installfromurl", (event, url) => {utils.mods.installFromURL(url)})
ipcMain.on("winLog", (event, ...args) => {win.webContents.send("log", ...args)});
ipcMain.on("winAlert", (event, ...args) => {win.webContents.send("alert", ...args)});
@@ -52,11 +55,35 @@ function start() {
ipcMain.on("installedmod", (event, modname) => {win.webContents.send("installedmod", modname)});
ipcMain.on("guigetmods", (event, ...args) => {win.webContents.send("mods", utils.mods.list())});
+ let gamepathlost = false;
+ ipcMain.on("gamepathlost", (event, ...args) => {
+ if (! gamepathlost) {
+ gamepathlost = true;
+ win.webContents.send("gamepathlost");
+ }
+ });
+
+ ipcMain.on("savesettings", (event, obj) => {utils.saveSettings(obj)})
+
+ ipcMain.on("can-autoupdate", (event) => {
+ if (! require("electron-updater").autoUpdater.isUpdaterActive() || cli.hasParam("no-vp-updates")) {
+ win.webContents.send("cant-autoupdate")
+ }
+ })
+
win.webContents.on("dom-ready", () => {
win.webContents.send("mods", utils.mods.list());
});
- if (utils.settings.autoupdate) {utils.updatevp(false)}
+ if (utils.settings.autoupdate) {
+ if (cli.hasParam("no-vp-updates")) {
+ utils.handleNorthstarUpdating();
+ } else {
+ utils.updatevp(false)
+ }
+ } else {
+ utils.handleNorthstarUpdating();
+ }
autoUpdater.on("update-downloaded", () => {
win.webContents.send("updateavailable")
@@ -76,7 +103,11 @@ ipcMain.on("installmod", () => {
utils.mods.install(cli.param("installmod"))
} else {
dialog.showOpenDialog({properties: ["openFile"]}).then(res => {
- utils.mods.install(res.filePaths[0]);
+ if (res.filePaths.length != 0) {
+ utils.mods.install(res.filePaths[0]);
+ } else {
+ win.webContents.send("setbuttons", true);
+ }
}).catch(err => {console.error(err)})
}
})
diff --git a/src/lang.js b/src/lang.js
index b3cbb43..f2fab3a 100644
--- a/src/lang.js
+++ b/src/lang.js
@@ -1,35 +1,62 @@
const fs = require("fs");
-var lang = "en"; // Default language
-
-// Loads fallback/default language strings
-var langDef = JSON.parse(fs.readFileSync(__dirname + `/lang/en.json`, "utf8"));
-
-// If settins are set it'll try to set the language to that instead of
-// the default, however if it can't find it, it'll still fallback to the
-// default language. This might happen as the default language is
-// retrieved from the renderer's navigator.language, which may have
-// languages we don't support yet.
-if (fs.existsSync("viper.json")) {
- lang = JSON.parse(fs.readFileSync("viper.json", "utf8")).lang;
- if (! lang) {lang = "en"} // Uses fallback, if language isn't set
- if (! fs.existsSync(__dirname + `/lang/${lang}.json`)) {
- if (fs.existsSync(__dirname + `/lang/${lang.replace(/-.*$/, "")}.json`)) {
- lang = lang.replace(/-.*$/, "");
- } else {
- lang = "en"; // Uses fallback if language doesn't exist
+const enLang = JSON.parse(fs.readFileSync(__dirname + `/lang/en.json`, "utf8"));
+let lang = "";
+var langObj = {};
+
+
+function _loadTranslation(forcedlang) {
+ if (fs.existsSync("viper.json")) {
+ // Validate viper.json
+ let opts = {
+ lang: "en",
+ autolang: true,
}
+
+ try {
+ opts = JSON.parse(fs.readFileSync("viper.json", "utf8"));
+ }catch (e) {}
+
+ lang = opts.lang;
+
+ if (! lang) {lang = "en"}
+
+ if (forcedlang) {lang = forcedlang}
+
+ if (opts.autolang == false) {
+ lang = opts.forcedlang;
+ if (! lang) {lang = "en"}
+ }
+
+ if (! fs.existsSync(__dirname + `/lang/${lang}.json`)) {
+ if (fs.existsSync(__dirname + `/lang/${lang.replace(/-.*$/, "")}.json`)) {
+ lang = lang.replace(/-.*$/, "");
+ } else {
+ lang = "en";
+ }
+ }
+ } else {
+ lang = "en";
}
+
+ langObj = JSON.parse(fs.readFileSync(__dirname + `/lang/${lang}.json`, "utf8"));
}
-var langObj = JSON.parse(fs.readFileSync(__dirname + `/lang/${lang}.json`, "utf8"));
-module.exports = (string) => {
- if (langObj[string]) { // Returns string from language
+module.exports = (string, forcedlang) => {
+ if (lang === "") {
+ _loadTranslation();
+ }
+
+ if (forcedlang) {
+ _loadTranslation(forcedlang);
+ }
+
+ if (langObj[string]) {
return langObj[string];
- } else { // If string doesn't exist
- if (langDef[string]) { // Retrieves from default lang instead
- return langDef[string];
+ } else {
+ if (enLang[string]) {
+ return enLang[string];
} else {
// If it's not in the default lang either, it returns the
// string, this is absolute fallback.
diff --git a/src/lang/en.json b/src/lang/en.json
index 27630ec..ee932a2 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1,4 +1,6 @@
{
+ "lang.title": "English",
+
"cli.help.help": "shows this help message",
"cli.help.debug": "opens the dev/debug tools",
"cli.help.version": "outputs version info",
@@ -6,6 +8,7 @@
"cli.help.update": "updates Northstar from your set game path",
"cli.help.setpath": "sets your game path",
"cli.help.updatevp": "updates Viper itself, if supported.",
+ "cli.help.novpupdates": "overwrites viper.json and disables Viper updates",
"cli.help.installmod": "installs a mod, folder or zip",
"cli.help.removemod": "removes a mod",
"cli.help.togglemod": "toggles a mod",
@@ -28,6 +31,8 @@
"cli.launch.linuxerror": "Launching the game is not currently supported on Linux",
+ "cli.gamepath.lost": "Gamepath not found, make sure it's mounted!",
+
"cli.mods.failed": "Failed to install mod!",
"cli.mods.removed": "Successfully removed mod!",
"cli.mods.toggled": "Successfully toggled mod",
@@ -62,6 +67,7 @@
"gui.mods.extracting": "Extracting mod...",
"gui.mods.installing": "Installing mod...",
"gui.mods.installedmod": "Installed mod!",
+ "gui.mods.dragdrop": "Drag and drop a mod to install",
"gui.browser.info": "Info",
"gui.browser.madeby": "by",
@@ -71,6 +77,29 @@
"gui.browser.reinstall": "Re-Install",
"gui.browser.loading": "Loading mods...",
"gui.browser.endoflist": "Maximum packages has been loaded.<br>Use the search for finding other packages!",
+ "gui.browser.noresults": "No results...",
+ "gui.browser.filter.mods": "Mods",
+ "gui.browser.filter.skins": "Skins",
+ "gui.browser.filter.client": "Client-side",
+ "gui.browser.filter.server": "Server-side",
+
+ "gui.settings.save": "Save",
+ "gui.settings.discard": "Discard",
+ "gui.settings.title.ns": "Northstar",
+ "gui.settings.title.language": "Language",
+ "gui.settings.title.updates": "Updates",
+ "gui.settings.nsargs.title": "Launch options",
+ "gui.settings.nsargs.desc": "Here you can add launch options for Northstar/Titanfall.",
+ "gui.settings.autolang.title": "Auto-Detect Language",
+ "gui.settings.autolang.desc": "When enabled, Viper tries to automatically detect your system language, when disabled you can manually change the language below.",
+ "gui.settings.forcedlang.title": "Language",
+ "gui.settings.forcedlang.desc": "When \"Auto-Detect Language\" is disabled, this will decide the language. Requires a restart to take effect.",
+ "gui.settings.autoupdate.title": "Viper Auto-Updates",
+ "gui.settings.autoupdate.desc": "Viper will automatically keep itself up-to-date.",
+ "gui.settings.nsupdate.title": "Northstar Auto-Updates",
+ "gui.settings.nsupdate.desc": "Viper will automatically keep Northstar up-to-date, however it can still manually be updated through the Northstar page.",
+ "gui.settings.excludes.title": "Retain files on update",
+ "gui.settings.excludes.desc": "When Northstar is updated, files specified here will not be overwritten by files from the new Northstar update, unless you know what you're changing, you should probably not change anything here. Each file is separated with a space.",
"gui.update.downloading": "Downloading...",
"gui.update.extracting": "Extracting update...",
@@ -89,6 +118,7 @@
"gui.selectpath": "Please select the path!",
"gui.gamepath.must": "The game path must be set to start Viper.",
"gui.gamepath.wrong": "This folder is not a valid game path.",
+ "gui.gamepath.lost": "Gamepath no longer exists/can't be found!\n\nMake sure your drive is mounted properly, or if you moved your game location that you update the game path.\n\nViper may not work properly until next restart!",
"gui.toast.title.installed": "Mod installed!",
"gui.toast.title.failed": "Failed to install",
@@ -115,5 +145,6 @@
"general.mods.installed": "Installed mods:",
"general.missingpath": "Game location could not be found automatically! Please select it manually!",
"general.notinstalled": "Northstar is not installed!",
- "general.launching": "Launching"
+ "general.launching": "Launching",
+ "general.invalidconfig": "Your config file is improperly formatted, if it's been manually edited, please validate that everything is typed correctly.\n\nIf you did not manually edit the config file, it is recommended to simply reset the config.\n\nTo reset your config file simply click \"Ok\" below.\n\nMore details:\n"
}
diff --git a/src/lang/es.json b/src/lang/es.json
index 8beabef..e3ef4f0 100644
--- a/src/lang/es.json
+++ b/src/lang/es.json
@@ -1,4 +1,6 @@
{
+ "lang.title": "Spanish - Español",
+
"cli.help.help": "muestra este mensaje de ayuda",
"cli.help.debug": "habre las herramientas de desarrollador/depuración ",
"cli.help.version": "muestra la información de la versión",
@@ -6,11 +8,14 @@
"cli.help.update": "actualiza Northstar desde la ruta de juego establecida",
"cli.help.setpath": "establece la ruta del juego",
"cli.help.updatevp": "actualiza Viper si es soportado",
+ "cli.help.novpupdates": "sobrescribe viper.json y deshabilita las actualizaciones de Viper",
"cli.help.installmod": "instala una modificación, desde una carpeta o zip",
"cli.help.removemod": "remueve una modificación",
"cli.help.togglemod": "alterna el estado de la modificación",
"cli.setpath.noarg": "No se ha proporcionado ningún argumento para --setpath",
+
+ "cli.gamepath.lost": "El directorio del juego no fue encontrado, ¡asegúrate de que esté montado!",
"cli.update.current": "Versión actual:",
"cli.update.downloading": "Descargando...",
@@ -62,6 +67,7 @@
"gui.mods.extracting": "Extrayendo modificación...",
"gui.mods.installing": "Instalando modificación...",
"gui.mods.installedmod": "¡Modificación instalada!",
+ "gui.mods.dragdrop": "Arrastra y suelta una modificación para instalarla",
"gui.browser.info": "Información",
"gui.browser.madeby": "por",
@@ -71,6 +77,29 @@
"gui.browser.reinstall": "Re-Instalar",
"gui.browser.loading": "Cargando modificaciones...",
"gui.browser.endoflist": "Se ha cargado el máximo de paquetes.<br>¡Usa la búsqueda para encontrar otros paquetes!",
+ "gui.browser.noresults": "Sin resultados...",
+ "gui.browser.filter.mods": "Modificaciones",
+ "gui.browser.filter.skins": "Skins",
+ "gui.browser.filter.client": "Del lado del cliente",
+ "gui.browser.filter.server": "Del lado del servidor",
+
+ "gui.settings.save": "Guardar",
+ "gui.settings.discard": "Descartar",
+ "gui.settings.title.ns": "Northstar",
+ "gui.settings.title.updates": "Actualizaciones",
+ "gui.settings.nsargs.title": "Opciones de lanzamiento",
+ "gui.settings.nsargs.desc": "Aqui puedes añadir opciones de lanzamiento para Northstar/Titanfall.",
+ "gui.settings.autoupdate.title": "Actualizaciones automáticas de Viper",
+ "gui.settings.autoupdate.desc": "Viper se mantendrá automáticamente actualizado.",
+ "gui.settings.nsupdate.title": "Actualizaciones automáticas de Northstar",
+ "gui.settings.nsupdate.desc": "Viper mantendrá Northstar actualizado automáticamente, sin embargo, todavía se puede actualizar manualmente a través de la sección de Northstar.",
+ "gui.settings.excludes.title": "Conservar archivos en la actualización",
+ "gui.settings.excludes.desc": "Cuando se actualice Northstar, los archivos especificados aquí no se sobrescribirán con archivos de la nueva actualización de Northstar. A menos que sepa lo que está cambiando, probablemente no debería cambiar nada aquí. Cada archivo se debe separar con un espacio.",
+ "gui.settings.title.language": "Idioma",
+ "gui.settings.autolang.title": "Detectar automáticamente el idioma",
+ "gui.settings.autolang.desc": "Cuando está habilitado, Viper intenta detectar automáticamente el idioma de su sistema, cuando está deshabilitado, puede cambiar manualmente el idioma a continuación.",
+ "gui.settings.forcedlang.title": "Idioma",
+ "gui.settings.forcedlang.desc": "Cuando \"Detectar automáticamente el idioma\" está deshabilitado, ésta opción decidirá el lenguaje. Se necesita reiniciar para que surja efecto.",
"gui.update.downloading": "Descargando...",
"gui.update.extracting": "Extrayendo actualización...",
@@ -89,6 +118,7 @@
"gui.selectpath": "Por favor, ¡elija la ruta del juego!",
"gui.gamepath.must": "La ruta del juego debe establecerse para ejecutar Viper.",
"gui.gamepath.wrong": "Esta carpeta no es una ruta válida para el juego.",
+ "gui.gamepath.lost": "¡El directorio del juego ya no existe/no se puede encontrar!\n\nAsegúrate de que tu unidad esté instalada correctamente o, si cambiaste la ubicación del juego, actualiza la ruta del juego.\n\nViper puede no funcionar correctamente hasta el próximo reinicio!",
"gui.toast.title.installed": "¡Modificación instalada!",
"gui.toast.title.failed": "¡Falló al instalar!",
diff --git a/src/lang/fr.json b/src/lang/fr.json
index 3bdeec6..81c5ada 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -1,4 +1,6 @@
{
+ "lang.title": "French - Français",
+
"cli.help.help": "affiche ce message d'aide",
"cli.help.debug": "affiche les outils de développement",
"cli.help.version": "retourne des informations sur la version du logiciel",
@@ -6,6 +8,7 @@
"cli.help.update": "met à jour Northstar sur le chemin du jeu précisé",
"cli.help.setpath": "enregistre le chemin du client de jeu",
"cli.help.updatevp": "met à jour le client Viper, si le format actuel le permet.",
+ "cli.help.novpupdates": "écrase viper.json et désactive les mises à jour de Viper",
"cli.help.installmod": "Installe un mod, dossier ou zip",
"cli.help.removemod": "Supprime un mod",
"cli.help.togglemod": "Active/désactive un mod",
@@ -28,6 +31,8 @@
"cli.launch.linuxerror": "Le support du jeu sur Linux n'est pas encore implémenté.",
+ "cli.gamepath.lost": "Dossier de jeu non trouvé, veuillez vérifier qu'il est bien accessible !",
+
"cli.mods.failed": "L'installation du mod a échoué.",
"cli.mods.removed": "Le mod a bien été supprimé.",
"cli.mods.toggled": "Le mod a bien été activé/désactivé.",
@@ -62,6 +67,7 @@
"gui.mods.extracting": "Extraction du mod...",
"gui.mods.installing": "Installation du mod...",
"gui.mods.installedmod": "Mod installé !",
+ "gui.mods.dragdrop": "Glissez/déposez un mod pour l'installer",
"gui.browser.info": "Info",
"gui.browser.madeby": "par",
@@ -71,6 +77,29 @@
"gui.browser.reinstall": "Réinstaller",
"gui.browser.loading": "Chargement des mods...",
"gui.browser.endoflist": "Fin de la liste de mods.<br>Utilisez la barre de recherche pour en trouver davantage !",
+ "gui.browser.noresults": "Pas de résultat",
+ "gui.browser.filter.mods": "Mods",
+ "gui.browser.filter.skins": "Skins",
+ "gui.browser.filter.client": "Côté client",
+ "gui.browser.filter.server": "Côté serveur",
+
+ "gui.settings.save": "Sauvegarder",
+ "gui.settings.discard": "Annuler",
+ "gui.settings.title.ns": "Northstar",
+ "gui.settings.title.language": "Langue",
+ "gui.settings.title.updates": "Mises à jour",
+ "gui.settings.nsargs.title": "Options de lancement",
+ "gui.settings.nsargs.desc": "Vous pouvez ajouter ici des options de démarrage pour Northstar/Titanfall.",
+ "gui.settings.autolang.title": "Auto-détection de la langue",
+ "gui.settings.autolang.desc": "Lorsque activée, Viper essaie de déterminer automatiquement la langue de votre système ; désactiver cette option vous permet de sélectionner manuellement la langue utilisée.",
+ "gui.settings.forcedlang.title": "Langue",
+ "gui.settings.forcedlang.desc": "Lorsque \"Auto-détection de la langue\" est désactivée, cette option permet de sélectionner la langue (requiert un redémarrage).",
+ "gui.settings.autoupdate.title": "Mises à jour pour Viper",
+ "gui.settings.autoupdate.desc": "Viper se tient automatiquement à jour.",
+ "gui.settings.nsupdate.title": "Mises à jour pour Northstar",
+ "gui.settings.nsupdate.desc": "Viper tient automatiquement Northstar à jour (n'empêche pas de le mettre à jour manuellement via sa page dédiée).",
+ "gui.settings.excludes.title": "Fichiers à conserver",
+ "gui.settings.excludes.desc": "Lorsque Northstar est mis à jour, ces fichiers ne seront pas écrasés par ceux provenant de la mise à jour; les noms de fichiers sont séparés par un espace.",
"gui.update.downloading": "Téléchargement de la mise à jour...",
"gui.update.extracting": "Extraction des fichiers...",
@@ -89,6 +118,7 @@
"gui.selectpath": "Veuillez sélectionner le dossier où se trouve le client Titanfall 2.",
"gui.gamepath.must": "Vous devez sélectionner le chemin du dossier du jeu Titanfall 2 pour pouvoir lancer Viper.",
"gui.gamepath.wrong": "Ce dossier ne contient pas le jeu Titanfall 2, et n'est donc pas valide.",
+ "gui.gamepath.lost": "Le chemin du jeu ne peut être trouvé / n'existe plus !\n\nVeuillez vérifier que votre disqué est correctement monté, ou, si vous avez déplacé votre jeu, que vous avez mis à jour le chemin du dossier.\n\nViper ne fonctionnera pas correctement jusqu'au prochain redémarrage.",
"gui.toast.title.installed": "Mod installé !",
"gui.toast.title.failed": "L'installation a échoué",
diff --git a/src/lang/maintainers.json b/src/lang/maintainers.json
index 094814a..96ad685 100644
--- a/src/lang/maintainers.json
+++ b/src/lang/maintainers.json
@@ -3,7 +3,9 @@
"list": {
"es": [
- "https://github.com/AA-Delta"
+ "https://github.com/AA-Delta",
+ "https://twitter.com/AADelta1",
+ "https://www.facebook.com/Juanesgtgt2/"
],
"fr": [
diff --git a/src/utils.js b/src/utils.js
index a0c88ef..ee101de 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -18,25 +18,56 @@ const { https } = require("follow-redirects");
process.chdir(app.getPath("appData"));
+var invalidsettings = false;
+
// Base settings
var settings = {
gamepath: "",
lang: "en-US",
+ nsupdate: true,
+ autolang: true,
+ forcedlang: "en",
autoupdate: true,
+ nsargs: "-multiple",
zip: "/northstar.zip",
// These files won't be overwritten when installing/updating
- // Northstar, useful for config file.
+ // Northstar, useful for config files
excludes: [
"ns_startup_args.txt",
"ns_startup_args_dedi.txt"
]
}
+// Logs into the dev tools of the renderer
+function winLog(msg) {
+ ipcMain.emit("winLog", msg, msg);
+}
+
+// Sends an alert to the renderer
+function winAlert(msg) {
+ ipcMain.emit("winAlert", msg, msg);
+}
+
// Creates the settings file with the base settings if it doesn't exist.
if (fs.existsSync("viper.json")) {
- settings = {...settings, ...JSON.parse(fs.readFileSync("viper.json", "utf8"))};
+ let conf = fs.readFileSync("viper.json", "utf8");
+ let json = "{}";
+
+ // Validates viper.json
+ try {
+ json = JSON.parse(conf);
+ }catch (e) {
+ invalidsettings = true;
+ }
+
+ settings = {...settings, ...json};
settings.zip = path.join(settings.gamepath + "/northstar.zip");
+
+ let args = path.join(settings.gamepath, "ns_startup_args.txt");
+ if (fs.existsSync(args)) {
+ settings.nsargs = fs.readFileSync(args, "utf8");
+ }
} else {
console.log(lang("general.missingpath"));
}
@@ -73,9 +104,9 @@ async function isGameRunning() {
//
// It uses isGameRunning() to ensure it doesn't run while the game is
// running, as that may have all kinds of issues.
-northstar_auto_updates: {
- if (!settings.autoupdate || !fs.existsSync("viper.json") || settings.gamepath.length === 0) {
- break northstar_auto_updates;
+function handleNorthstarUpdating() {
+ if (! settings.nsupdate || ! fs.existsSync("viper.json") || settings.gamepath.length === 0) {
+ return;
}
async function _checkForUpdates() {
@@ -170,8 +201,19 @@ async function setpath(win, forcedialog) {
// As to not have to do the same one liner a million times, this
// function exists, as the name suggests, it simply writes the current
// settings to the disk.
-function saveSettings() {
- fs.writeFileSync(app.getPath("appData") + "/viper.json", JSON.stringify(settings));
+//
+// You can also pass a settings object to the function and it'll try and
+// merge it together with the already existing settings
+function saveSettings(obj = {}) {
+ if (invalidsettings) {return false}
+
+ settings = {...settings, ...obj};
+
+ if (fs.existsSync(settings.gamepath)) {
+ fs.writeFileSync(path.join(settings.gamepath, "ns_startup_args.txt"), settings.nsargs);
+ }
+
+ fs.writeFileSync(app.getPath("appData") + "/viper.json", JSON.stringify({...settings, ...obj}));
}
// Returns the current Northstar version
@@ -182,7 +224,10 @@ function getNSVersion() {
if (fs.existsSync(versionFilePath)) {
return fs.readFileSync(versionFilePath, "utf8");
} else {
- fs.writeFileSync(versionFilePath, "unknown");
+ if (gamepathExists()) {
+ fs.writeFileSync(versionFilePath, "unknown");
+ }
+
return "unknown";
}
}
@@ -225,6 +270,8 @@ restoreExcludedFiles();
// <file>.excluded, then rename them back after the extraction. The
// unzip module does not support excluding files directly.
async function update() {
+ if (! gamepathExists()) {return}
+
ipcMain.emit("ns-update-event", "cli.update.checking");
console.log(lang("cli.update.checking"));
var version = getNSVersion();
@@ -237,6 +284,7 @@ async function update() {
console.log(lang("cli.update.uptodate"), version);
winLog(lang("gui.update.uptodate"));
+ cli.exit();
return;
} else {
if (version != "unknown") {
@@ -299,6 +347,13 @@ async function update() {
function updatevp(autoinstall) {
const { autoUpdater } = require("electron-updater");
+ if (! autoUpdater.isUpdaterActive()) {
+ if (settings.nsupdate) {
+ handleNorthstarUpdating();
+ }
+ return cli.exit();
+ }
+
if (autoinstall) {
autoUpdater.on("update-downloaded", (info) => {
autoUpdater.quitAndInstall();
@@ -306,7 +361,14 @@ function updatevp(autoinstall) {
}
autoUpdater.on("error", (info) => {cli.exit(1)});
- autoUpdater.on("update-not-available", (info) => {cli.exit()});
+ autoUpdater.on("update-not-available", (info) => {
+ // only check for NS updates if Viper itself has no updates and
+ // if NS auto updates is enabled.
+ if (settings.nsupdate || cli.hasArgs()) {
+ handleNorthstarUpdating();
+ }
+ cli.exit();
+ });
autoUpdater.checkForUpdatesAndNotify();
}
@@ -334,14 +396,10 @@ function launch(version) {
}
}
-// Logs into the dev tools of the renderer
-function winLog(msg) {
- ipcMain.emit("winLog", msg, msg);
-}
-
-// Sends an alert to the renderer
-function winAlert(msg) {
- ipcMain.emit("winAlert", msg, msg);
+// Returns true/false depending on if the gamepath currently exists/is
+// mounted, used to avoid issues...
+function gamepathExists() {
+ return fs.existsSync(settings.gamepath);
}
// Used to manage mods.
@@ -594,40 +652,44 @@ const mods = {
}
try {
- fs.createReadStream(mod).pipe(unzip.Extract({path: cache}))
- .on("finish", () => {
- setTimeout(() => {
- let manifest = path.join(cache, "manifest.json");
- if (fs.existsSync(manifest)) {
- files = fs.readdirSync(path.join(cache, "mods"));
- if (fs.existsSync(path.join(cache, "mods/mod.json"))) {
- if (mods.install(path.join(cache, "mods"), require(manifest).name, manifest, true)) {
- return true;
- }
- } else {
- for (let i = 0; i < files.length; i++) {
- let mod = path.join(cache, "mods", files[i]);
- if (fs.statSync(mod).isDirectory()) {
- setTimeout(() => {
- if (mods.install(mod, false, manifest)) {return true};
- }, 1000)
+ if (mod.replace(/.*\./, "").toLowerCase() == "zip") {
+ fs.createReadStream(mod).pipe(unzip.Extract({path: cache}))
+ .on("finish", () => {
+ setTimeout(() => {
+ let manifest = path.join(cache, "manifest.json");
+ if (fs.existsSync(manifest)) {
+ files = fs.readdirSync(path.join(cache, "mods"));
+ if (fs.existsSync(path.join(cache, "mods/mod.json"))) {
+ if (mods.install(path.join(cache, "mods"), require(manifest).name, manifest, true)) {
+ return true;
+ }
+ } else {
+ for (let i = 0; i < files.length; i++) {
+ let mod = path.join(cache, "mods", files[i]);
+ if (fs.statSync(mod).isDirectory()) {
+ setTimeout(() => {
+ if (mods.install(mod, false, manifest)) {return true};
+ }, 1000)
+ }
}
- }
- if (files.length == 0) {
- ipcMain.emit("failedmod");
- return notamod();
+ if (files.length == 0) {
+ ipcMain.emit("failedmod");
+ return notamod();
+ }
}
- }
- return notamod();
- }
+ return notamod();
+ }
- if (mods.install(cache)) {
- installed();
- } else {return notamod()}
- }, 1000)
- });
+ if (mods.install(cache)) {
+ installed();
+ } else {return notamod()}
+ }, 1000)
+ });
+ } else {
+ return notamod();
+ }
}catch(err) {return notamod()}
}
},
@@ -787,7 +849,15 @@ const mods = {
console.log(mods.modfile().get())
setInterval(() => {
- ipcMain.emit("guigetmods");
+ if (gamepathExists()) {
+ ipcMain.emit("guigetmods");
+ } else {
+ if (fs.existsSync("viper.json")) {
+ if (settings.gamepath != "") {
+ ipcMain.emit("gamepathlost");
+ }
+ }
+ }
}, 1500)
module.exports = {
@@ -799,9 +869,12 @@ module.exports = {
setpath,
updatevp,
settings,
+ saveSettings,
getNSVersion,
getTF2Version,
isGameRunning,
+ gamepathExists,
+ handleNorthstarUpdating,
setlang: (lang) => {
settings.lang = lang;
saveSettings();