aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/app')
-rw-r--r--src/app/browser.js259
-rw-r--r--src/app/css/dragui.css58
-rw-r--r--src/app/css/launcher.css328
-rw-r--r--src/app/css/popups.css371
-rw-r--r--src/app/css/theming.css62
-rw-r--r--src/app/css/toasts.css52
-rw-r--r--src/app/css/webview.css16
-rw-r--r--src/app/icons/external.pngbin0 -> 9764 bytes
-rw-r--r--src/app/icons/game.pngbin0 -> 1454 bytes
-rw-r--r--src/app/icons/language.pngbin0 -> 7132 bytes
-rw-r--r--src/app/icons/updates.pngbin0 -> 10300 bytes
-rw-r--r--src/app/index.html47
-rw-r--r--src/app/lang.js2
-rw-r--r--src/app/launcher.js99
-rw-r--r--src/app/main.css742
-rw-r--r--src/app/main.js126
-rw-r--r--src/app/settings.js14
17 files changed, 1354 insertions, 822 deletions
diff --git a/src/app/browser.js b/src/app/browser.js
index 40670bc..db68d31 100644
--- a/src/app/browser.js
+++ b/src/app/browser.js
@@ -2,9 +2,24 @@ const Fuse = require("fuse.js");
var fuse;
var packages = [];
+var packagecount = 0;
+
var Browser = {
maxentries: 50,
filters: {
+ getpkgs: () => {
+ let pkgs = [];
+ let other = [];
+ for (let i in packages) {
+ if (! Browser.filters.isfiltered(packages[i].categories)) {
+ pkgs.push(packages[i]);
+ } else {
+ other.push(packages[i]);
+ }
+ }
+
+ return pkgs;
+ },
get: () => {
let filtered = [];
let unfiltered = [];
@@ -27,6 +42,19 @@ var Browser = {
let filtered = Browser.filters.get().filtered;
let unfiltered = Browser.filters.get().unfiltered;
let state = false;
+
+ let filters = [
+ "Mods", "Skins",
+ "Client-side", "Server-side",
+ ];
+
+ let newcategories = [];
+ for (let i = 0; i < categories.length; i++) {
+ if (filters.includes(categories[i])) {
+ newcategories.push(categories[i]);
+ }
+ }; categories = newcategories;
+
if (categories.length == 0) {return true}
for (let i = 0; i < categories.length; i++) {
if (filtered.includes(categories[i])) {
@@ -53,14 +81,14 @@ var Browser = {
let spacing = parseInt(getComputedStyle(filters).getPropertyValue("--spacing"));
filters.style.top = filterRect.bottom - spacing;
- filters.style.right = filterRect.right - filterRect.left + filterRect.width;
+ filters.style.right = filterRect.right - filterRect.left + filterRect.width - (spacing / 2);
},
},
toggle: (state) => {
if (state) {
browser.scrollTo(0, 0);
- overlay.classList.add("shown")
- browser.classList.add("shown")
+ overlay.classList.add("shown");
+ browser.classList.add("shown");
if (browserEntries.querySelectorAll(".el").length == 0) {
Browser.loadfront();
@@ -69,18 +97,21 @@ var Browser = {
} else if (! state) {
if (state != undefined) {
Browser.filters.toggle(false);
- overlay.classList.remove("shown")
- browser.classList.remove("shown")
+ overlay.classList.remove("shown");
+ browser.classList.remove("shown");
+ preview.classList.remove("shown");
return
}
}
browser.scrollTo(0, 0);
- overlay.classList.toggle("shown")
- browser.classList.toggle("shown")
+ overlay.classList.toggle("shown");
+ browser.classList.toggle("shown");
},
loadfront: async () => {
Browser.loading();
+
+ packagecount = 0;
if (packages.length < 1) {
packages = await (await fetch("https://northstar.thunderstore.io/api/v1/package/")).json();
@@ -90,8 +121,15 @@ var Browser = {
})
}
- for (let i in packages) {
- new BrowserElFromObj(packages[i]);
+ let pkgs = Browser.filters.getpkgs();
+ for (let i in pkgs) {
+ if (packagecount >= Browser.maxentries) {
+ Browser.endoflist();
+ break
+ }
+
+ new BrowserElFromObj(pkgs[i]);
+ packagecount++;
}
},
loading: (string) => {
@@ -107,21 +145,53 @@ var Browser = {
browserEntries.innerHTML = `<div class="loading">${lang('gui.browser.loading')}</div>`;
}
},
- endoflist: () => {
- if (browserEntries.querySelector(".message")) {return}
- browserEntries.innerHTML += `<div class="message">${lang('gui.browser.endoflist')}</div>`
+ endoflist: (isEnd) => {
+ let pkgs = [];
+ let filtered = Browser.filters.getpkgs();
+ for (let i = 0; i < filtered.length; i++) {
+ if ([packagecount + i]) {
+ pkgs.push(filtered[packagecount + i]);
+ } else {
+ break
+ }
+ }
+
+ if (browserEntries.querySelector(".message")) {
+ browserEntries.querySelector(".message").remove();
+ }
+
+ if (pkgs.length == 0 || isEnd) {
+ Browser.msg(`${lang('gui.browser.endoflist')}`);
+ return
+ }
+
+ Browser.msg(`<button id="loadmore">${lang("gui.browser.loadmore")}</button>`);
+ loadmore.addEventListener("click", () => {
+ Browser.loadpkgs(pkgs);
+ Browser.endoflist(pkgs);
+ })
},
search: (string) => {
Browser.loading();
let res = fuse.search(string);
if (res.length < 1) {
- Browser.loading(lang("gui.browser.noresults"))
+ Browser.loading(lang("gui.browser.noresults"));
return
}
+ packagecount = 0;
+
+ 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);
+ count++;
+ }
+
+ if (count < 1) {
+ Browser.loading(lang("gui.browser.noresults"));
}
},
setbutton: (mod, string) => {
@@ -157,6 +227,58 @@ var Browser = {
}
}, 1501)
}
+ },
+ loadpkgs: (pkgs, clear) => {
+ if (clear) {packagecount = 0}
+
+ if (browserEntries.querySelector(".message")) {
+ browserEntries.querySelector(".message").remove();
+ }
+
+ let count = 0;
+ for (let i in pkgs) {
+ if (count >= Browser.maxentries) {
+ if (pkgs[i] === undefined) {
+ Browser.endoflist(true);
+ }
+
+ Browser.endoflist();
+ break
+ }
+
+ try {
+ new BrowserElFromObj(pkgs[i]);
+ }catch(e) {}
+
+ count++;
+ packagecount++;
+ }
+ },
+ msg: (html) => {
+ let msg = document.createElement("div");
+ msg.classList.add("message");
+ msg.innerHTML = html;
+
+ browserEntries.appendChild(msg);
+ }
+}
+
+function openExternal(url) {
+ require("electron").shell.openExternal(url);
+}
+
+var view = document.querySelector(".popup#preview webview");
+var Preview = {
+ show: () => {
+ preview.classList.add("shown");
+ },
+ hide: () => {
+ preview.classList.remove("shown");
+ },
+ set: (url, autoshow) => {
+ if (autoshow != false) {Preview.show()}
+ view.src = url;
+ document.querySelector("#preview #external").setAttribute("onclick", `openExternal("${url}")`);
}
}
@@ -171,16 +293,14 @@ function BrowserElFromObj(obj) {
download: pkg.download_url,
version: pkg.version_number,
categories: pkg.categories,
- description: pkg.description
+ description: pkg.description,
+ dependencies: pkg.dependencies,
})
}
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",
@@ -230,25 +350,30 @@ function BrowserEl(properties) {
}
}
}
-
- browserEntries.innerHTML += `
- <div class="el" id="mod-${normalize(properties.title)}">
- <div class="image">
- <img 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="visual">${properties.version}</button>
- <button class="visual">${lang("gui.browser.madeby")} ${properties.author}</button>
- </div>
+
+ let entry = document.createElement("div");
+ entry.classList.add("el");
+ entry.id = `mod-${normalize(properties.title)}`;
+
+ entry.innerHTML = `
+ <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 class="install" onclick='installFromURL("${properties.download}", ${JSON.stringify(properties.dependencies)}, true)'>${installstr}</button>
+ <button class="info" onclick="Preview.set('${properties.url}')">${lang('gui.browser.view')}</button>
+ <button class="visual">${properties.version}</button>
+ <button class="visual">${lang("gui.browser.madeby")} ${properties.author}</button>
</div>
`
+
+ browserEntries.appendChild(entry);
}
-ipcRenderer.on("removedmod", (event, mod) => {
+ipcRenderer.on("removed-mod", (event, mod) => {
setButtons(true);
Browser.setbutton(mod.name, lang("gui.browser.install"));
if (mod.manifestname) {
@@ -256,7 +381,7 @@ ipcRenderer.on("removedmod", (event, mod) => {
}
})
-ipcRenderer.on("failedmod", (event, modname) => {
+ipcRenderer.on("failed-mod", (event, modname) => {
setButtons(true);
new Toast({
timeout: 10000,
@@ -266,7 +391,17 @@ ipcRenderer.on("failedmod", (event, modname) => {
})
})
-ipcRenderer.on("installedmod", (event, mod) => {
+ipcRenderer.on("no-internet", (event, modname) => {
+ setButtons(true);
+ new Toast({
+ timeout: 10000,
+ scheme: "error",
+ title: lang("gui.toast.noInternet.title"),
+ description: lang("gui.toast.noInternet.desc")
+ })
+})
+
+ipcRenderer.on("installed-mod", (event, mod) => {
setButtons(true);
Browser.setbutton(mod.name, lang("gui.browser.reinstall"));
@@ -284,14 +419,21 @@ ipcRenderer.on("installedmod", (event, mod) => {
title: lang("gui.toast.title.installed"),
description: mod.name + " " + lang("gui.toast.desc.installed")
})
+
+ if (installqueue.length != 0) {
+ installFromURL("https://thunderstore.io/package/download/" + installqueue[0]);
+ installqueue.shift();
+ }
})
function normalize(items) {
let main = (string) => {
- return string.replaceAll(" ", "").replaceAll(".", "").replaceAll("-", "").replaceAll("_", "").toLowerCase()
+ return string.replaceAll(" ", "")
+ .replaceAll(".", "").replaceAll("-", "")
+ .replaceAll("_", "").toLowerCase();
}
if (typeof items == "string") {
- return main(items)
+ return main(items);
} else {
let newArray = [];
for (let i = 0; i < items.length; i++) {
@@ -322,11 +464,50 @@ search.addEventListener("keyup", () => {
}
})
-browser.addEventListener("scroll", () => {
- Browser.filters.toggle(false);
+let events = ["scroll", "mousedown", "touchdown"];
+events.forEach((event) => {
+ browser.addEventListener(event, () => {
+ Preview.hide();
+
+ let mouseAt = document.elementsFromPoint(mouseX, mouseY);
+ if (! mouseAt.includes(document.querySelector("#filter"))
+ && ! mouseAt.includes(document.querySelector(".overlay"))) {
+ Browser.filters.toggle(false);
+ }
+ })
+});
+
+view.addEventListener("dom-ready", () => {
+ let css = [
+ fs.readFileSync(__dirname + "/css/theming.css", "utf8"),
+ fs.readFileSync(__dirname + "/css/webview.css", "utf8")
+ ]
+
+ view.insertCSS(css.join(" "));
+})
+
+view.addEventListener("did-stop-loading", () => {
+ view.style.display = "flex";
+ setTimeout(() => {
+ view.classList.remove("loading");
+ }, 200)
+})
+
+view.addEventListener("did-start-loading", () => {
+ view.style.display = "none";
+ view.classList.add("loading");
+})
+
+let mouseY = 0;
+let mouseX = 0;
+browser.addEventListener("mousemove", (event) => {
+ mouseY = event.clientY;
+ mouseX = event.clientX;
})
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 = ''")
+ checks[i].setAttribute("onclick",
+ "this.classList.toggle('checked');Browser.loadfront();search.value = ''"
+ )
}
diff --git a/src/app/css/dragui.css b/src/app/css/dragui.css
new file mode 100644
index 0000000..fb5b1a7
--- /dev/null
+++ b/src/app/css/dragui.css
@@ -0,0 +1,58 @@
+@import "theming.css";
+
+/*
+ This stylesheet is meant for the DragUI, i.e the UI that pops up when dragging
+ a modfile over the window.
+*/
+
+#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;
+}
diff --git a/src/app/css/launcher.css b/src/app/css/launcher.css
new file mode 100644
index 0000000..ce54ddf
--- /dev/null
+++ b/src/app/css/launcher.css
@@ -0,0 +1,328 @@
+@import "theming.css";
+
+/*
+ This stylesheet is meant for various elements around the launcher,
+ notably the navbar, launch buttons, and sidebar.
+*/
+
+.gamesContainer {
+ width: 10%;
+ height: 100%;
+ min-width: 95px;
+ max-width: 120px;
+
+ float: left;
+ display: flex;
+ flex-wrap: wrap;
+ align-content: center;
+}
+
+.mainContainer {
+ height: 100%;
+ flex-grow: 1;
+ display: flex;
+ position: relative;
+}
+
+/* nav bar buttons */
+.gamesContainer button {
+ background-size: 90%;
+ background-position: center;
+ background-repeat: no-repeat;
+
+ border: none;
+ transition: 0.3s ease-in-out;
+ background-color: transparent;
+
+
+ margin: 20px;
+ position: relative;
+ box-sizing: border-box;
+ flex-basis: calc(100% - 10px);
+}
+
+.gamesContainer button.inactive {
+ opacity: 0.5;
+ transform: scale(0.9);
+}
+
+.gamesContainer button::before {
+ content: "";
+ display: block;
+ padding-top: 100%;
+}
+
+.gamesContainer button .content {
+ width: 100%;
+ height: 100%;
+ top: 0; left: 0;
+ position: absolute;
+}
+
+#vpBtn {background-image: url("../icons/viper.png")}
+#nsBtn {background-image: url("../icons/northstar.png")}
+#tfBtn {
+ background-image: url("../icons/titanfall2.png");
+ background-size: 69%; /* nice */
+}
+
+.contentContainer {
+ width: 85%;
+ color: white;
+ flex-grow: 1;
+ opacity: 1.0;
+ margin-left: 5%;
+ position: absolute;
+ transition: 0.15s ease-in-out;
+}
+
+.contentContainer.hidden {
+ opacity: 0.0;
+ pointer-events: none;
+}
+
+.contentMenu {
+ padding: 0;
+ flex-grow: 1;
+ display: flex;
+ font-size: 20px;
+ list-style: none;
+ margin-bottom: 0;
+ align-items: center;
+ justify-content: center;
+ margin-top: var(--padding);
+}
+
+/* some sections do not need space between them and menu */
+#nsMain, #nsRelease, #vpReleaseNotes, .playBtnContainer {
+ margin-top: 0 !important;
+}
+
+.contentMenu li {
+ opacity: 0.6;
+ margin: 0 26px;
+ cursor: pointer;
+ font-weight: 700;
+ text-align: center;
+ height: fit-content;
+ transition: opacity 0.3s ease-in-out;
+}
+
+.contentMenu li:last-child {margin-right: 0px}
+.contentMenu li:first-child {margin-left: 0px}
+
+.contentMenu li:hover {opacity: 0.7}
+
+.contentMenu li[active] {
+ opacity: 1.0;
+ pointer-events: none;
+}
+
+.contentMenu li::after {
+ top: 10px;
+ width: 30px;
+ height: 5px;
+ opacity: 0.0;
+ content: " ";
+ display: block;
+ text-align: center;
+ position: relative;
+ border-radius: 50px;
+ left: calc(50% - 15px);
+ background: rgb(var(--red));
+ transition: 0.2s ease-in-out;
+}
+
+.contentMenu li[active]::after {
+ top: 5px;
+ opacity: 1.0;
+}
+
+.section {
+ opacity: 1.0;
+ position: fixed;
+ right: calc(var(--padding) * 2);
+ left: calc(100px + var(--padding));
+ transition: opacity 0.15s ease-in-out;
+}
+
+.section.hidden {
+ opacity: 0.0;
+ pointer-events: none;
+}
+
+.contentBody img {max-width: 100%}
+.contentBody .img {text-align: center}
+
+.contentBody .section > :first-child:not(.img) {
+ margin-top: 35px;
+}
+
+.contentContainer .playBtnContainer {
+ text-align: center;
+}
+
+.contentContainer .playBtn {
+ width: 27%;
+ height: 11%;
+ border: none;
+ color: white;
+ padding: 20px;
+ font-size: 24px;
+ font-weight: bold;
+ margin-top: 100px;
+ margin-bottom: 10px;
+ border-radius: 10px;
+ background: var(--redbg);
+ transition: 0.2s ease-in-out;
+ filter: drop-shadow(0px 8px 5px rgba(0, 0, 0, 0.1));
+}
+
+.contentContainer .playBtn:hover {
+ transform: scale(1.05);
+ filter: drop-shadow(0px 5px 15px rgba(0, 0, 0, 0.3)) brightness(110%);
+}
+
+.contentContainer .playBtn:active {
+ opacity: 0.7;transform: scale(0.98);
+ filter: drop-shadow(0px 5px 10px rgba(0, 0, 0, 0.4));
+}
+
+.contentContainer #nsMain .playBtn {
+ background: var(--bluebg);
+}
+
+#nsContent .contentMenu {
+ margin-bottom: 0;
+}
+
+.contentBody .img img {
+ transform: scale(0.85);
+}
+
+.contentBody .img {
+ width: 100%;
+ text-align: center;
+}
+
+#nsRelease, #vpReleaseNotes {
+ height: 80vh;
+ overflow-y: scroll;
+ flex-direction: column;
+ margin-top: 10px !important;
+}
+
+.inline * {
+ display: inline-block;
+}
+
+#vpMain {
+ margin-top: 140px;
+ text-align: center;
+}
+
+#vpMain img {
+ width: 20%;
+}
+
+#vpVersion {
+ font-size: 16px;
+}
+
+.simplebar-scrollbar:before {
+ background: rgb(var(--red)) !important;
+}
+
+#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;
+}
+
+button.visual {
+ opacity: 1.0;
+ padding-right: 0px;
+ pointer-events: none;
+ background: transparent !important;
+}
+
+code {
+ font-size: 16px;
+ padding: 2px 5px;
+ border-radius: 3px;
+ background-color: #00000070;
+}
+
+#nsMods .line {
+ display: flex;
+ align-items: center;
+ margin-top: calc(var(--padding) / 2);
+}
+
+#modsdiv {
+ height: 50vh;
+ overflow-y: scroll;
+ border-radius: 5px;
+ background: var(--bg);
+ backdrop-filter: blur(15px);
+ padding: calc(var(--padding) / 4);
+}
+
+#modsdiv .mod {
+ display: flex;
+ border-radius: 5px;
+ transition: 0.1s ease-in-out;
+ margin: calc(var(--padding) / 3);
+ padding: calc(var(--padding) / 3);
+}
+
+#modsdiv .mod.selected {
+ background: var(--selbg);
+}
+
+#modsdiv .mod .disabled, .modbtns {
+ margin-left: auto;
+}
+
+.modbtns button {
+ margin-left: var(--spacing);
+ --spacing: calc(var(--padding) / 3);
+ margin-top: calc(var(--spacing) / 2);
+ margin-bottom: calc(var(--spacing) / 2);
+}
+
+#serverstatus {
+ --spacing: calc(var(--padding) / 5);
+
+ transition-duration: 0.2s;
+ transition-timing-function: ease-in-out;
+ transition-property: background, opacity;
+
+ opacity: 0.0;
+ display: block;
+ margin: 0 auto;
+ font-weight: 700;
+ width: fit-content;
+ color: transparent;
+ border-radius: 50px;
+ flex-basis: max-content;
+ background: transparent;
+ margin-top: calc(var(--spacing) * 2);
+ padding: var(--spacing) calc(var(--spacing) * 3);
+}
+
+#serverstatus.up,
+#serverstatus.down {
+ color: white;
+ opacity: 1.0;
+}
+
+#serverstatus.up {background: rgb(var(--blue));}
+#serverstatus.down {background: rgb(var(--red));}
diff --git a/src/app/css/popups.css b/src/app/css/popups.css
new file mode 100644
index 0000000..826955b
--- /dev/null
+++ b/src/app/css/popups.css
@@ -0,0 +1,371 @@
+@import "theming.css";
+
+/*
+ This stylesheet is meant for the various popups we use, whether it be the
+ previewer, the browser, the settings menu, or anything alike.
+*/
+
+.popup {
+ --spacing: var(--padding);
+
+ z-index: 2;
+ opacity: 0.0;
+ position: fixed;
+ overflow-y: scroll;
+ top: var(--spacing);
+ pointer-events: none;
+ left: var(--spacing);
+ background: var(--bg);
+ right: var(--spacing);
+ bottom: var(--spacing);
+ transform: scale(0.98);
+ backdrop-filter: blur(15px);
+ border-radius: calc(var(--padding) / 3);
+
+ transition-duration: 0.15s;
+ transition-property: opacity, transform;
+ transition-timing-function: ease-in-out;
+}
+
+.popup.shown {
+ opacity: 1.0;
+ pointer-events: all;
+ transform: scale(1.0);
+}
+
+.popup.small {
+ left: 20vw;
+ right: 20vw;
+ top: calc(var(--padding) * 2);
+ bottom: calc(var(--padding) * 2);
+}
+
+#overlay {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1;
+ opacity: 0.0;
+ position: fixed;
+ background: var(--bg);
+ pointer-events: none;
+ transition: opacity 0.15s ease-in-out;
+}
+
+#overlay.shown {
+ opacity: 0.8;
+ pointer-events: all;
+}
+
+/* browser/preview popup { */
+@keyframes fadein {
+ 0% {opacity: 0.0}
+ 100% {opacity: 1.0}
+}
+
+#browserEntries {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.popup webview {
+ width: 78%;
+ margin: 0 auto;
+ filter: opacity(1.0);
+ transition: filter 0.15s ease-in-out;
+ margin-top: calc(var(--spacing) / 2);
+ height: calc(100% - calc(var(--spacing) / 2));
+}
+
+.popup webview.loading {
+ filter: opacity(0.0);
+ pointer-events: none;
+}
+
+.popup .el, .popup .misc, .popup .loading {
+ --spacing: calc(var(--padding) / 2);
+ --height: calc(var(--padding) * 3.5);
+ --mischeight: calc(var(--padding) * 1.5);
+
+ animation-duration: 0.15s;
+ animation-iteration-count: 1;
+ animation-name: fadein;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in-out;
+
+ opacity: 0.0;
+ transition: 0.15s ease-in-out;
+}
+
+.popup .el, .popup #search,
+.popup #close, .popup .misc button,
+.option .actions select, .option .actions input {
+ color: white;
+ display: flex;
+ align-items: center;
+ height: var(--height);
+ margin: var(--spacing);
+ padding: var(--spacing);
+ background: var(--selbg);
+ border-radius: var(--spacing);
+ width: calc(50% - var(--spacing) * 4);
+}
+
+.popup .misc, .popup #search, .option .actions input {
+ --height: var(--mischeight);
+}
+
+.popup .misc {
+ display: flex;
+}
+
+.popup .misc.vertical {
+ display: block;
+}
+
+.popup .misc.fixed {
+ width: 100%;
+ position: fixed;
+}
+
+.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);
+}
+
+.popup #search:focus,
+.option .actions input:focus,
+.option .actions button:active {
+ filter: brightness(1.5);
+}
+
+.popup .misc button {
+ --height: calc(var(--padding) * 1.5);
+
+ padding: 0px;
+ margin-left: 0px;
+ padding: 0px !important;
+ width: var(--height) !important;
+}
+
+.popup .misc button img {
+ opacity: 0.6;
+ width: var(--height);
+ transform: scale(0.5);
+ height: var(--height) !important;
+}
+
+.popup .misc button:last-child {
+ margin-left: 0px !important;
+}
+
+.popup#preview #close,
+.popup .misc.vertical button {
+ margin: var(--spacing) var(--spacing) 0 auto !important;
+}
+
+.popup .loading {
+ width: 100%;
+ color: white;
+ display: flex;
+ position: absolute;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ height: calc(100% - var(--mischeight) - var(--height));
+}
+
+.popup .message {
+ color: white;
+ text-align: center;
+ margin: var(--padding);
+ width: calc(100% - var(--padding));
+}
+
+.popup .el .image, .popup .el .image img {
+ width: var(--height);
+ height: var(--height);
+ margin-right: var(--spacing);
+ border-radius: var(--spacing);
+}
+
+.popup .el .image img.blur {
+ z-index: -1;
+ position: relative;
+ filter: blur(10px);
+ top: calc(var(--height) * -1 + 5px);
+}
+
+.popup .el .text {
+ overflow: hidden;
+}
+
+.popup .el .title, .popup .el .description {
+ height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.popup .el .title {
+ font-size: 1.2em;
+ font-weight: 700;
+}
+
+.popup .message #loadmore {
+ background: rgb(var(--blue2));
+}
+
+.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));
+}
+/* } */
+
+/* settings popup { */
+
+.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);
+}
+
+#options.popup .misc button {
+ margin-left: 0px;
+ width: auto !important;
+ padding-right: calc(var(--padding) / 2) !important;
+}
+
+.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));
+}
+
+.switch.on:hover::after {
+ transform: scale(1.2);
+}
+
+.title {
+ display: flex;
+}
+
+.title img {
+ width: 30px;
+ height: 30px;
+ margin: auto 0;
+}
+
+.title h2 {
+ margin-left: calc(var(--padding) / 3);
+}
+
+/* } */
diff --git a/src/app/css/theming.css b/src/app/css/theming.css
new file mode 100644
index 0000000..2d45b1d
--- /dev/null
+++ b/src/app/css/theming.css
@@ -0,0 +1,62 @@
+/*
+ the only reason some of these properties have an !important property
+ is for it to overwrite Thunderstore's CSS in the preview <webview>
+*/
+
+:root {
+ --red: 199, 119, 127 !important;
+ --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, rgb(var(--red)), #FA4343);
+ --bluebg: linear-gradient(45deg, rgb(var(--blue)), #7380ED);
+}
+
+body, button, input {
+ font-family: "Roboto", sans-serif !important;
+}
+
+body, button, img, a {
+ user-select: none;
+ -webkit-user-drag: none;
+}
+
+a {
+ text-decoration: none !important;
+ color: rgb(var(--red)) !important;
+ transition: filter 0.2s ease-in !important;
+}
+
+a:hover {
+ filter: brightness(80%) !important;
+}
+
+
+::-webkit-scrollbar {
+ width: 8px !important;
+}
+
+::-webkit-scrollbar-track {
+ border-radius: 10px !important;
+ background: transparent !important;
+}
+
+::-webkit-scrollbar-thumb {
+ border-radius: 10px !important;
+ background: rgb(var(--red)) !important;
+}
+
+::selection {
+ color: black !important;
+ background: rgb(var(--red)) !important;
+}
diff --git a/src/app/css/toasts.css b/src/app/css/toasts.css
new file mode 100644
index 0000000..57ba055
--- /dev/null
+++ b/src/app/css/toasts.css
@@ -0,0 +1,52 @@
+@import "theming.css";
+
+#toasts {
+ position: fixed;
+ z-index: 100000;
+ right: calc(var(--padding) * 1.5);
+ bottom: calc(var(--padding) * 1.5);
+}
+
+@keyframes bodyfadeaway {
+ 0% {opacity: 0.0; transform: scale(0.95)}
+ 100% {opacity: 1.0; transform: scale(1.0)}
+}
+
+#toasts .toast {
+ width: 300px;
+ opacity: 0.0;
+ cursor: pointer;
+ overflow: hidden;
+ max-height: 100vh;
+ background: #FFFFFF;
+ transform: scale(0.95);
+ transition: 0.2s ease-in-out;
+ padding: calc(var(--padding) / 2);
+ margin-top: calc(var(--padding) / 2);
+ border-radius: calc(var(--padding) / 2.5);
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
+
+ animation-duration: 0.2s;
+ animation-iteration-count: 1;
+ animation-name: bodyfadeaway;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease-in-out;
+}
+
+#toasts .toast.hidden {
+ margin-top: 0px;
+ max-height: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ filter: opacity(0.0);
+ transform: scale(0.95);
+}
+
+#toasts .toast:not(.hidden):hover {filter: opacity(0.9)}
+#toasts .toast:not(.hidden):active {filter: opacity(0.8)}
+
+.toast .description {
+ opacity: 0.8;
+ font-size: 0.8em;
+ font-weight: 600;
+}
diff --git a/src/app/css/webview.css b/src/app/css/webview.css
new file mode 100644
index 0000000..982ebfc
--- /dev/null
+++ b/src/app/css/webview.css
@@ -0,0 +1,16 @@
+body {
+ overflow-x: hidden;
+ background: transparent !important;
+ background-color: transparent !important;
+}
+
+#ncmp_tool,
+.navbar, .bottom-padding,
+.card-header, .breadcrumb,
+.list-group, .mb-4, .my-2, .mt-2,
+#thunderstore-mod-manager-ad-alert {
+ display: none !important;
+}
+
+.mt-2.mb-2 {display: block !important}
+.card {transform: translateY(-1.0rem)}
diff --git a/src/app/icons/external.png b/src/app/icons/external.png
new file mode 100644
index 0000000..0b4f99e
--- /dev/null
+++ b/src/app/icons/external.png
Binary files differ
diff --git a/src/app/icons/game.png b/src/app/icons/game.png
new file mode 100644
index 0000000..7db058f
--- /dev/null
+++ b/src/app/icons/game.png
Binary files differ
diff --git a/src/app/icons/language.png b/src/app/icons/language.png
new file mode 100644
index 0000000..011e1f9
--- /dev/null
+++ b/src/app/icons/language.png
Binary files differ
diff --git a/src/app/icons/updates.png b/src/app/icons/updates.png
new file mode 100644
index 0000000..4b505ee
--- /dev/null
+++ b/src/app/icons/updates.png
Binary files differ
diff --git a/src/app/index.html b/src/app/index.html
index 794e209..5143520 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -36,7 +36,10 @@
</button>
</div>
<div class="options">
- <h2>%%gui.settings.title.ns%%</h2>
+ <div class="title">
+ <img src="icons/game.png">
+ <h2>%%gui.settings.title.ns%%</h2>
+ </div>
<div class="option" name="nsargs">
<div class="text">
%%gui.settings.nsargs.title%%
@@ -72,6 +75,10 @@
</div>
</div>
<h2>%%gui.settings.title.language%%</h2>
+ <div class="title">
+ <img src="icons/language.png">
+ <h2>%%gui.settings.title.language%%</h2>
+ </div>
<div class="option" name="autolang">
<div class="text">
%%gui.settings.autolang.title%%
@@ -96,7 +103,10 @@
</select>
</div>
</div>
- <h2>%%gui.settings.title.updates%%</h2>
+ <div class="title">
+ <img src="icons/updates.png">
+ <h2>%%gui.settings.title.updates%%</h2>
+ </div>
<div class="option" name="autoupdate">
<div class="text">
%%gui.settings.autoupdate.title%%
@@ -130,6 +140,21 @@
<input type="text">
</div>
</div>
+ <div class="title">
+ <img src="icons/settings.png">
+ <h2>%%gui.settings.title.misc%%</h2>
+ </div>
+ <div class="option" name="originkill">
+ <div class="text">
+ %%gui.settings.originkill.title%%
+ <div class="desc">
+ %%gui.settings.originkill.desc%%
+ </div>
+ </div>
+ <div class="actions">
+ <button class="switch off"></button>
+ </div>
+ </div>
</div>
</div>
@@ -156,6 +181,17 @@
<div class="loading">%%gui.browser.loading%%</div>
</div>
</div>
+ <div class="popup small" id="preview">
+ <div class="misc fixed vertical">
+ <button id="close" onclick="Preview.hide()">
+ <img src="icons/close.png">
+ </button>
+ <button id="external" onclick="">
+ <img src="icons/external.png">
+ </button>
+ </div>
+ <webview></webview>
+ </div>
<nav class="gamesContainer">
<button id="vpBtn" onclick="page(0)"></button>
@@ -208,7 +244,8 @@
<button id="playNsBtn" class="playBtn" onclick="launch()">%%gui.launch%%</button>
<div class="inline">
<div id="nsversion"></div>
- <a id="update" href="#" onclick="update()">(%%gui.update.check%%)</a>
+ <a id="update" href="#" onclick="updateNorthstar()">(%%gui.update.check%%)</a>
+ <div id="serverstatus" class="checking"></div>
</div>
</div>
</div>
@@ -223,7 +260,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>
@@ -252,4 +289,4 @@
<script src="settings.js"></script>
<script src="launcher.js"></script>
</body>
-</html>
+<
diff --git a/src/app/lang.js b/src/app/lang.js
index 6fdcd8d..f1c31d3 100644
--- a/src/app/lang.js
+++ b/src/app/lang.js
@@ -10,7 +10,7 @@ function setlang() {
if (html[i][0] != " " &&
html[i][html[i].length - 1] != " ") {
// Replaces it with it's string
- html[i] = lang(html[i])
+ html[i] = lang(html[i]);
}
}
diff --git a/src/app/launcher.js b/src/app/launcher.js
index e0d7619..e1dbbe0 100644
--- a/src/app/launcher.js
+++ b/src/app/launcher.js
@@ -1,10 +1,14 @@
const markdown = require("marked").parse;
+var servercount;
+var playercount;
+var masterserver;
+
// Changes the main page
// This is the tabs in the sidebar
function page(page) {
- let pages = document.querySelectorAll(".mainContainer .contentContainer")
- let btns = document.querySelectorAll(".gamesContainer button")
+ let btns = document.querySelectorAll(".gamesContainer button");
+ let pages = document.querySelectorAll(".mainContainer .contentContainer");
for (let i = 0; i < pages.length; i++) {
pages[i].classList.add("hidden");
@@ -19,36 +23,39 @@ function page(page) {
bgHolder.setAttribute("bg", page);
}; page(1)
-
-// Updates the Viper release notes
-ipcRenderer.on("vp-notes", (event, response) => {
+function formatRelease(notes) {
let content = "";
- for (const release of response) {
- content += "# " + release.name + "\n\n"
- + release.body.replaceAll("\r\n", "\n") + "\n\n\n";
+ if (notes.length === 1) {
+ content = notes[0];
+ } else {
+ for (let release of notes) {
+ if (release.prerelease) {continue}
+ content += "# " + release.name + "\n\n" + release.body + "\n\n\n";
+ }
+
+ content = content.replaceAll(/\@(\S+)/g, `<a href="https://github.com/$1">@$1</a>`);
}
- vpReleaseNotes.innerHTML = markdown(content);
-});
-
-async function loadVpReleases() {
- ipcRenderer.send("get-vp-notes");
-}; loadVpReleases();
+ return markdown(content, {
+ breaks: true
+ });
+}
+// Updates the Viper release notes
+ipcRenderer.on("vp-notes", (event, response) => {
+ vpReleaseNotes.innerHTML = formatRelease(response);
+});
// Updates the Northstar release notes
ipcRenderer.on("ns-notes", (event, response) => {
- let content = "";
-
- for (let release of response) {
- content += "# " + release.name + "\n\n"
- + release.body.replaceAll("\r\n", "\nhtmlbreak") + "\n\n\n";
- }
-
- nsRelease.innerHTML = markdown(content).replaceAll("htmlbreak", "<br>");
+ nsRelease.innerHTML = formatRelease(response);
});
+async function loadVpReleases() {
+ ipcRenderer.send("get-vp-notes");
+}; loadVpReleases();
+
async function loadNsReleases() {
ipcRenderer.send("get-ns-notes");
}; loadNsReleases();
@@ -83,7 +90,10 @@ function showVpSection(section) {
}
function showNsSection(section) {
- if (!["main", "release", "mods"].includes(section)) throw new Error("unknown ns section");
+ if (!["main", "release", "mods"].includes(section)) {
+ throw new Error("unknown ns section");
+ }
+
nsMainBtn.removeAttribute("active");
nsModsBtn.removeAttribute("active");
nsReleaseBtn.removeAttribute("active");
@@ -108,3 +118,46 @@ function showNsSection(section) {
break;
}
}
+
+async function loadServers() {
+ serverstatus.classList.add("checking");
+
+ try {
+ let servers = await (await fetch("https://northstar.tf/client/servers")).json();
+ masterserver = true;
+
+ playercount = 0;
+ servercount = servers.length;
+
+ for (let i = 0; i < servers.length; i++) {
+ playercount += servers[i].playerCount
+ }
+ }catch (err) {
+ playercount = 0;
+ servercount = 0;
+ masterserver = false;
+ }
+
+ serverstatus.classList.remove("checking");
+
+ if (servercount == 0 || ! servercount || ! playercount) {masterserver = false}
+
+ let playerstr = lang("gui.server.players");
+ if (playercount == 1) {
+ playerstr = lang("gui.server.player");
+ }
+
+ if (masterserver) {
+ serverstatus.classList.add("up");
+ serverstatus.innerHTML = `${servercount} ${lang("gui.server.servers")} - ${playercount} ${playerstr}`;
+ } else {
+ serverstatus.classList.add("down");
+ serverstatus.innerHTML = lang("gui.server.offline");
+
+ }
+}; loadServers()
+
+// Refreshes every 5 minutes
+setInterval(() => {
+ loadServers();
+}, 300000)
diff --git a/src/app/main.css b/src/app/main.css
index 87d89e0..f39c5a1 100644
--- a/src/app/main.css
+++ b/src/app/main.css
@@ -1,52 +1,14 @@
-:root {
- --red: 199, 119, 127;
- --blue: 129, 161, 193;
- --orange: 213, 151, 131;
-
- --padding: 25px;
-
- --bg: rgba(0, 0, 0, 0.5);
- --selbg: rgba(80, 80, 80, 0.5);
- --redbg: linear-gradient(45deg, rgb(var(--red)), #FA4343);
- --bluebg: linear-gradient(45deg, rgb(var(--blue)), #7380ED);
-}
-
-.popup, #modsdiv {
- outline: 1px solid #444444;
- border: 3px solid var(--bg);
-}
-
-::-webkit-scrollbar {
- width: 8px;
-}
-
-::-webkit-scrollbar-track {
- border-radius: 10px;
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- border-radius: 10px;
- background: rgb(var(--red));
-}
-
-::selection {
- color: black;
- background: rgb(var(--red));
-}
+@import "css/dragui.css";
+@import "css/toasts.css";
+@import "css/popups.css";
+@import "css/theming.css";
+@import "css/launcher.css";
body {
margin: 0;
overflow: hidden;
}
-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,281 +24,19 @@ button {
transition: 0.2s ease-in-out;
}
-.playBtn, .gamesContainer button, #winbtns div {
- cursor: pointer;
-}
-
-.popup {
- --spacing: var(--padding);
-
- z-index: 2;
- opacity: 0.0;
- position: fixed;
- overflow-y: scroll;
- top: var(--spacing);
- pointer-events: none;
- left: var(--spacing);
- background: var(--bg);
- right: var(--spacing);
- bottom: var(--spacing);
- transform: scale(0.98);
- backdrop-filter: blur(15px);
- border-radius: calc(var(--padding) / 3);
- transition: opacity 0.15s ease-in-out, transform 0.15s ease-in-out;
-}
-
-.popup.shown {
- opacity: 1.0;
- pointer-events: all;
- transform: scale(1.0);
-}
-
-#overlay {
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 1;
- opacity: 0.0;
- position: fixed;
- background: var(--bg);
- pointer-events: none;
- transition: opacity 0.15s ease-in-out;
-}
-
-#overlay.shown {
- opacity: 0.8;
- pointer-events: all;
-}
-
-@keyframes fadein {
- 0% {opacity: 0.0}
- 100% {opacity: 1.0}
-}
-
-.popup .el, .popup .misc, .popup .loading {
- --spacing: calc(var(--padding) / 2);
- --height: calc(var(--padding) * 3);
- --mischeight: calc(var(--padding) * 1.5);
-
- animation-duration: 0.15s;
- animation-iteration-count: 1;
- animation-name: fadein;
- animation-fill-mode: forwards;
- animation-timing-function: ease-in-out;
-
- opacity: 0.0;
- transition: 0.15s ease-in-out;
-}
-
-.popup .el, .popup #search, .option .actions select, .option .actions input, .popup #close, .popup .misc button {
- color: white;
- display: flex;
- align-items: center;
- height: var(--height);
- margin: var(--spacing);
- padding: var(--spacing);
- background: var(--selbg);
- border-radius: var(--spacing);
- width: calc(100% - var(--spacing) * 4);
-}
-
-.popup .misc, .popup #search, .option .actions input {
- --height: var(--mischeight);
-}
-
-.popup .misc {
- display: flex;
-}
-
-.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);
-}
-
-.popup #search:focus, .option .actions input:focus, .option .actions button:active {
- filter: brightness(1.5);
-}
-
-.popup .misc button {
- --height: calc(var(--padding) * 1.5);
-
- padding: 0px;
- margin-left: 0px;
- padding: 0px !important;
- width: var(--height) !important;
-}
-
-.popup .misc button img {
- opacity: 0.6;
- width: var(--height);
- transform: scale(0.6);
- height: var(--height) !important;
-}
-
-#options.popup .misc button {
- margin-left: 0px;
- width: auto !important;
- padding-right: calc(var(--padding) / 2) !important;
-}
-
-.popup .loading {
- width: 100%;
- color: white;
- display: flex;
- text-align: center;
- align-items: center;
- justify-content: center;
- height: calc(100% - var(--mischeight) - var(--height));
-}
-
-.popup .message {
- color: white;
- text-align: center;
- margin: var(--padding);
- width: calc(100% - var(--padding));
-}
-
-.popup .el .image, .popup .el .image img {
- width: var(--height);
- height: var(--height);
- margin-right: var(--spacing);
- border-radius: var(--spacing);
-}
-
-.popup .el .text {
- overflow: hidden;
-}
-
-.popup .el .title, .popup .el .description {
- height: 1.2em;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-.popup .el .title {font-size: 1.2em}
-.popup .el .description {font-size: 0.8em}
-.popup .el button {
- background: rgb(var(--blue));
- margin-top: var(--spacing);
-}
-
-.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);
-}
+button:hover {filter: brightness(110%)}
+button:active {filter: brightness(90%)}
-.checks {
+.popup, #modsdiv {
+ outline: 1px solid #444444;
+ border: 3px solid var(--bg);
}
-.check {
- display:flex;
+.playBtn, .gamesContainer button, #winbtns div {
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));
-}
-
+/* window buttons { */
#winbtns {
z-index: 2;
display: flex;
@@ -359,11 +59,13 @@ button {
#winbtns #minimize {background-image: url("icons/minimize.png")}
#winbtns #settings {background-image: url("icons/settings.png")}
+#winbtns #settings:hover {
+ transform: rotate(90deg);
+}
+
#winbtns div:hover {opacity: 1.0}
#winbtns div:active {transform: scale(0.95)}
-
-button:hover {filter: brightness(110%)}
-button:active {filter: brightness(90%)}
+/* } */
img {pointer-events: none}
@@ -377,7 +79,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);
}
@@ -385,412 +87,7 @@ img {pointer-events: none}
#bgHolder[bg="1"] {background-image: url("../assets/bg/northstar.jpg")}
#bgHolder[bg="2"] {background-image: url("../assets/bg/tf2.jpg")}
-.gamesContainer {
- width: 10%;
- height: 100%;
- min-width: 95px;
- max-width: 120px;
-
- float: left;
- display: flex;
- flex-wrap: wrap;
- align-content: center;
-}
-
-.mainContainer {
- height: 100%;
- flex-grow: 1;
- display: flex;
- position: relative;
-}
-
-
-/* nav bar buttons */
-.gamesContainer button {
- background-size: 90%;
- background-position: center;
- background-repeat: no-repeat;
-
- border: none;
- transition: 0.3s ease-in-out;
- background-color: transparent;
-
-
- margin: 20px;
- position: relative;
- box-sizing: border-box;
- flex-basis: calc(100% - 10px);
-}
-
-.gamesContainer button.inactive {
- opacity: 0.5;
- transform: scale(0.9);
-}
-
-.gamesContainer button::before {
- content: "";
- display: block;
- padding-top: 100%;
-}
-
-.gamesContainer button .content {
- width: 100%;
- height: 100%;
- top: 0; left: 0;
- position: absolute;
-}
-
-#vpBtn {background-image: url("icons/viper.png")}
-#nsBtn {background-image: url("icons/northstar.png")}
-#tfBtn {
- background-image: url("icons/titanfall2.png");
- background-size: 69%; /* nice */
-}
-
-.contentContainer {
- width: 85%;
- color: white;
- flex-grow: 1;
- opacity: 1.0;
- margin-left: 5%;
- position: absolute;
- transition: 0.15s ease-in-out;
-}
-
-.contentContainer.hidden {
- opacity: 0.0;
- pointer-events: none;
-}
-
-.contentMenu {
- padding: 0;
- flex-grow: 1;
- display: flex;
- font-size: 20px;
- list-style: none;
- margin-bottom: 0;
- align-items: center;
- justify-content: center;
- margin-top: var(--padding);
-}
-
-/* some sections do not need space between them and menu */
-#nsMain, #nsRelease, #vpReleaseNotes, .playBtnContainer {
- margin-top: 0 !important;
-}
-
-.contentMenu li {
- opacity: 0.6;
- margin: 0 26px;
- cursor: pointer;
- font-weight: 700;
- text-align: center;
- height: fit-content;
- transition: opacity 0.3s ease-in-out;
-}
-
-.contentMenu li:last-child {margin-right: 0px}
-.contentMenu li:first-child {margin-left: 0px}
-
-.contentMenu li:hover {opacity: 0.7}
-
-.contentMenu li[active] {
- opacity: 1.0;
- pointer-events: none;
-}
-
-.contentMenu li::after {
- top: 10px;
- width: 30px;
- height: 5px;
- opacity: 0.0;
- content: " ";
- display: block;
- text-align: center;
- position: relative;
- border-radius: 50px;
- background: rgb(var(--red));
- left: calc(50% - 15px);
- transition: 0.2s ease-in-out;
-}
-
-.contentMenu li[active]::after {
- top: 5px;
- opacity: 1.0;
-}
-
-.section {
- opacity: 1.0;
- position: fixed;
- right: calc(var(--padding) * 2);
- left: calc(100px + var(--padding));
- transition: opacity 0.15s ease-in-out;
-}
-
-.section.hidden {
- opacity: 0.0;
- pointer-events: none;
-}
-
-.contentBody img {max-width: 100%}
-.contentBody .img {text-align: center}
-
-.contentBody .section > :first-child:not(.img) {
- margin-top: 35px;
-}
-
-.contentContainer .playBtnContainer {
- text-align: center;
-}
-
-.contentContainer .playBtn {
- width: 27%;
- height: 11%;
- border: none;
- color: white;
- padding: 20px;
- font-size: 24px;
- font-weight: bold;
- margin-top: 100px;
- margin-bottom: 10px;
- border-radius: 10px;
- background: var(--redbg);
- transition: 0.3s ease-in-out;
- filter: drop-shadow(0px 8px 5px rgba(0, 0, 0, 0.1));
-}
-
-.contentContainer .playBtn:hover {
- transform: scale(1.05);
- filter: drop-shadow(0px 5px 15px rgba(0, 0, 0, 0.3)) brightness(110%);
-}
-
-.contentContainer .playBtn:active {
- opacity: 0.7;transform: scale(0.98);
- filter: drop-shadow(0px 5px 10px rgba(0, 0, 0, 0.4));
-}
-
-.contentContainer #nsMain .playBtn {
- background: var(--bluebg);
-}
-
-a {
- color: rgb(var(--red));
- text-decoration: none;
- transition: filter 0.2s ease-in;
-}
-
-a:hover {
- filter: brightness(80%);
-}
-
-#nsContent .contentMenu {
- margin-bottom: 0;
-}
-
-.contentBody .img img {
- transform: scale(0.85);
-}
-
-.contentBody .img {
- width: 100%;
- text-align: center;
-}
-
-#nsRelease, #vpReleaseNotes {
- height: 80vh;
- overflow-y: scroll;
- flex-direction: column;
- margin-top: 10px !important;
-}
-
-.inline * {
- display: inline-block;
-}
-
-#vpMain {
- margin-top: 140px;
- text-align: center;
-}
-
-#vpMain img {
- width: 20%;
-}
-
-#vpVersion {
- font-size: 16px;
-}
-
-.simplebar-scrollbar:before {
- background: rgb(var(--red)) !important;
-}
-
-#installmod {background: rgb(var(--blue))}
-#togglemod, #toggleall {background: rgb(var(--orange))}
-#removeall, #removemod {background: rgb(var(--red))}
-button:disabled {
- opacity: 0.5;
- pointer-events: none;
-}
-
-button.visual {
- opacity: 1.0;
- pointer-events: none;
- background: transparent !important;
-}
-
-code {
- font-size: 16px;
- padding: 2px 5px;
- border-radius: 3px;
- background-color: #00000070;
-}
-
-#nsMods .line {
- display: flex;
- align-items: center;
- margin-top: calc(var(--padding) / 2);
-}
-
-#modsdiv {
- height: 50vh;
- overflow-y: scroll;
- border-radius: 5px;
- background: var(--bg);
- backdrop-filter: blur(15px);
- padding: calc(var(--padding) / 4);
-}
-
-#modsdiv .mod {
- display: flex;
- border-radius: 5px;
- transition: 0.1s ease-in-out;
- margin: calc(var(--padding) / 3);
- padding: calc(var(--padding) / 3);
-}
-
-#modsdiv .mod.selected {
- background: var(--selbg);
-}
-
-#modsdiv .mod .disabled, .modbtns {
- margin-left: auto;
-}
-
-.modbtns button {
- margin-left: var(--spacing);
- --spacing: calc(var(--padding) / 3);
- margin-top: calc(var(--spacing) / 2);
- margin-bottom: calc(var(--spacing) / 2);
-}
-
-#toasts {
- position: fixed;
- z-index: 100000;
- right: calc(var(--padding) * 1.5);
- bottom: calc(var(--padding) * 1.5);
-}
-
-@keyframes bodyfadeaway {
- 0% {opacity: 0.0; transform: scale(0.95)}
- 100% {opacity: 1.0; transform: scale(1.0)}
-}
-
-#toasts .toast {
- width: 300px;
- opacity: 0.0;
- cursor: pointer;
- overflow: hidden;
- max-height: 100vh;
- background: #FFFFFF;
- transform: scale(0.95);
- transition: 0.2s ease-in-out;
- padding: calc(var(--padding) / 2);
- margin-top: calc(var(--padding) / 2);
- border-radius: calc(var(--padding) / 2.5);
- box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
-
- animation-duration: 0.2s;
- animation-iteration-count: 1;
- animation-name: bodyfadeaway;
- animation-fill-mode: forwards;
- animation-timing-function: ease-in-out;
-}
-
-#toasts .toast.hidden {
- margin-top: 0px;
- max-height: 0px;
- padding-top: 0px;
- padding-bottom: 0px;
- filter: opacity(0.0);
- transform: scale(0.95);
-}
-
-#toasts .toast:not(.hidden):hover {filter: opacity(0.9)}
-#toasts .toast:not(.hidden):active {filter: opacity(0.8)}
-
-.toast .title {
-}
-
-.toast .description {
- opacity: 0.8;
- font-size: 0.8em;
- 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,
.contentContainer,
.gamesContainer {
@@ -804,6 +101,7 @@ code {
-webkit-app-region: no-drag;
}
-a, button, #close, #nsRelease, #vpReleaseNotes, .mod, #overlay, #modsdiv, #winbtns, .contentMenu {
+a, button, #close, #nsRelease, #vpReleaseNotes,
+.mod, #overlay, #modsdiv, #winbtns, .contentMenu {
-webkit-app-region: no-drag;
}
diff --git a/src/app/main.js b/src/app/main.js
index 4918777..ad1a44f 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -1,6 +1,6 @@
const fs = require("fs");
const path = require("path");
-const { ipcRenderer, shell } = require("electron");
+const { ipcRenderer, shell, ipcMain } = require("electron");
const lang = require("../lang");
var modsobj = {};
@@ -15,6 +15,7 @@ var settings = {
autolang: true,
forcedlang: "en",
autoupdate: true,
+ originkill: false,
zip: "/northstar.zip",
lang: navigator.language,
winebin: "/usr/bin/wine64",
@@ -29,7 +30,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) {
@@ -46,8 +64,14 @@ if (fs.existsSync("viper.json")) {
setpath();
}
+
+// Show a toast message if no Internet connection has been detected.
+if (!navigator.onLine) {
+ ipcRenderer.send("no-internet");
+}
+
function exit() {ipcRenderer.send("exit")}
-function update() {ipcRenderer.send("update")}
+function updateNorthstar() {ipcRenderer.send("update-northstar")}
// Reports to the main process about game path status.
// @param {boolean} value is game path loaded
@@ -58,15 +82,15 @@ function setpath(value = false) {
// Tells the main process to launch or install Northstar
function launch() {
if (shouldInstallNorthstar) {
- update();
+ updateNorthstar();
shouldInstallNorthstar = false;
} else {
- ipcRenderer.send("launch");
+ ipcRenderer.send("launch-ns");
}
}
// Tells the main process to launch the vanilla game
-function launchVanilla() {ipcRenderer.send("launchVanilla")}
+function launchVanilla() {ipcRenderer.send("launch-vanilla")}
// In conjunction with utils.js' winLog(), it'll send log messages in
// the devTools from utils.js
@@ -85,13 +109,11 @@ function setButtons(state) {
}
}
- disablearray(document.querySelectorAll(".playBtnContainer .playBtn"))
- disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button"))
- disablearray(document.querySelectorAll("#browser #browserEntries .text button"))
+ 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("gamestate", (event, state) => {
setButtons(! state);
@@ -105,7 +127,11 @@ ipcRenderer.on("gamestate", (event, state) => {
btns[1].innerHTML = string;
})
-ipcRenderer.on("gamepathlost", (event, state) => {
+ipcRenderer.on("set-buttons", (event, state) => {
+ setButtons(state);
+})
+
+ipcRenderer.on("gamepath-lost", (event, state) => {
page(0);
setButtons(false);
alert(lang("gui.gamepath.lost"));
@@ -117,6 +143,7 @@ ipcRenderer.on("ns-update-event", (event, key) => {
console.log(lang(key));
switch(key) {
case "cli.update.uptodate.short":
+ case "cli.update.noInternet":
setButtons(true);
playNsBtn.innerText = lang("gui.launch");
break;
@@ -171,7 +198,7 @@ function selected(all) {
}
}
- ipcRenderer.send("removemod", selected)
+ ipcRenderer.send("remove-mod", selected);
},
toggle: () => {
if (selected.match(/^Northstar\./)) {
@@ -184,33 +211,82 @@ function selected(all) {
}
}
- ipcRenderer.send("togglemod", selected)
+ ipcRenderer.send("toggle-mod", selected);
}
}
}
+let installqueue = [];
+
// Tells the main process to install a mod through the file selector
function installmod() {
setButtons(false);
- ipcRenderer.send("installmod")
+ ipcRenderer.send("install-mod");
}
// Tells the main process to directly install a mod from this path
function installFromPath(path) {
setButtons(false);
- ipcRenderer.send("installfrompath", path)
+ ipcRenderer.send("install-from-path", path);
}
// Tells the main process to install a mod from a URL
-function installFromURL(url) {
+function installFromURL(url, dependencies, clearqueue) {
+ 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(depend);
+ prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.madeby")} ${pkg[0]}`);
+ }
+ }
+ }
+
+ dependencies = newdepends;
+ }
+
+ if (dependencies && dependencies.length != 0) {
+ let confirminstall = confirm(lang("gui.mods.confirmdependencies") + prettydepends.join("\n"));
+ if (! confirminstall) {
+ return
+ }
+ }
+
setButtons(false);
- ipcRenderer.send("installfromurl", url)
+ ipcRenderer.send("install-from-url", url, dependencies);
+
+ if (dependencies) {
+ installqueue = dependencies;
+ }
+}
+
+function isModInstalled(modname) {
+ for (let i = 0; i < modsobj.all.length; i++) {
+ let mod = modsobj.all[i];
+ if (mod.ManifestName) {
+ if (mod.ManifestName.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) => {
settings.gamepath = newpath;
- ipcRenderer.send("guigetmods");
+ ipcRenderer.send("gui-getmods");
})
// Continuation of log()
@@ -258,23 +334,23 @@ ipcRenderer.on("version", (event, versions) => {
shouldInstallNorthstar = true;
playNsBtn.innerText = lang("gui.installnorthstar");
}
-}); ipcRenderer.send("getversion");
+}); ipcRenderer.send("get-version");
// When an update is available it'll ask the user about it
-ipcRenderer.on("updateavailable", () => {
+ipcRenderer.on("update-available", () => {
if (confirm(lang("gui.update.available"))) {
- ipcRenderer.send("updatenow");
+ ipcRenderer.send("update-now");
}
})
// Error out when no game path is set
-ipcRenderer.on("nopathselected", () => {
+ipcRenderer.on("no-path-selected", () => {
alert(lang("gui.gamepath.must"));
exit();
});
// Error out when game path is wrong
-ipcRenderer.on("wrongpath", () => {
+ipcRenderer.on("wrong-path", () => {
alert(lang("gui.gamepath.wrong"));
setpath(false);
});
@@ -303,7 +379,7 @@ document.addEventListener("drop", (e) => {
event.stopPropagation();
dragUI.classList.remove("shown");
- installFromPath(event.dataTransfer.files[0].path)
+ installFromPath(event.dataTransfer.files[0].path);
});
document.body.addEventListener("keyup", (e) => {
diff --git a/src/app/settings.js b/src/app/settings.js
index 96d9379..34975fe 100644
--- a/src/app/settings.js
+++ b/src/app/settings.js
@@ -3,26 +3,26 @@ var Settings = {
if (state) {
Settings.load();
options.scrollTo(0, 0);
- overlay.classList.add("shown")
- options.classList.add("shown")
+ overlay.classList.add("shown");
+ options.classList.add("shown");
return
} else if (! state) {
if (state != undefined) {
- overlay.classList.remove("shown")
- options.classList.remove("shown")
+ overlay.classList.remove("shown");
+ options.classList.remove("shown");
return
}
}
Settings.load();
options.scrollTo(0, 0);
- overlay.classList.toggle("shown")
- options.classList.toggle("shown")
+ overlay.classList.toggle("shown");
+ options.classList.toggle("shown");
},
apply: () => {
settings = {...settings, ...Settings.get()};
- ipcRenderer.send("savesettings", Settings.get());
+ ipcRenderer.send("save-settings", Settings.get());
},
reloadSwitches: () => {
let switches = document.querySelectorAll(".switch");