diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | PUBLISH.md | 15 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rw-r--r-- | package-lock.json | 18 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | src/app/browser.js | 248 | ||||
-rw-r--r-- | src/app/css/dragui.css | 58 | ||||
-rw-r--r-- | src/app/css/launcher.css | 328 | ||||
-rw-r--r-- | src/app/css/popups.css | 371 | ||||
-rw-r--r-- | src/app/css/theming.css | 62 | ||||
-rw-r--r-- | src/app/css/toasts.css | 52 | ||||
-rw-r--r-- | src/app/css/webview.css | 16 | ||||
-rw-r--r-- | src/app/icons/external.png | bin | 0 -> 9764 bytes | |||
-rw-r--r-- | src/app/icons/game.png | bin | 0 -> 1454 bytes | |||
-rw-r--r-- | src/app/icons/language.png | bin | 0 -> 7132 bytes | |||
-rw-r--r-- | src/app/icons/updates.png | bin | 0 -> 10300 bytes | |||
-rw-r--r-- | src/app/index.html | 44 | ||||
-rw-r--r-- | src/app/lang.js | 2 | ||||
-rw-r--r-- | src/app/launcher.js | 56 | ||||
-rw-r--r-- | src/app/main.css | 771 | ||||
-rw-r--r-- | src/app/main.js | 97 | ||||
-rw-r--r-- | src/app/settings.js | 14 | ||||
-rw-r--r-- | src/extras/findgame.js | 12 | ||||
-rw-r--r-- | src/index.js | 171 | ||||
-rw-r--r-- | src/lang/en.json | 13 | ||||
-rw-r--r-- | src/lang/es.json | 13 | ||||
-rw-r--r-- | src/lang/fr.json | 11 | ||||
-rw-r--r-- | src/utils.js | 258 |
28 files changed, 1635 insertions, 1010 deletions
@@ -1,2 +1,4 @@ +.vscode/ + dist/ node_modules/ @@ -1,9 +1,10 @@ # Publishing a new release -1. Make sure your code works! -2. Update `package.json` version -3. Make sure `package.json`'s `repository.url` key references correct repository -4. Ensure application builds correctly with `npm run build:[windows/linux]` -5. Expose `GH_TOKEN` environment var with your Github token -6. Build and publish with `npm run publish:[windows/linux]` -7. Edit the draft release message and publish the new release!
\ No newline at end of file + 1. Make sure your code works! + 2. Update `package.json` version + 3. Make sure `package.json`'s `repository.url` key references correct repository + 4. Ensure application builds correctly with `npm run build:[windows/linux]` + 5. Expose `GH_TOKEN` environment var with your Github token (`build/publish.sh` asks for it) + 6. Build and publish with `npm run publish:[windows/linux]` + - Optionally just use `build/publish.sh`, however that only works on Linux/Systems with a `/bin/sh` file, it also checks whether all files have been localized, and that the version numbers have been updated + 7. Edit the draft release message and publish the new release! @@ -1,7 +1,6 @@ <p align="center"> <img src="src/assets/icons/512x512.png" width="200px"><br> - <a href="https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-Setup-1.4.0.exe"><img src="assets/download.png" width="300px"></a><br> - <br> + <a href="https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-Setup-1.6.0.exe"><img src="assets/download.png" width="300px"></a><br> <a href="FAQ.md">FAQ</a> | <a href="https://github.com/0neGal/viper/releases">Releases</a><br> </p> @@ -16,9 +15,9 @@ Downloads are available on the [releases page](https://github.com/0neGal/viper/r Please note that some versions will update themselves automatically when a new release is available (just like Origin or Steam) and some will NOT, so choose it accordingly. Only the AppImage, Flatpak and Windows Setup/Installer can auto-update. -**Windows:** [`Viper Setup [x.y.z].exe`](https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-Setup-1.4.0.exe) (auto-updates, and is recommanded), [`Viper [x.y.z].exe`](https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-1.4.0.exe) (single executable, no fuss) +**Windows:** [`Viper Setup [x.y.z].exe`](https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-Setup-1.6.0.exe) (auto-updates, and is recommanded), [`Viper [x.y.z].exe`](https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-1.6.0.exe) (single executable, no fuss) -**Linux:** [`.AppImage`](https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-1.4.0.AppImage) or [Flatpak](https://flathub.org/apps/details/com.github._0negal.Viper) (auto-updates), [AUR (unofficial)](https://aur.archlinux.org/packages/viper-bin), [`.deb`](https://github.com/0neGal/viper/releases/download/v1.4.0/viper-1.4.0_amd64.deb), [`.rpm`](https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-1.4.0.x86_64.rpm), [`.tar.gz`](https://github.com/0neGal/viper/releases/download/v1.4.0/Viper-1.4.0.tar.gz) +**Linux:** [`.AppImage`](https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-1.6.0.AppImage) or [Flatpak](https://flathub.org/apps/details/com.github._0negal.Viper) (auto-updates), [AUR (unofficial)](https://aur.archlinux.org/packages/viper-bin), [`.deb`](https://github.com/0neGal/viper/releases/download/v1.6.0/viper-1.6.0_amd64.deb), [`.rpm`](https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-1.6.0.x86_64.rpm), [`.tar.gz`](https://github.com/0neGal/viper/releases/download/v1.6.0/Viper-1.6.0.tar.gz) <a href="https://github.com/0neGal/viper/releases"><img src="https://img.shields.io/github/v/release/0neGal/viper" alt="GitHub release (latest by date)"></a> <img src="https://img.shields.io/github/downloads/0neGal/viper/latest/total" alt="GitHub release downloads (latest by date)"> @@ -72,8 +71,8 @@ Some of the existing launchers are listed below: * Viper - A launcher, updater and mod manager with an easy to use GUI * [ViperSH](https://github.com/0neGal/viper-sh) - A Bourne Shell, CLI only, Northstar updater and mod manager * [Ronin](https://github.com/MindSwipe/ronin) - a CLI only updater - * [laundmo's updater](https://github.com/laundmo/northstar-updater) - another CLI only updater * [VTOL](https://github.com/BigSpice/VTOL) - an updater and manager for mods, very feature rich + * [r2modman](https://github.com/ebkr/r2modmanPlus) - General purpose mod manager, which has support for Northstar ## Development diff --git a/package-lock.json b/package-lock.json index 432cae2..6fce2e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "viper", - "version": "1.4.0", + "version": "1.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "viper", - "version": "1.4.0", + "version": "1.6.0", "license": "GPL-3.0-or-later", "dependencies": { "electron-updater": "^4.6.1", @@ -21,7 +21,7 @@ "unzipper": "^0.10.11" }, "devDependencies": { - "electron": "^16.0.6", + "electron": "^16.2.6", "electron-builder": "^22.5.1" } }, @@ -1033,9 +1033,9 @@ } }, "node_modules/electron": { - "version": "16.0.6", - "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.6.tgz", - "integrity": "sha512-Xs9dYLYhcJf3wXn8m2gDqFTb1L862KEhMxOx9swfFBHj6NoUPPtUgw/RyPQ0tXN1XPxG9vnBkoI0BdcKwrLKuQ==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-16.2.6.tgz", + "integrity": "sha512-FJLnIu318WNh1WigMmWqSidOPwipwym2Qi3Hs/YY6znquztf6ZJuaq/TdJJyHIJHld+znG0hSmq3VbyW5KUr9A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3984,9 +3984,9 @@ } }, "electron": { - "version": "16.0.6", - "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.6.tgz", - "integrity": "sha512-Xs9dYLYhcJf3wXn8m2gDqFTb1L862KEhMxOx9swfFBHj6NoUPPtUgw/RyPQ0tXN1XPxG9vnBkoI0BdcKwrLKuQ==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-16.2.6.tgz", + "integrity": "sha512-FJLnIu318WNh1WigMmWqSidOPwipwym2Qi3Hs/YY6znquztf6ZJuaq/TdJJyHIJHld+znG0hSmq3VbyW5KUr9A==", "dev": true, "requires": { "@electron/get": "^1.13.0", diff --git a/package.json b/package.json index 852f53f..eb80d7e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "viper", "productName": "Viper", - "version": "1.4.0", + "version": "1.6.0", "description": "Launcher+Updater for TF|2 Northstar", "main": "src/index.js", "build": { @@ -60,7 +60,7 @@ "unzipper": "^0.10.11" }, "devDependencies": { - "electron": "^16.0.6", + "electron": "^16.2.6", "electron-builder": "^22.5.1" } } diff --git a/src/app/browser.js b/src/app/browser.js index e22ab3c..4b9f2e3 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])) { @@ -59,8 +87,8 @@ var Browser = { 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,26 +350,30 @@ function BrowserEl(properties) { } } } - - browserEntries.innerHTML += ` - <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 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> + + 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) { @@ -257,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, @@ -267,7 +391,7 @@ ipcRenderer.on("failedmod", (event, modname) => { }) }) -ipcRenderer.on("installedmod", (event, mod) => { +ipcRenderer.on("installed-mod", (event, mod) => { setButtons(true); Browser.setbutton(mod.name, lang("gui.browser.reinstall")); @@ -285,14 +409,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++) { @@ -323,11 +454,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 Binary files differnew file mode 100644 index 0000000..0b4f99e --- /dev/null +++ b/src/app/icons/external.png diff --git a/src/app/icons/game.png b/src/app/icons/game.png Binary files differnew file mode 100644 index 0000000..7db058f --- /dev/null +++ b/src/app/icons/game.png diff --git a/src/app/icons/language.png b/src/app/icons/language.png Binary files differnew file mode 100644 index 0000000..011e1f9 --- /dev/null +++ b/src/app/icons/language.png diff --git a/src/app/icons/updates.png b/src/app/icons/updates.png Binary files differnew file mode 100644 index 0000000..4b505ee --- /dev/null +++ b/src/app/icons/updates.png diff --git a/src/app/index.html b/src/app/index.html index 02a150f..819caac 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%% @@ -48,7 +51,10 @@ <input> </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%% @@ -73,7 +79,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%% @@ -107,6 +116,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> @@ -133,6 +157,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> @@ -186,6 +221,7 @@ <div class="inline"> <div id="nsversion"></div> <a id="update" href="#" onclick="update()">(%%gui.update.check%%)</a> + <div id="serverstatus" class="checking"></div> </div> </div> </div> @@ -229,4 +265,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 51d3a63..fbeb703 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"); @@ -82,7 +86,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"); @@ -107,3 +114,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 99d806c..f39c5a1 100644 --- a/src/app/main.css +++ b/src/app/main.css @@ -1,58 +1,14 @@ -:root { - --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, 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} @@ -68,300 +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.5); - height: var(--height) !important; -} - -#options.popup .misc button { - margin-left: 0px; - width: auto !important; - padding-right: calc(var(--padding) / 2) !important; -} - -.popup .misc button:last-child { - margin-left: 0px !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 .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 .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); -} +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; @@ -384,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} @@ -410,418 +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.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); -} - -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))} -#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); -} - -#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 { @@ -835,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 95b6f4c..9fb3191 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -14,6 +14,7 @@ var settings = { autolang: true, forcedlang: "en", autoupdate: true, + originkill: false, zip: "/northstar.zip", lang: navigator.language, excludes: [ @@ -36,9 +37,9 @@ if (fs.existsSync("viper.json")) { }catch (e) { let reset = confirm(lang("general.invalidconfig", navigator.language) + e); if (! reset) { - ipcRenderer.send("exit") + ipcRenderer.send("exit"); } else { - fs.writeFileSync("viper.json", "{}") + fs.writeFileSync("viper.json", "{}"); ipcRenderer.send("relaunch"); } @@ -76,12 +77,12 @@ function launch() { update(); 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 @@ -100,13 +101,16 @@ 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("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")); @@ -172,7 +176,7 @@ function selected(all) { } } - ipcRenderer.send("removemod", selected) + ipcRenderer.send("remove-mod", selected); }, toggle: () => { if (selected.match(/^Northstar\./)) { @@ -185,33 +189,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() @@ -259,23 +312,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); }); @@ -304,7 +357,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 23b38c9..63b4b99 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"); diff --git a/src/extras/findgame.js b/src/extras/findgame.js index 3beca23..615c5b4 100644 --- a/src/extras/findgame.js +++ b/src/extras/findgame.js @@ -38,13 +38,13 @@ module.exports = async () => { // `.length - 1` This is because the last value is `contentstatsid` for (let i = 0; i < values.length; i++) { - let data_array = Object.values(values[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]) + console.log("Found game in:", data_array[0]); return data_array[0] + "/steamapps/common/Titanfall2"; } else { - console.log("Game not in:", data_array[0]) + console.log("Game not in:", data_array[0]); } } } @@ -69,10 +69,10 @@ module.exports = async () => { 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]) + console.log("Searching VDF file at:", folders[i]); - let data = fs.readFileSync(folders[i]) - let read_vdf = readvdf(data.toString()) + let data = fs.readFileSync(folders[i]); + let read_vdf = readvdf(data.toString()); if (read_vdf) {return read_vdf} } } diff --git a/src/index.js b/src/index.js index 6c3c79c..2c03529 100644 --- a/src/index.js +++ b/src/index.js @@ -3,78 +3,103 @@ const path = require("path"); const { autoUpdater } = require("electron-updater"); const { app, ipcMain, BrowserWindow, dialog } = require("electron"); -const Emitter = require("events"); -const events = new Emitter(); - const utils = require("./utils"); const cli = require("./cli"); const requests = require("./extras/requests"); +var log = console.log; + // Starts the actual BrowserWindow, which is only run when using the // GUI, for the CLI this function is never called. function start() { win = new BrowserWindow({ width: 1000, height: 600, + title: "Viper", // Hides the window initially, it'll be shown when the DOM is // loaded, as to not cause visual issues. show: false, - title: "Viper", // In the future we may want to allow the user to resize the window, // as it's fairly responsive, but for now we won't allow that. resizable: false, - titleBarStyle: "hidden", + frame: false, + titleBarStyle: "hidden", icon: path.join(__dirname, "assets/icons/512x512.png"), webPreferences: { + webviewTag: true, nodeIntegration: true, contextIsolation: false, }, - }); + }); - // When --debug is added it'll open the dev tools + // when --debug is added it'll open the dev tools if (cli.hasParam("debug")) {win.openDevTools()} - // General setup + // general setup win.removeMenu(); win.loadFile(__dirname + "/app/index.html"); + win.send = (channel, data) => { + win.webContents.send(channel, data); + }; send = win.send; + + ipcMain.on("exit", () => { + if (utils.settings.originkill) { + utils.isOriginRunning().then((running) => { + if (running) { + utils.killOrigin().then(process.exit(0)) + } else { + process.exit(0) + } + }) + } else { + process.exit(0) + } + }); + ipcMain.on("minimize", () => {win.minimize()}); + ipcMain.on("relaunch", () => {app.relaunch(); app.exit()}); + + // passthrough to renderer from main + ipcMain.on("win-log", (event, ...args) => {send("log", ...args)}); + ipcMain.on("win-alert", (event, ...args) => {send("alert", ...args)}); + + // mod states + ipcMain.on("failed-mod", (event, modname) => {send("failed-mod", modname)}); + ipcMain.on("removed-mod", (event, modname) => {send("removed-mod", modname)}); + ipcMain.on("gui-getmods", (event, ...args) => {send("mods", utils.mods.list())}); + ipcMain.on("installed-mod", (event, modname) => {send("installed-mod", modname)}); - 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)}); - ipcMain.on("ns-update-event", (event) => win.webContents.send("ns-update-event", event)); - ipcMain.on("failedmod", (event, modname) => {win.webContents.send("failedmod", modname)}); - ipcMain.on("removedmod", (event, modname) => {win.webContents.send("removedmod", modname)}); - ipcMain.on("installedmod", (event, modname) => {win.webContents.send("installedmod", modname)}); - ipcMain.on("guigetmods", (event, ...args) => {win.webContents.send("mods", utils.mods.list())}); + // install calls + ipcMain.on("install-from-path", (event, path) => {utils.mods.install(path)}); + ipcMain.on("install-from-url", (event, url) => {utils.mods.installFromURL(url)}); + + win.webContents.on("dom-ready", () => { + send("mods", utils.mods.list()); + }); + // ensures gamepath still exists and is valid on startup let gamepathlost = false; - ipcMain.on("gamepathlost", (event, ...args) => { + ipcMain.on("gamepath-lost", (event, ...args) => { if (! gamepathlost) { gamepathlost = true; - win.webContents.send("gamepathlost"); + send("gamepath-lost"); } }); - ipcMain.on("savesettings", (event, obj) => {utils.saveSettings(obj)}) + ipcMain.on("save-settings", (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") + // allows renderer to check for updates + ipcMain.on("ns-update-event", (event) => {send("ns-update-event", event)}); + ipcMain.on("can-autoupdate", () => { + if (! autoUpdater.isUpdaterActive() || cli.hasParam("no-vp-updates")) { + send("cant-autoupdate"); } }) - win.webContents.on("dom-ready", () => { - win.webContents.send("mods", utils.mods.list()); - }); - + // start auto-update process if (utils.settings.autoupdate) { if (cli.hasParam("no-vp-updates")) { utils.handleNorthstarUpdating(); @@ -86,41 +111,42 @@ function start() { } autoUpdater.on("update-downloaded", () => { - win.webContents.send("updateavailable") + send("update-available"); }); - // Updates and restarts Viper, if user says yes to do so. - // Otherwise it'll do it on the next start up. - ipcMain.on("updatenow", () => { + // updates and restarts Viper, if user says yes to do so. + // otherwise it'll do it on the next start up. + ipcMain.on("update-now", () => { autoUpdater.quitAndInstall(); }) } // General events used to handle utils.js stuff without requiring the // module inside the file that sent the event. { -ipcMain.on("installmod", () => { +ipcMain.on("install-mod", () => { if (cli.hasArgs()) { - utils.mods.install(cli.param("installmod")) + utils.mods.install(cli.param("installmod")); } else { dialog.showOpenDialog({properties: ["openFile"]}).then(res => { if (res.filePaths.length != 0) { utils.mods.install(res.filePaths[0]); } else { - win.webContents.send("setbuttons", true); + send("set-buttons", true); } - }).catch(err => {console.error(err)}) + }).catch(err => {error(err)}); } }) -ipcMain.on("removemod", (event, mod) => {utils.mods.remove(mod)}) -ipcMain.on("togglemod", (event, mod) => {utils.mods.toggle(mod)}) +ipcMain.on("remove-mod", (event, mod) => {utils.mods.remove(mod)}); +ipcMain.on("toggle-mod", (event, mod) => {utils.mods.toggle(mod)}); + +ipcMain.on("launch-ns", () => {utils.launch()}); +ipcMain.on("launch-vanilla", () => {utils.launch("vanilla")}); -ipcMain.on("launch", (event) => {utils.launch()}) -ipcMain.on("setlang", (event, lang) => {utils.setlang(lang)}) -ipcMain.on("launchVanilla", (event) => {utils.launch("vanilla")}) +ipcMain.on("setlang", (event, lang) => {utils.setlang(lang)}); -ipcMain.on("update", (event) => {utils.update()}) -ipcMain.on("setpathcli", (event) => {utils.setpath()}); +ipcMain.on("update", () => {utils.update()}) +ipcMain.on("setpath-cli", () => {utils.setpath()}); ipcMain.on("setpath", (event, value) => { if (! value) { if (! win.isVisible()) { @@ -133,66 +159,69 @@ ipcMain.on("setpath", (event, value) => { } }); -function _sendVersionsInfo() { - win.webContents.send("version", { +// retrieves various local version numbers +function sendVersionsInfo() { + send("version", { ns: utils.getNSVersion(), tf2: utils.getTF2Version(), vp: "v" + require("../package.json").version }); } -// Sends the version info back to the renderer -ipcMain.on("getversion", () => {_sendVersionsInfo()}); +// sends the version info back to the renderer +ipcMain.on("get-version", () => {sendVersionsInfo()}); -// Prints out version info for the CLI -ipcMain.on("versioncli", () => { - console.log("Viper: v" + require("../package.json").version); - console.log("Northstar: " + utils.getNSVersion()); - console.log("Node: " + process.version); - console.log("Electron: v" + process.versions.electron); +// prints out version info for the CLI +ipcMain.on("version-cli", () => { + log("Viper: v" + require("../package.json").version); + log("Northstar: " + utils.getNSVersion()); + log("Node: " + process.version); + log("Electron: v" + process.versions.electron); cli.exit(); }) -ipcMain.on("getmods", (event) => { +// sends installed mods info to renderer +ipcMain.on("getmods", () => { let mods = utils.mods.list(); if (mods.all.length > 0) { - console.log(`${utils.lang("general.mods.installed")} ${mods.all.length}`) - console.log(`${utils.lang("general.mods.enabled")} ${mods.enabled.length}`) + log(`${utils.lang("general.mods.installed")} ${mods.all.length}`); + log(`${utils.lang("general.mods.enabled")} ${mods.enabled.length}`); for (let i = 0; i < mods.enabled.length; i++) { - console.log(` ${mods.enabled[i].Name} ${mods.enabled[i].Version}`) + log(` ${mods.enabled[i].Name} ${mods.enabled[i].Version}`); } if (mods.disabled.length > 0) { - console.log(`${utils.lang("general.mods.disabled")} ${mods.disabled.length}`) + log(`${utils.lang("general.mods.disabled")} ${mods.disabled.length}`); for (let i = 0; i < mods.disabled.length; i++) { - console.log(` ${mods.disabled[i].Name} ${mods.disabled[i].Version}`) + log(` ${mods.disabled[i].Name} ${mods.disabled[i].Version}`); } } cli.exit(0); } else { - console.log("No mods installed"); + log("No mods installed"); cli.exit(0); } }) // } +// allows renderer to set a new renderer ipcMain.on("newpath", (event, newpath) => { - if (newpath === false && !win.isVisible()) { - win.webContents.send("nopathselected"); + if (newpath === false && ! win.isVisible()) { + win.send("no-path-selected"); } else { _sendVersionsInfo(); if (!win.isVisible()) { win.show(); } } -}); ipcMain.on("wrongpath", (event) => { - win.webContents.send("wrongpath"); +}); ipcMain.on("wrong-path", () => { + win.send("wrong-path"); }); -// Ensures ./ is the config folder where viper.json is located. +// ensures PWD/CWD is the config folder where viper.json is located process.chdir(app.getPath("appData")); -// Starts the GUI or CLI +// starts the GUI or CLI if (cli.hasArgs()) { if (cli.hasParam("updatevp")) { utils.updatevp(true); @@ -206,11 +235,11 @@ if (cli.hasArgs()) { }) } -// Returns cached requests +// returns cached requests ipcMain.on("get-ns-notes", async () => { - win.webContents.send("ns-notes", await requests.getNsReleaseNotes()); + win.send("ns-notes", await requests.getNsReleaseNotes()); }); ipcMain.on("get-vp-notes", async () => { - win.webContents.send("vp-notes", await requests.getVpReleaseNotes()); + win.send("vp-notes", await requests.getVpReleaseNotes()); }); diff --git a/src/lang/en.json b/src/lang/en.json index ee932a2..8cece10 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -68,15 +68,18 @@ "gui.mods.installing": "Installing mod...", "gui.mods.installedmod": "Installed mod!", "gui.mods.dragdrop": "Drag and drop a mod to install", + "gui.mods.confirmdependencies": "This package has dependencies, shown below, clicking \"Ok\" will install the package and the dependencies.\n\n", "gui.browser.info": "Info", + "gui.browser.view": "View", "gui.browser.madeby": "by", "gui.browser.search": "Search...", "gui.browser.update": "Update", "gui.browser.install": "Install", "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.loadmore": "Load more...", + "gui.browser.endoflist": "All packages have been loaded.", "gui.browser.noresults": "No results...", "gui.browser.filter.mods": "Mods", "gui.browser.filter.skins": "Skins", @@ -88,6 +91,7 @@ "gui.settings.title.ns": "Northstar", "gui.settings.title.language": "Language", "gui.settings.title.updates": "Updates", + "gui.settings.title.misc": "Miscellaneous", "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", @@ -100,6 +104,8 @@ "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.settings.originkill.title": "Automatically quit Origin", + "gui.settings.originkill.desc": "When Viper exits, automatically quit Origin as well.", "gui.update.downloading": "Downloading...", "gui.update.extracting": "Extracting update...", @@ -110,6 +116,11 @@ "gui.nsupdate.gaming.title": "Northstar update available!", "gui.nsupdate.gaming.body": "An update for Northstar is available.\nYou can force its installation after closing the game.", + "gui.server.player": "player", + "gui.server.players": "players", + "gui.server.servers": "servers", + "gui.server.offline": "Masterserver is Offline", + "gui.launch": "Launch", "gui.launchvanilla": "Vanilla", "gui.launchnorthstar": "Northstar", diff --git a/src/lang/es.json b/src/lang/es.json index dbccad8..c341143 100644 --- a/src/lang/es.json +++ b/src/lang/es.json @@ -68,6 +68,7 @@ "gui.mods.installing": "Instalando modificación...", "gui.mods.installedmod": "¡Modificación instalada!", "gui.mods.dragdrop": "Arrastra y suelta una modificación para instalarla", + "gui.mods.confirmdependencies": "Este paquete tiene dependencias, se muestran abajo. Presionar \"Ok\" instalará el paquete y las dependencias.\n\n", "gui.browser.info": "Información", "gui.browser.madeby": "por", @@ -76,17 +77,20 @@ "gui.browser.install": "Instalar", "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.endoflist": "Todos los paquetes han sido cargados", "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.browser.view": "Ver", + "gui.browser.loadmore": "Cargar más...", "gui.settings.save": "Guardar", "gui.settings.discard": "Descartar", "gui.settings.title.ns": "Northstar", "gui.settings.title.updates": "Actualizaciones", + "gui.settings.title.misc": "Misceláneos", "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", @@ -95,6 +99,8 @@ "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.originkill.title": "Cerrar Origin automáticamente", + "gui.settings.originkill.desc": "Cuando Viper se cierra, cerrar automáticamente Origin también.", "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.", @@ -127,6 +133,11 @@ "gui.toast.desc.malformed": "tiene una estructura de carpetas incorrecta, si usted es el desarrollador, debe corregir esto.", "gui.toast.desc.failed": "Se produjo un error desconocido al intentar instalar la modificación. Esto puede ser culpa del autor de la modificación, y también puede ser culpa de Viper.", + "gui.server.player": "jugador", + "gui.server.players": "jugadores", + "gui.server.servers": "servidores", + "gui.server.offline": "El servidor Master está desconectado", + "viper.menu.main": "Viper", "viper.menu.release": "Notas de la versión", "viper.menu.info": "Extras", diff --git a/src/lang/fr.json b/src/lang/fr.json index d162988..1fde982 100644 --- a/src/lang/fr.json +++ b/src/lang/fr.json @@ -68,14 +68,17 @@ "gui.mods.installing": "Installation du mod...", "gui.mods.installedmod": "Mod installé !", "gui.mods.dragdrop": "Glissez/déposez un mod pour l'installer", + "gui.mods.confirmdependencies": "Ce mod a des dépendances (affichées ci-dessous), cliquer \"Ok\" les installera en même temps que le mod.\n\n", "gui.browser.info": "Info", + "gui.browser.view": "Voir", "gui.browser.madeby": "par", "gui.browser.search": "Rechercher", "gui.browser.update": "Mise à jour", "gui.browser.install": "Installer", "gui.browser.reinstall": "Réinstaller", "gui.browser.loading": "Chargement des mods...", + "gui.browser.loadmore": "Charger plus de 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", @@ -88,6 +91,7 @@ "gui.settings.title.ns": "Northstar", "gui.settings.title.language": "Langue", "gui.settings.title.updates": "Mises à jour", + "gui.settings.title.misc": "Divers", "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", @@ -100,6 +104,8 @@ "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.settings.originkill.title": "Quitter automatiquement Origin", + "gui.settings.originkill.desc": "Lorsque Viper est fermé, Origin sera également automatiquement fermé.", "gui.update.downloading": "Téléchargement de la mise à jour...", "gui.update.extracting": "Extraction des fichiers...", @@ -110,6 +116,11 @@ "gui.nsupdate.gaming.title": "Mise à jour Northstar disponible !", "gui.nsupdate.gaming.body": "Une mise à jour pour Northstar est disponible.\nVous pourrez l'installer après avoir fermé le jeu.", + "gui.server.player": "joueur", + "gui.server.players": "joueurs", + "gui.server.servers": "serveurs", + "gui.server.offline": "Le serveur maître est hors-ligne", + "gui.launch": "Jouer", "gui.launchvanilla": "Vanilla", "gui.launchnorthstar": "Northstar", diff --git a/src/utils.js b/src/utils.js index eb9f195..e8d14cc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -29,6 +29,7 @@ var settings = { autolang: true, forcedlang: "en", autoupdate: true, + originkill: false, nsargs: "-multiple", zip: "/northstar.zip", @@ -42,12 +43,12 @@ var settings = { // Logs into the dev tools of the renderer function winLog(msg) { - ipcMain.emit("winLog", msg, msg); + ipcMain.emit("win-log", msg, msg); } // Sends an alert to the renderer function winAlert(msg) { - ipcMain.emit("winAlert", msg, msg); + ipcMain.emit("win-alert", msg, msg); } // Creates the settings file with the base settings if it doesn't exist. @@ -101,6 +102,47 @@ async function isGameRunning() { }); } +// checks if any origin processes are running +async function isOriginRunning() { + return new Promise(resolve => { + let procs = ["Origin.exe", "OriginClientService.exe"]; + let cmd = (() => { + switch (process.platform) { + case "linux": return "ps -A"; + case "win32": return "tasklist"; + } + })(); + + exec(cmd, (err, stdout) => { + procs.forEach(proc => { + if (stdout.includes(proc)) { + resolve(true); + return; + } + resolve(false); + }); + }); + }); +} + +// kill origin processes +async function killOrigin() { + return new Promise(resolve => { + let proc = "Origin.exe"; //I'm pretty sure we only have to kill this one + let cmd = (() => { + switch (process.platform) { + case "linux": return "killall " + proc; + case "win32": return "taskkill /IM " + proc + " /F"; + } + })(); + + exec(cmd, (err, stdout) => { + // just try and fail silently if we don't find it w/e + resolve(true); + }); + }); +} + // Handles auto updating Northstar. // // It uses isGameRunning() to ensure it doesn't run while the game is @@ -121,7 +163,7 @@ function handleNorthstarUpdating() { if (await isGameRunning()) { console.log(lang("cli.autoupdates.gamerunning")); new Notification({ - title: lang("gui.nsupdate.gaming.title"), + title: lang("gui.nsupdate.gaming.title"), body: lang("gui.nsupdate.gaming.body") }).show(); } else { @@ -129,11 +171,11 @@ function handleNorthstarUpdating() { update(); } } else { - console.log(lang("cli.autoupdates.noupdate")) + console.log(lang("cli.autoupdates.noupdate")); } setTimeout( - _checkForUpdates, + _checkForUpdates, 15 * 60 * 1000 // interval in between each update check // by default 15 minutes. @@ -187,11 +229,11 @@ async function setpath(win, forcedialog) { return; } if (! fs.existsSync(path.join(res.filePaths[0], "Titanfall2.exe"))) { - ipcMain.emit("wrongpath"); + ipcMain.emit("wrong-path"); return; } - setGamepath(res.filePaths[0]) + setGamepath(res.filePaths[0]); cli.exit(); return; @@ -220,17 +262,57 @@ function saveSettings(obj = {}) { // Returns the current Northstar version // If not installed it'll return "unknown" function getNSVersion() { - var versionFilePath = path.join(settings.gamepath, "ns_version.txt"); + // if NorthstarLauncher.exe doesn't exist, always return "unknown" + if (! fs.existsSync(path.join(settings.gamepath, "NorthstarLauncher.exe"))) { + return "unknown"; + } - if (fs.existsSync(versionFilePath)) { - return fs.readFileSync(versionFilePath, "utf8"); - } else { - if (gamepathExists()) { - fs.writeFileSync(versionFilePath, "unknown"); + // mods to check version of + var versionFiles = [ + "Northstar.Client", + "Northstar.Custom", + "Northstar.CustomServers" + ] + + var versions = []; + + + let add = (version) => { + versions.push(version) + } + + // checks version of mods + for (let i = 0; i < versionFiles.length; i++) { + var versionFile = path.join(settings.gamepath, "R2Northstar/mods/", versionFiles[i],"/mod.json"); + if (fs.existsSync(versionFile)) { + if (! fs.statSync(versionFile).isFile()) { + add("unknown"); + } + + try { + add("v" + JSON.parse(fs.readFileSync(versionFile, "utf8")).Version); + }catch(err) { + add("unknown"); + } + } else { + add("unknown"); } + } - return "unknown"; + if (versions.includes("unknown")) {return "unknown"} + + // verifies all mods have the same version number + let mismatch = false; + let baseVersion = versions[0]; + for (let i = 0; i < versions.length; i++) { + if (versions[i] != baseVersion) { + mismatch = true; + break + } } + + if (mismatch) {return "unknown"} + return baseVersion; } @@ -254,7 +336,7 @@ function restoreExcludedFiles() { for (let i = 0; i < settings.excludes.length; i++) { let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]); if (fs.existsSync(exclude + ".excluded")) { - fs.renameSync(exclude + ".excluded", exclude) + fs.renameSync(exclude + ".excluded", exclude); } } } @@ -278,6 +360,7 @@ async function update() { var version = getNSVersion(); const latestAvailableVersion = await requests.getLatestNsVersion(); + console.log(latestAvailableVersion) // Makes sure it is not already the latest version if (version === latestAvailableVersion) { @@ -290,7 +373,7 @@ async function update() { } else { if (version != "unknown") { console.log(lang("cli.update.current"), version); - }; + }; console.log(lang("cli.update.downloading") + ":", latestAvailableVersion); ipcMain.emit("ns-update-event", "cli.update.downloading"); } @@ -299,7 +382,7 @@ async function update() { for (let i = 0; i < settings.excludes.length; i++) { let exclude = path.join(settings.gamepath + "/" + settings.excludes[i]); if (fs.existsSync(exclude)) { - fs.renameSync(exclude, exclude + ".excluded") + fs.renameSync(exclude, exclude + ".excluded"); } } @@ -318,19 +401,32 @@ async function update() { stream.on("finish", () => { stream.close(); + let extract = fs.createReadStream(settings.zip); + winLog(lang("gui.update.extracting")); ipcMain.emit("ns-update-event", "gui.update.extracting"); console.log(lang("cli.update.downloaddone")); // Extracts the zip, this is the part where we're actually // installing Northstar. - fs.createReadStream(settings.zip).pipe(unzip.Extract({path: settings.gamepath})) - .on("finish", () => { - fs.writeFileSync(path.join(settings.gamepath, "ns_version.txt"), latestAvailableVersion); + extract.pipe(unzip.Extract({path: settings.gamepath})) + + let max = received; + received = 0; + + extract.on("data", (chunk) => { + received += chunk.length; + let percent = Math.floor(received / max * 100); + ipcMain.emit("ns-update-event", lang("gui.update.extracting") + " " + percent + "%"); + }) + + extract.on("end", () => { + extract.close(); ipcMain.emit("getversion"); restoreExcludedFiles(); - ipcMain.emit("guigetmods"); + ipcMain.emit("gui-getmods"); + ipcMain.emit("get-version"); ipcMain.emit("ns-update-event", "cli.update.uptodate.short"); winLog(lang("gui.update.finished")); console.log(lang("cli.update.finished")); @@ -380,19 +476,21 @@ function updatevp(autoinstall) { // however it'll be added at some point. function launch(version) { if (process.platform == "linux") { - console.error("error:", lang("cli.launch.linuxerror")) + winAlert(lang("cli.launch.linuxerror")); + console.error("error:", lang("cli.launch.linuxerror")); cli.exit(1); + return; } process.chdir(settings.gamepath); switch(version) { case "vanilla": - console.log(lang("general.launching"), "Vanilla...") - run(path.join(settings.gamepath + "/Titanfall2.exe")) + console.log(lang("general.launching"), "Vanilla..."); + run(path.join(settings.gamepath + "/Titanfall2.exe")); break; default: - console.log(lang("general.launching"), "Northstar...") - run(path.join(settings.gamepath + "/NorthstarLauncher.exe")) + console.log(lang("general.launching"), "Northstar..."); + run(path.join(settings.gamepath + "/NorthstarLauncher.exe")); break; } } @@ -418,8 +516,8 @@ const mods = { let modpath = path.join(settings.gamepath, "R2Northstar/mods"); if (getNSVersion() == "unknown") { - winLog(lang("general.notinstalled")) - console.log("error: " + lang("general.notinstalled")) + winLog(lang("general.notinstalled")); + console.log("error: " + lang("general.notinstalled")); cli.exit(1); return false; } @@ -428,7 +526,7 @@ const mods = { let disabled = []; if (! fs.existsSync(modpath)) { - fs.mkdirSync(path.join(modpath), {recursive: true}) + fs.mkdirSync(path.join(modpath), {recursive: true}); return { enabled: [], disabled: [], @@ -436,7 +534,7 @@ const mods = { }; } - files = fs.readdirSync(modpath) + files = fs.readdirSync(modpath); files.forEach((file) => { if (fs.statSync(path.join(modpath, file)).isDirectory()) { let modjson = path.join(modpath, file, "mod.json"); @@ -487,8 +585,8 @@ const mods = { let modpath = path.join(settings.gamepath, "R2Northstar/mods"); if (getNSVersion() == "unknown") { - winLog(lang("general.notinstalled")) - console.log("error: " + lang("general.notinstalled")) + winLog(lang("general.notinstalled")); + console.log("error: " + lang("general.notinstalled")); cli.exit(1); return false; } @@ -513,11 +611,11 @@ const mods = { let file = path.join(modpath, "..", "enabledmods.json"); if (! fs.existsSync(modpath)) { - fs.mkdirSync(path.join(modpath), {recursive: true}) + fs.mkdirSync(path.join(modpath), {recursive: true}); } if (! fs.existsSync(file)) { - fs.writeFileSync(file, "{}") + fs.writeFileSync(file, "{}"); } return { @@ -528,7 +626,7 @@ const mods = { names[list[i].Name] = true } - fs.writeFileSync(file, JSON.stringify(names)) + fs.writeFileSync(file, JSON.stringify(names)); }, disable: (mod) => { let data = JSON.parse(repair(fs.readFileSync(file, "utf8"))); @@ -574,15 +672,15 @@ const mods = { let modname = mod.replace(/^.*(\\|\/|\:)/, ""); if (getNSVersion() == "unknown") { - winLog(lang("general.notinstalled")) - console.log("error: " + lang("general.notinstalled")) + winLog(lang("general.notinstalled")); + console.log("error: " + lang("general.notinstalled")); cli.exit(1); return false; } let notamod = () => { - winLog(lang("gui.mods.notamod")) - console.log("error: " + lang("cli.mods.notamod")) + winLog(lang("gui.mods.notamod")); + console.log("error: " + lang("cli.mods.notamod")); cli.exit(1); return false; } @@ -591,30 +689,30 @@ const mods = { console.log(lang("cli.mods.installed")); cli.exit(); - winLog(lang("gui.mods.installedmod")) + winLog(lang("gui.mods.installedmod")); if (modname == "mods") { - let manifest = path.join(app.getPath("userData"), "Archives/manifest.json") + let manifest = path.join(app.getPath("userData"), "Archives/manifest.json"); if (fs.existsSync(manifest)) { modname = require(manifest).name; } } - ipcMain.emit("installedmod", "", { + ipcMain.emit("installed-mod", "", { name: modname, malformed: malformed, }); - ipcMain.emit("guigetmods"); + ipcMain.emit("gui-getmods"); return true; } if (! fs.existsSync(mod)) {return notamod()} if (fs.statSync(mod).isDirectory()) { - winLog(lang("gui.mods.installing")) + winLog(lang("gui.mods.installing")); files = fs.readdirSync(mod); - if (fs.existsSync(path.join(mod, "mod.json")) && + if (fs.existsSync(path.join(mod, "mod.json")) && fs.statSync(path.join(mod, "mod.json")).isFile()) { if (fs.existsSync(path.join(modpath, modname))) { @@ -622,8 +720,8 @@ const mods = { } let copydest = path.join(modpath, modname); if (typeof destname == "string") {copydest = path.join(modpath, destname)} - copy(mod, copydest) - copy(manifestfile, path.join(copydest, "manifest.json")) + copy(mod, copydest); + copy(manifestfile, path.join(copydest, "manifest.json")); return installed(); } else { @@ -634,7 +732,7 @@ const mods = { if (fs.existsSync(path.join(mod, files[i], "mod.json")) && fs.statSync(path.join(mod, files[i], "mod.json")).isFile()) { - mods.install(path.join(mod, files[i])) + mods.install(path.join(mod, files[i])); if (mods.install(path.join(mod, files[i]))) {return true}; } } @@ -645,7 +743,7 @@ const mods = { return notamod(); } else { - winLog(lang("gui.mods.extracting")) + winLog(lang("gui.mods.extracting")); let cache = path.join(app.getPath("userData"), "Archives"); if (fs.existsSync(cache)) { fs.rmSync(cache, {recursive: true}); @@ -677,7 +775,7 @@ const mods = { } if (files.length == 0) { - ipcMain.emit("failedmod"); + ipcMain.emit("failed-mod"); return notamod(); } } @@ -708,26 +806,18 @@ const mods = { if (fs.existsSync(tmp)) { if (! fs.statSync(tmp).isDirectory()) { - fs.rmSync(tmp) + fs.rmSync(tmp); } } else { - fs.mkdirSync(tmp) + fs.mkdirSync(tmp); if (fs.existsSync(modlocation)) { - fs.rmSync(modlocation) + fs.rmSync(modlocation); } } let stream = fs.createWriteStream(modlocation); res.pipe(stream); - // let received = 0; - // // Progress messages, we should probably switch this to - // // percentage instead of how much is downloaded. - // res.on("data", (chunk) => { - // received += chunk.length; - // ipcMain.emit("ns-update-event", lang("gui.update.downloading") + " " + (received / 1024 / 1024).toFixed(1) + "mb"); - // }) - stream.on("finish", () => { stream.close(); mods.install(modlocation); @@ -743,8 +833,8 @@ const mods = { let modpath = path.join(settings.gamepath, "R2Northstar/mods"); if (getNSVersion() == "unknown") { - winLog(lang("general.notinstalled")) - console.log("error: " + lang("general.notinstalled")) + winLog(lang("general.notinstalled")); + console.log("error: " + lang("general.notinstalled")); cli.exit(1); return false; } @@ -752,19 +842,19 @@ const mods = { if (mod == "allmods") { let modlist = mods.list().all; for (let i = 0; i < modlist.length; i++) { - mods.remove(modlist[i].Name) + mods.remove(modlist[i].Name); } return } let disabled = path.join(modpath, "disabled"); if (! fs.existsSync(disabled)) { - fs.mkdirSync(disabled) + fs.mkdirSync(disabled); } let modName = mods.get(mod).FolderName; if (! modName) { - console.log("error: " + lang("cli.mods.cantfind")) + console.log("error: " + lang("cli.mods.cantfind")); cli.exit(1); return; } @@ -784,8 +874,8 @@ const mods = { fs.rmSync(modPath, {recursive: true}); console.log(lang("cli.mods.removed")); cli.exit(); - ipcMain.emit("guigetmods"); - ipcMain.emit("removedmod", "", { + ipcMain.emit("gui-getmods"); + ipcMain.emit("removed-mod", "", { name: mod.replace(/^.*(\\|\/|\:)/, ""), manifestname: manifestname }); @@ -802,8 +892,8 @@ const mods = { // function. However we currently have no need for that. toggle: (mod, fork) => { if (getNSVersion() == "unknown") { - winLog(lang("general.notinstalled")) - console.log("error: " + lang("general.notinstalled")) + winLog(lang("general.notinstalled")); + console.log("error: " + lang("general.notinstalled")); cli.exit(1); return false; } @@ -811,7 +901,7 @@ const mods = { if (mod == "allmods") { let modlist = mods.list().all; for (let i = 0; i < modlist.length; i++) { - mods.toggle(modlist[i].Name, true) + mods.toggle(modlist[i].Name, true); } console.log(lang("cli.mods.toggledall")); @@ -824,17 +914,17 @@ const mods = { console.log(lang("cli.mods.toggled")); cli.exit(); } - ipcMain.emit("guigetmods"); + ipcMain.emit("gui-getmods"); } }; setInterval(() => { if (gamepathExists()) { - ipcMain.emit("guigetmods"); + ipcMain.emit("gui-getmods"); } else { if (fs.existsSync("viper.json")) { if (settings.gamepath != "") { - ipcMain.emit("gamepathlost"); + ipcMain.emit("gamepath-lost"); } } } @@ -842,19 +932,27 @@ setInterval(() => { module.exports = { mods, - lang, winLog, - launch, + update, - setpath, updatevp, - settings, - saveSettings, getNSVersion, getTF2Version, + handleNorthstarUpdating, + + launch, + killOrigin, isGameRunning, + isOriginRunning, + + + settings, + saveSettings, + + setpath, gamepathExists, - handleNorthstarUpdating, + + lang, setlang: (lang) => { settings.lang = lang; saveSettings(); |