aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2022-02-08 21:15:38 +0100
committerGitHub <noreply@github.com>2022-02-08 21:15:38 +0100
commit70e50148c8a4315c7fc25d5052f9ebee1c5d56bc (patch)
tree5670e225378ebcb0e73efb0c84265357f74f00cb /src/app
parente2aa874f48dc585f0d67c5655df234bdf356cc53 (diff)
parent1bd83cf1a72332e142bb63acab47ac3877061875 (diff)
downloadViper-70e50148c8a4315c7fc25d5052f9ebee1c5d56bc.tar.gz
Viper-70e50148c8a4315c7fc25d5052f9ebee1c5d56bc.zip
Merge pull request #50 from 0neGal/thunderstore
Thunderstore support
Diffstat (limited to 'src/app')
-rw-r--r--src/app/browser.js235
-rw-r--r--src/app/icons/no-image.pngbin0 -> 1959 bytes
-rw-r--r--src/app/index.html15
-rw-r--r--src/app/main.css244
-rw-r--r--src/app/main.js18
5 files changed, 479 insertions, 33 deletions
diff --git a/src/app/browser.js b/src/app/browser.js
new file mode 100644
index 0000000..5062d86
--- /dev/null
+++ b/src/app/browser.js
@@ -0,0 +1,235 @@
+const Fuse = require("fuse.js");
+var fuse;
+var packages = [];
+
+var Browser = {
+ maxentries: 50,
+ toggle: (state) => {
+ if (state) {
+ browser.scrollTo(0, 0);
+ overlay.classList.add("shown")
+ browser.classList.add("shown")
+ return
+ } else if (! state) {
+ if (state != undefined) {
+ overlay.classList.remove("shown")
+ browser.classList.remove("shown")
+ return
+ }
+ }
+
+ browser.scrollTo(0, 0);
+ overlay.classList.toggle("shown")
+ browser.classList.toggle("shown")
+ },
+ loadfront: async () => {
+ Browser.loading();
+
+ if (packages.length < 1) {
+ packages = await (await fetch("https://northstar.thunderstore.io/api/v1/package/")).json();
+
+ fuse = new Fuse(packages, {
+ keys: ["full_name"]
+ })
+ }
+
+ for (let i in packages) {
+ if (i == Browser.maxentries) {Browser.endoflist();break}
+ new BrowserElFromObj(packages[i]);
+ }
+ },
+ loading: (string) => {
+ if (string) {
+ browserEntries.innerHTML = `<div class="loading">${string}</div>`;
+ }
+
+ if (! browserEntries.querySelector(".loading")) {
+ browserEntries.innerHTML = `<div class="loading">${lang('gui.browser.loading')}</div>`;
+ }
+ },
+ endoflist: () => {
+ browserEntries.innerHTML += `<div class="message">${lang('gui.browser.endoflist')}</div>`
+ },
+ search: (string) => {
+ Browser.loading();
+ let res = fuse.search(string);
+
+ if (res.length < 1) {
+ Browser.loading("No results...")
+ 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}`);
+
+ for (let i = 0; i < elems.length; i++) {
+ elems[i].querySelector(".text button").innerHTML = string;
+ }
+ } else {
+ let make = (str) => {
+ if (document.getElementById(str)) {
+ return Browser.setbutton(str, string);
+ } else {
+ return false;
+ }
+ }
+
+ setTimeout(() => {
+ for (let i = 0; i < modsobj.all.length; i++) {
+ let modname = normalize(modsobj.all[i].Name);
+ let modfolder = normalize(modsobj.all[i].FolderName);
+
+ if (mod.includes(modname)) {
+ if (! make(modname)) {
+ if (modsobj.all[i].ManifestName) {
+ make(normalize(modsobj.all[i].ManifestName));
+ }
+ }
+ }
+ else if (mod.includes(modfolder)) {make(modfolder);break}
+ }
+ }, 1501)
+ }
+ }
+}; Browser.loadfront()
+
+document.body.addEventListener("keyup", (e) => {
+ if (e.key == "Escape") {Browser.toggle(false)}
+})
+
+function BrowserElFromObj(obj) {
+ let pkg = {...obj, ...obj.versions[0]};
+
+ new BrowserEl({
+ title: pkg.name,
+ image: pkg.icon,
+ author: pkg.owner,
+ url: pkg.package_url,
+ download: pkg.download_url,
+ version: pkg.version_number,
+ description: pkg.description
+ })
+}
+
+function BrowserEl(properties) {
+ properties = {
+ title: "No name",
+ version: "1.0.0",
+ image: "icons/no-image.png",
+ author: "Unnamed Pilot",
+ description: "No description",
+ ...properties
+ }
+
+ if (properties.version[0] != "v") {
+ properties.version = "v" + properties.version;
+ }
+
+ if (browserEntries.querySelector(".loading")) {
+ browserEntries.innerHTML = "";
+ }
+
+ let installstr = lang("gui.browser.install");
+
+ if (normalize(modsdiv.innerText.split("\n")).includes(normalize(properties.title))) {
+ installstr = lang("gui.browser.reinstall");
+
+ for (let i = 0; i < modsobj.all.length; i++) {
+ if (normalize(modsobj.all[i].Name) == normalize(properties.title)
+ && "v" + modsobj.all[i].Version != properties.version) {
+
+ installstr = lang("gui.browser.update");
+ }
+ }
+ } else {
+ for (let i = 0; i < modsobj.all.length; i++) {
+ let title = normalize(properties.title);
+ let folder = normalize(modsobj.all[i].FolderName);
+ let manifestname = null;
+ if (modsobj.all[i].ManifestName) {
+ manifestname = normalize(modsobj.all[i].ManifestName);
+ }
+
+ if (title.includes(folder) || title.includes(manifestname)) {
+ installstr = lang("gui.browser.reinstall");
+
+ if (folder == title
+ && "v" + modsobj.all[i].Version != properties.version) {
+
+ installstr = lang("gui.browser.update");
+ }
+ }
+ }
+ }
+
+ browserEntries.innerHTML += `
+ <div class="el" id="${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>
+ </div>
+ `
+}
+
+ipcRenderer.on("removedmod", (event, mod) => {
+ setButtons(true);
+ Browser.setbutton(mod.name, lang("gui.browser.install"));
+ if (mod.manifestname) {
+ Browser.setbutton(mod.manifestname, lang("gui.browser.install"));
+ }
+})
+
+ipcRenderer.on("installedmod", (event, modname) => {
+ setButtons(true);
+ Browser.setbutton(modname, lang("gui.browser.reinstall"));
+})
+
+function normalize(items) {
+ let main = (string) => {
+ return string.replaceAll(" ", "").replaceAll(".", "").replaceAll("-", "").replaceAll("_", "").toLowerCase()
+ }
+ if (typeof items == "string") {
+ return main(items)
+ } else {
+ let newArray = [];
+ for (let i = 0; i < items.length; i++) {
+ newArray.push(main(items[i]));
+ }
+
+ return newArray;
+ }
+}
+
+let searchtimeout;
+let searchstr = "";
+search.addEventListener("keyup", () => {
+ clearTimeout(searchtimeout);
+
+ if (searchstr != search.value) {
+ if (search.value.replaceAll(" ", "") == "") {
+ searchstr = "";
+ Browser.loadfront();
+ return
+ }
+
+ searchtimeout = setTimeout(() => {
+ Browser.search(search.value);
+ searchstr = search.value;
+ }, 500)
+ }
+})
diff --git a/src/app/icons/no-image.png b/src/app/icons/no-image.png
new file mode 100644
index 0000000..43265d1
--- /dev/null
+++ b/src/app/icons/no-image.png
Binary files differ
diff --git a/src/app/index.html b/src/app/index.html
index fe383a0..b861736 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -12,6 +12,19 @@
<div id="minimize" onclick="ipcRenderer.send('minimize')"></div>
<div id="close" onclick="ipcRenderer.send('exit')"></div>
</div>
+
+ <div id="overlay" onclick="Browser.toggle(false)"></div>
+ <div id="browser">
+ <div id="misc">
+ <input id="search" placeholder="%%gui.browser.search%%">
+ <button id="close" onclick="Browser.toggle(false)">
+ <img src="icons/close.png">
+ </button>
+ </div>
+ <div id="browserEntries">
+ <div class="loading">%%gui.browser.loading%%</div>
+ </div>
+ </div>
<nav class="gamesContainer">
<button id="vpBtn" onclick="page(0)"></button>
@@ -79,6 +92,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>
</div>
</div>
</div>
@@ -102,6 +116,7 @@
<script src="lang.js"></script>
<script src="main.js"></script>
+ <script src="browser.js"></script>
<script src="launcher.js"></script>
</body>
</html>
diff --git a/src/app/main.css b/src/app/main.css
index de0db46..94ef767 100644
--- a/src/app/main.css
+++ b/src/app/main.css
@@ -9,7 +9,11 @@
--selbg: rgba(80, 80, 80, 0.5);
--redbg: linear-gradient(45deg, var(--red), #FA4343);
--bluebg: linear-gradient(45deg, var(--blue), #7380ED);
+}
+#browser, #modsdiv {
+ outline: 1px solid #444444;
+ border: 3px solid var(--bg);
}
::-webkit-scrollbar {
@@ -26,12 +30,191 @@
background: var(--red);
}
+::selection {
+ color: black;
+ background: var(--red);
+}
+
+body {
+ margin: 0;
+ overflow: hidden;
+ user-select: none;
+}
+
+body, button, input {font-family: "Roboto", sans-serif}
+
+button {outline: none}
+b, strong {font-weight: 700}
+body, input, button {font-weight: 500}
+
+button {
+ border: none;
+ color: white;
+ outline: none;
+ cursor: pointer;
+ font-weight: 700;
+ padding: 5px 10px;
+ border-radius: 5px;
+ transition: 0.2s ease-in-out;
+}
+
.playBtn, .gamesContainer button, #winbtns div {
cursor: pointer;
}
-#winbtns {
+#browser {
+ --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;
+}
+
+#browser.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}
+}
+
+#browser .el, #browser #misc, #browser .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;
+}
+
+#browser .el, #browser #search, #browser #close {
+ 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);
+}
+
+#browser #misc, #browser #search {
+ --height: var(--mischeight);
+}
+
+#browser #misc {
+ display: flex;
+}
+
+#browser #search {
+ border: none;
+ outline: none;
+ transition: filter 0.15s ease-in-out;
+ width: calc(100% - var(--spacing) * 2);
+}
+
+#browser #search:focus {
+ filter: brightness(1.5);
+}
+
+#browser #close {
+ --height: calc(var(--padding) * 1.5);
+
+ padding: 0px;
+ margin-left: 0px;
+ width: var(--height);
+}
+
+#browser #close img {
+ opacity: 0.6;
+ width: var(--height);
+ height: var(--height);
+ transform: scale(0.6);
+}
+
+#browser .loading {
+ width: 100%;
+ color: white;
+ display: flex;
+ text-align: center;
+ align-items: center;
+ justify-content: center;
+ height: calc(100% - var(--mischeight) - var(--height));
+}
+
+#browser .message {
+ color: white;
+ text-align: center;
+ margin: var(--padding);
+ width: calc(100% - var(--padding));
+}
+
+#browser .el .image, #browser .el .image img {
+ width: var(--height);
+ height: var(--height);
+ margin-right: var(--spacing);
+ border-radius: var(--spacing);
+}
+
+#browser .el .text {
+ overflow: hidden;
+}
+
+#browser .el .title, #browser .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);
+ margin-top: var(--spacing);
+}
+
+#winbtns {
+ z-index: 2;
display: flex;
position: fixed;
top: var(--padding);
@@ -54,40 +237,18 @@
#winbtns div:hover {opacity: 1.0}
#winbtns div:active {transform: scale(0.98)}
-body {
- margin: 0;
- overflow: hidden;
- user-select: none;
- font-family: "Roboto", sans-serif;
-}
-
-button {outline: none}
-b, strong {font-weight: 700}
-body, input, button {font-weight: 500}
-
-button {
- border: none;
- color: white;
- outline: none;
- font-weight: 700;
- padding: 5px 10px;
- border-radius: 5px;
- transition: 0.2s ease-in-out;
-}
-
button:hover {filter: brightness(110%)}
button:active {filter: brightness(90%)}
img {pointer-events: none}
#bgHolder {
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- position: fixed;
- transform: scale(1.1);
+ top: -5px;
+ left: -5px;
+ right: -5px;
+ bottom: -5px;
+ z-index: -1;
+ position: absolute;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
@@ -162,7 +323,7 @@ img {pointer-events: none}
}
.contentContainer {
- width: 90%;
+ width: 85%;
color: white;
flex-grow: 1;
opacity: 1.0;
@@ -346,6 +507,12 @@ button:disabled {
pointer-events: none;
}
+button.visual {
+ opacity: 1.0;
+ pointer-events: none;
+ background: transparent !important;
+}
+
code {
font-size: 16px;
padding: 2px 5px;
@@ -385,16 +552,27 @@ code {
}
.modbtns button {
- margin-left: calc(var(--padding) / 3);
+ margin-left: var(--spacing);
+ --spacing: calc(var(--padding) / 3);
+ margin-top: calc(var(--spacing) / 2);
+ margin-bottom: calc(var(--spacing) / 2);
}
/* drag control */
-#bgHolder {
+#bgHolder,
+.contentContainer,
+.gamesContainer {
user-select: none;
-webkit-app-region: drag;
}
-a, button, .contentMenu, #close, #nsRelease, #vpReleaseNotes, .mod {
+#overlay.shown ~ #bgHolder,
+#overlay.shown ~ .contentContainer,
+#overlay.shown ~ .gamesContainer {
+ -webkit-app-region: no-drag;
+}
+
+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 19f6c9e..169f86f 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -3,6 +3,7 @@ const path = require("path");
const { ipcRenderer, shell } = require("electron");
const lang = require("../lang");
+var modsobj = {};
let shouldInstallNorthstar = false;
// Base settings
@@ -66,6 +67,15 @@ function log(msg) {
// updating/installing Northstar.
function setButtons(state) {
playNsBtn.disabled = !state;
+
+ let disablearray = (array) => {
+ for (let i = 0; i < array.length; i++) {
+ array[i].disabled = !state;
+ }
+ }
+
+ disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button"))
+ disablearray(document.querySelectorAll("#browser #browserEntries .text button"))
}
// Frontend part of updating Northstar
@@ -148,9 +158,16 @@ function selected(all) {
// Tells the main process to install a mod
function installmod() {
+ setButtons(false);
ipcRenderer.send("installmod")
}
+// Tells the main process to install a mod from a URL
+function installFromURL(url) {
+ setButtons(false);
+ ipcRenderer.send("installfromurl", url)
+}
+
// Frontend part of settings a new game path
ipcRenderer.on("newpath", (event, newpath) => {
settings.gamepath = newpath;
@@ -163,6 +180,7 @@ ipcRenderer.on("alert", (event, msg) => {alert(msg)})
// Updates the installed mods
ipcRenderer.on("mods", (event, mods) => {
+ modsobj = mods;
if (! mods) {return}
modcount.innerHTML = `${lang("gui.mods.count")} ${mods.all.length}`;