-
Welcome to Tauri!
+
+
+
+
SteamDepotDownloaderGUI
+
+
+
+
+
+
-
+
+
+
+
Steam Depot Downloader
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Set location
+
+
+
+ Open location
+
+
+
+ Busy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
dotnet was not found.
+
+
+
+
+ Download
+
+
+
+
+
+
+
+
+
+ Please fill in all required fields.
+
+
+
+
+
+
+
+ Downloading and extracting DepotDownloader
+
+
-
+
+
+
+
+
UNKNOWN
+
00pium
+
Settings
+
+
Appearance
+
+
+
Output
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
deleted file mode 100644
index 9a7530b8..00000000
--- a/src/main.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const { invoke } = window.__TAURI__.core;
-
-let greetInputEl;
-let greetMsgEl;
-
-async function greet() {
- // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
- greetMsgEl.textContent = await invoke("greet", { name: greetInputEl.value });
-}
-
-window.addEventListener("DOMContentLoaded", () => {
- greetInputEl = document.querySelector("#greet-input");
- greetMsgEl = document.querySelector("#greet-msg");
- document.querySelector("#greet-form").addEventListener("submit", (e) => {
- e.preventDefault();
- greet();
- });
-});
diff --git a/src/styles.css b/src/styles.css
deleted file mode 100644
index f7de85bf..00000000
--- a/src/styles.css
+++ /dev/null
@@ -1,109 +0,0 @@
-:root {
- font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
- font-size: 16px;
- line-height: 24px;
- font-weight: 400;
-
- color: #0f0f0f;
- background-color: #f6f6f6;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- -webkit-text-size-adjust: 100%;
-}
-
-.container {
- margin: 0;
- padding-top: 10vh;
- display: flex;
- flex-direction: column;
- justify-content: center;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: 0.75s;
-}
-
-.logo.tauri:hover {
- filter: drop-shadow(0 0 2em #24c8db);
-}
-
-.row {
- display: flex;
- justify-content: center;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-
-a:hover {
- color: #535bf2;
-}
-
-h1 {
- text-align: center;
-}
-
-input,
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- color: #0f0f0f;
- background-color: #ffffff;
- transition: border-color 0.25s;
- box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
-}
-
-button {
- cursor: pointer;
-}
-
-button:hover {
- border-color: #396cd8;
-}
-button:active {
- border-color: #396cd8;
- background-color: #e8e8e8;
-}
-
-input,
-button {
- outline: none;
-}
-
-#greet-input {
- margin-right: 5px;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- color: #f6f6f6;
- background-color: #2f2f2f;
- }
-
- a:hover {
- color: #24c8db;
- }
-
- input,
- button {
- color: #ffffff;
- background-color: #0f0f0f98;
- }
- button:active {
- background-color: #0f0f0f69;
- }
-}
diff --git a/src/ts/main.ts b/src/ts/main.ts
new file mode 100644
index 00000000..1cf53e72
--- /dev/null
+++ b/src/ts/main.ts
@@ -0,0 +1,215 @@
+import $ from "jquery";
+import {invoke} from "@tauri-apps/api/core";
+import {open as openDialog} from "@tauri-apps/plugin-dialog";
+import {open as openShell} from "@tauri-apps/plugin-shell";
+import {listen} from "@tauri-apps/api/event";
+
+function setLoader(state: boolean) {
+ $("#busy").prop("hidden", !state);
+}
+
+
+function setLoadingState(state: boolean) {
+ $("#busy").prop("hidden", !state);
+
+ // loop through all buttons and input fields and disable them
+ for (const element of document.querySelectorAll("button, input")) {
+ if (element.closest("#settings-content")) continue;
+ (element as any).disabled = state;
+ }
+
+ // These elements need additional properties to be properly disabled
+ $("#pickpath").prop("ariaDisabled", state);
+ $("#downloadbtn").prop("ariaDisabled", state);
+
+ // disable internet buttons
+ for (const element of document.querySelectorAll("#internet-btns div")) {
+ element.ariaDisabled = String(state);
+ }
+}
+
+
+/// Returns list of IDs of invalid form fields
+const invalidFields = () => {
+ const form = document.forms[0];
+
+ const invalidFields: string[] = [];
+ for (const input of form) {
+ const inputElement = input as HTMLInputElement;
+ const valid = !(inputElement.value === "" && inputElement?.parentElement?.classList.contains("required"));
+ if (!valid) {
+ invalidFields.push(inputElement.id);
+ }
+ }
+ // console.debug(`[${invalidFields.join(", ")}] fields invalid/empty`);
+
+ return invalidFields;
+};
+
+
+$(async () => {
+ let terminalsCollected = false;
+ let downloadDirectory: string | null;
+
+ // Startup logic
+ setLoadingState(true);
+
+ await invoke("preload_vectum");
+
+ setLoadingState(false);
+
+
+ // Collect the rest of the terminals in the background.
+ if (!terminalsCollected) {
+ setLoader(true);
+ // @ts-ignore
+ const terminals = await invoke("get_all_terminals") as string[];
+ for (const terminal in terminals) {
+ console.log(terminal);
+ }
+
+ // Allow opening settings now that it is ready to be shown.
+ $("#settings-button").prop("ariaDisabled", false);
+ terminalsCollected = true;
+ setLoader(false);
+ }
+
+ $("#pickpath").on("click", async () => {
+ // Open a dialog
+ downloadDirectory = await openDialog({
+ title: "Choose where to download the game. You can specify the directory later.",
+ multiple: false,
+ directory: true,
+ canCreateDirectories: true
+ });
+
+ if (downloadDirectory == null) {
+ // user cancelled
+ $("#checkpath").prop("ariaDisabled", true);
+ $("#checkpath").prop("disabled", true);
+ return;
+ }
+
+ $("#checkpath").prop("ariaDisabled", false);
+ $("#checkpath").prop("disabled", false);
+
+ console.log(downloadDirectory);
+ });
+
+ $("#checkpath").on("click", async () => {
+ console.log(`Checking path: ${downloadDirectory}`);
+
+ if (downloadDirectory != null) {
+ await openShell(downloadDirectory);
+ } else {
+ $("#checkpath").prop("ariaDisabled", true);
+ }
+ });
+
+ $("#downloadbtn").on("click", async () => {
+ console.log("download button clicked");
+
+ if (invalidFields().length > 0) {
+ // Loop through invalid fields. If there are any, make those "errored" and block the download button.
+ for (const id of invalidFields()) {
+ document.getElementById(id)?.parentElement?.classList.toggle("errored", true);
+ $("#emptywarning").prop("hidden", false);
+ $("#downloadbtn").prop("ariaDisabled", true);
+ }
+ return;
+ }
+
+ setLoadingState(true);
+ $("#downloadingnotice").prop("hidden", false);
+ $("#busy").prop("hidden", true); // Don't show the loader this time.
+
+ const terminalChoice = (document.getElementById("terminal-dropdown") as HTMLSelectElement).selectedIndex;
+ const directoryNameChoice = $("#folder-name-custom-input").val();
+
+
+ // Output path w/ directories chosen is: {downloadDirectory}/{directoryNameChoice}
+ const vectumOptions = {
+ terminal: terminalChoice == 14 ? null : terminalChoice,
+ output_directory: downloadDirectory || null, // if not specified let backend choose a path.
+ directory_name: directoryNameChoice || null,
+ };
+
+ const steamDownload = {
+ // String || null translate to Some(String) || None
+ username: String($("#username").val()).trim() || null,
+ password: String($("#password").val()).trim() || null,
+ app_id: $("#appid").val(),
+ depot_id: $("#depotid").val(),
+ manifest_id: $("#manifestid").val(),
+ options: vectumOptions
+ };
+
+ console.log(steamDownload);
+ await invoke("download_depotdownloader");
+
+ $("#downloadingnotice").prop("hidden", true);
+ setLoadingState(false);
+
+ console.debug("DepotDownloader download process completed. Starting game download...");
+
+ await invoke("start_download", {steamDownload: steamDownload});
+ console.log("All done. Ready for next game");
+ });
+
+ $("#settings-button").on("click", async () => {
+ if (terminalsCollected) $("#settings-surrounding").css("display", "block");
+ });
+
+ $("#settings-surrounding").on("click", (event) => {
+ if (event.target === document.getElementById("settings-surrounding")) {
+ $("#settings-surrounding").css("display", "none");
+
+ }
+ });
+
+ $("#opium-btn").on("click", () => {
+ openShell("https://00pium.net");
+ });
+
+
+ document.forms[0].addEventListener("input", (event) => {
+ // Remove errored class. This is a bad way to do it, but it works for now.
+ const target = event.target as HTMLElement;
+ target?.parentElement?.classList.toggle("errored", false);
+
+ // If there are no more invalid fields, hide the warning and enable the download button again
+ if (invalidFields().length === 0) {
+ $("#emptywarning").prop("hidden", true);
+ $("#downloadbtn").prop("ariaDisabled", false);
+ }
+ });
+});
+
+
+let a = 0;
+// Each terminal that is installed gets received from rust with this event.
+listen<[number, number]>("working-terminal", (event) => {
+ a++;
+ console.log(
+ `Terminal #${event.payload[0]} is installed. a = ${a}`
+ );
+ const terminalSelection = (document.getElementById("terminal-dropdown") as HTMLSelectElement);
+
+ // Enable the
of the terminal because we know it is available. Ignore null check because we know it is valid.
+ // @ts-ignore
+ terminalSelection.options.item(event.payload[0]).disabled = false;
+ // @ts-ignore 16
+
+ terminalSelection.options.item(event.payload[0]).text = terminalSelection.options.item(event.payload[0]).text.slice(0,-16);
+
+ $("#terminals-found").text(`${a}/${event.payload[1]}`);
+});
+
+
+listen("default-terminal", (event) => {
+ console.log(
+ `Default terminal is ${event.payload}.`
+ );
+
+ $("#default-terminal").text(event.payload);
+});
\ No newline at end of file
diff --git a/src/ts/preload.ts b/src/ts/preload.ts
new file mode 100644
index 00000000..6ecb3ab0
--- /dev/null
+++ b/src/ts/preload.ts
@@ -0,0 +1,34 @@
+import {message} from "@tauri-apps/plugin-dialog";
+import {invoke} from "@tauri-apps/api/core";
+import {open} from "@tauri-apps/plugin-shell";
+import $ from "jquery";
+
+
+$(async () => {
+ /* eslint-disable indent */
+ switch (await invoke("internet_connection")) {
+ case false: {
+ await message("No internet connection! Can't proceed.", {
+ title: "SteamDepotDownloaderGUI", kind: "error", okLabel: "Close"
+ });
+ }
+ }
+ /* eslint-enable indent */
+
+ //discord
+ $("#smbtn1").on("click", () => {
+ open("https://discord.com/invite/3qCt4DT5qe");
+ });
+ // steamdb
+ $("#smbtn2").on("click", () => {
+ open("https://steamdb.info/instantsearch");
+ });
+ // donate
+ $("#smbtn3").on("click", () => {
+ open("https://paypal.me/onderkin");
+ });
+ // tutorial
+ $("#smbtn4").on("click", () => {
+ open("https://youtube.com/playlist?list=PLRAjc5plLScj967hnsYX-I3Vjw9C1v7Ca");
+ });
+});
\ No newline at end of file
diff --git a/src/ts/settings.ts b/src/ts/settings.ts
new file mode 100644
index 00000000..b2301734
--- /dev/null
+++ b/src/ts/settings.ts
@@ -0,0 +1,49 @@
+import {getVersion} from "@tauri-apps/api/app";
+import {open} from "@tauri-apps/plugin-shell";
+import $ from "jquery";
+
+
+$(async () => {
+ $("#version-info").text(`v${await getVersion()}`);
+
+ $("#theme-auto").on("click", () => {
+ setTheme("auto");
+ });
+ $("#theme-light").on("click", () => {
+ setTheme("light");
+ });
+ $("#theme-dark").on("click", () => {
+ setTheme("dark");
+ });
+
+ $("#folder-name-appid").on("click", () => {
+ $("#folder-name-custom").attr("aria-selected", "false");
+ $("#folder-name-appid").attr("aria-selected", "true");
+ $("#folder-name-custom-input").prop("disabled", true);
+ $("#folder-name-custom-input").val("");
+ });
+
+ // todo: fix folder-name-custom-input not disabled on untouched app state
+
+ $("#folder-name-custom").on("click", () => {
+ $("#folder-name-appid").attr("aria-selected", "false");
+ $("#folder-name-custom").attr("aria-selected", "true");
+ $("#folder-name-custom-input").prop("disabled", false);
+ });
+
+ console.log(await getVersion());
+
+ $("#version-info").on("click", async () => {
+ await open(`https://github.com/mmvanheusden/SteamDepotDownloaderGUI/releases/v${await getVersion()}`);
+ });
+});
+
+function setTheme(theme: string) {
+ $("#theme-auto").attr("aria-selected", String(theme === "auto"));
+ $("#theme-light").attr("aria-selected", String(theme === "light"));
+ $("#theme-dark").attr("aria-selected", String(theme === "dark"));
+ $("#theme").attr("data-color-mode", theme);
+}
+
+
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..3ecf1f5e
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": [
+ "src"
+ ]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..b85099c7
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,35 @@
+import {defineConfig} from "vite";
+
+// @ts-expect-error process is a nodejs global
+const host = process.env.TAURI_DEV_HOST;
+
+// https://vitejs.dev/config/
+export default defineConfig(async () => ({
+
+ // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
+ //
+ // 1. prevent vite from obscuring rust errors
+ clearScreen: false,
+ // 2. tauri expects a fixed port, fail if that port is not available
+ server: {
+ port: 1420,
+ strictPort: true,
+ host: host || false,
+ hmr: host
+ ? {
+ protocol: "ws",
+ host,
+ port: 1421,
+ }
+ : undefined,
+ watch: {
+ // 3. tell vite to ignore watching `src-tauri`
+ ignored: ["**/src-tauri/**"],
+ }
+ },
+ root: "src",
+ build: {
+ outDir: '../dist',
+ emptyOutDir: true,
+ }
+}));