From a400276846967b13b587e204b91bb52a6e407bce Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Sat, 15 Feb 2025 10:39:12 -0800 Subject: [PATCH 01/19] chore: $ npm install zustand --- package-lock.json | 37 +++++++++++++++++++++++++++++++++---- package.json | 3 ++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ad0c7e..b63974e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "zustand": "^5.0.3" }, "devDependencies": { "@tailwindcss/vite": "^4.0.6", @@ -1521,13 +1522,13 @@ "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1623,7 +1624,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/daisyui": { "version": "5.0.0-beta.7", @@ -2349,6 +2350,34 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 998eff2..f01bdcd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "zustand": "^5.0.3" }, "devDependencies": { "@tailwindcss/vite": "^4.0.6", From be7f5767cb83ec6b270a2592f854ceedc5b2293a Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:22:12 -0800 Subject: [PATCH 02/19] refactor: use zustand for state management --- src/App.tsx | 22 +++++++++++++++------- src/store/index.ts | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/store/index.ts diff --git a/src/App.tsx b/src/App.tsx index be2926c..7dc012e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { download } from "@tauri-apps/plugin-upload"; import { fetch } from "@tauri-apps/plugin-http"; import { Child, Command } from "@tauri-apps/plugin-shell"; import { mkdir, exists, readDir, BaseDirectory } from "@tauri-apps/plugin-fs"; +import { useStore } from "./store"; import "./App.css"; const urlNetwork = "https://test.net.zknet.io"; @@ -43,11 +44,18 @@ function App() { const [networkId, setNetworkId] = useState(""); const [dlProgress, setDlProgress] = useState(0); const [clientPid, setClientPid] = useState(0); - const [appVersion, setAppVersion] = useState(""); - const [platformArch, setPlatformArch] = useState(""); - const [platformSupported, setPlatformSupported] = useState(false); - const [networks, setNetworks] = useState([]); - const [isConnected, setIsConnected] = useState(false); + + const appVersion = useStore((s) => s.appVersion); + const isConnected = useStore((s) => s.isConnected); + const isPlatformSupported = useStore((s) => s.isPlatformSupported); + const networks = useStore((s) => s.networks); + const platformArch = useStore((s) => s.platformArch); + + const setAppVersion = useStore((s) => s.setAppVersion); + const setIsConnected = useStore((s) => s.setIsConnected); + const setIsPlatformSupported = useStore((s) => s.setIsPlatformSupported); + const setNetworks = useStore((s) => s.setNetworks); + const setPlatformArch = useStore((s) => s.setPlatformArch); // run once on startup (twice in dev mode) useEffect(() => { @@ -59,7 +67,7 @@ function App() { setAppVersion(v); setPlatformArch(getPlatformArch()); - setPlatformSupported(true); + setIsPlatformSupported(true); setNetworks(await getNetworks()); })(); } catch (error: any) { @@ -235,7 +243,7 @@ function App() { className={`logo ${isConnected ? "pulsing" : ""}`} /> - {platformSupported && + {isPlatformSupported && (clientPid === 0 ? ( <>

Enter a network identifier for access.

diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..3ca96c2 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,22 @@ +import { create } from "zustand"; +import { combine } from "zustand/middleware"; + +export const useStore = create( + combine( + { + appVersion: "", + isConnected: false, + isPlatformSupported: false, + networks: [] as string[], + platformArch: "", + }, + (set) => ({ + setAppVersion: (appVersion: string) => set({ appVersion }), + setIsConnected: (isConnected: boolean) => set({ isConnected }), + setIsPlatformSupported: (isPlatformSupported: boolean) => + set({ isPlatformSupported }), + setNetworks: (networks: string[]) => set({ networks }), + setPlatformArch: (platformArch: string) => set({ platformArch }), + }), + ), +); From d753ea5df74458e97e994d192261192890a9633a Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:25:39 -0800 Subject: [PATCH 03/19] refactor: extract Footer component --- src/App.tsx | 14 +------------- src/components/Footer.tsx | 17 +++++++++++++++++ src/components/index.ts | 1 + 3 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 src/components/Footer.tsx create mode 100644 src/components/index.ts diff --git a/src/App.tsx b/src/App.tsx index 7dc012e..4ebdf65 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { download } from "@tauri-apps/plugin-upload"; import { fetch } from "@tauri-apps/plugin-http"; import { Child, Command } from "@tauri-apps/plugin-shell"; import { mkdir, exists, readDir, BaseDirectory } from "@tauri-apps/plugin-fs"; +import { Footer } from "./components"; import { useStore } from "./store"; import "./App.css"; @@ -45,7 +46,6 @@ function App() { const [dlProgress, setDlProgress] = useState(0); const [clientPid, setClientPid] = useState(0); - const appVersion = useStore((s) => s.appVersion); const isConnected = useStore((s) => s.isConnected); const isPlatformSupported = useStore((s) => s.isPlatformSupported); const networks = useStore((s) => s.networks); @@ -219,18 +219,6 @@ function App() { return child.pid; } - const Footer = () => ( - - ); - return (
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..52b84f7 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,17 @@ +import { useStore } from "../store"; + +export function Footer() { + const appVersion = useStore((s) => s.appVersion); + const platformArch = useStore((s) => s.platformArch); + return ( +
+
+ ZKNetwork Client + | + Version: {appVersion} + | + Platform: {platformArch} +
+
+ ); +} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..e5ea0e5 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export { Footer } from "./Footer"; From aeb5c4ef5f96c2b403dc437a528b4890d21de97d Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:25:58 -0800 Subject: [PATCH 04/19] refactor: extract utility functions to module --- src/App.tsx | 31 ++----------------------------- src/utils/index.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 src/utils/index.ts diff --git a/src/App.tsx b/src/App.tsx index 4ebdf65..3689cb6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,39 +6,12 @@ import { arch, platform } from "@tauri-apps/plugin-os"; import { download } from "@tauri-apps/plugin-upload"; import { fetch } from "@tauri-apps/plugin-http"; import { Child, Command } from "@tauri-apps/plugin-shell"; -import { mkdir, exists, readDir, BaseDirectory } from "@tauri-apps/plugin-fs"; +import { mkdir, exists } from "@tauri-apps/plugin-fs"; import { Footer } from "./components"; import { useStore } from "./store"; +import { getNetworks, getPlatformArch, urlNetwork } from "./utils"; import "./App.css"; -const urlNetwork = "https://test.net.zknet.io"; - -// Map the os platform and architecture to a supported ZKN format -const getPlatformArch = (): string => { - const platArch = `${platform()}-${arch()}`; - switch (platArch) { - case "linux-aarch64": - return "linux-arm64"; - case "linux-x86_64": - return "linux-x64"; - case "macos-aarch64": - case "macos-x86_64": - return "macos"; - case "windows-x86_64": - return "windows-x64"; - default: - throw new Error(`Unsupported Operating System: ${platArch}`); - } -}; - -// Get networks with previously downloaded assets -const getNetworks = async () => { - const entries = await readDir("networks", { - baseDir: BaseDirectory.AppLocalData, - }); - return entries.filter((i) => i.isDirectory).map((i) => i.name); -}; - function App() { const [msg, setMsg] = useState(""); const [msgType, setMsgType] = useState(""); // error, info, success diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..45802bc --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,30 @@ +import { BaseDirectory, readDir } from "@tauri-apps/plugin-fs"; +import { arch, platform } from "@tauri-apps/plugin-os"; + +export const urlNetwork = "https://test.net.zknet.io"; + +// Map the os platform and architecture to a supported ZKN format +export const getPlatformArch = (): string => { + const platArch = `${platform()}-${arch()}`; + switch (platArch) { + case "linux-aarch64": + return "linux-arm64"; + case "linux-x86_64": + return "linux-x64"; + case "macos-aarch64": + case "macos-x86_64": + return "macos"; + case "windows-x86_64": + return "windows-x64"; + default: + throw new Error(`Unsupported Operating System: ${platArch}`); + } +}; + +// Get networks with previously downloaded assets +export const getNetworks = async () => { + const entries = await readDir("networks", { + baseDir: BaseDirectory.AppLocalData, + }); + return entries.filter((i) => i.isDirectory).map((i) => i.name); +}; From 2f547d1a6d6ed83412db4c5886758e8ce81101a1 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:07:59 -0800 Subject: [PATCH 05/19] chore: $ npm install react-router --- package-lock.json | 47 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 48 insertions(+) diff --git a/package-lock.json b/package-lock.json index b63974e..43674ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.1.5", "zustand": "^5.0.3" }, "devDependencies": { @@ -1512,6 +1513,11 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1620,6 +1626,14 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2152,6 +2166,29 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", + "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/rollup": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz", @@ -2207,6 +2244,11 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2231,6 +2273,11 @@ "node": ">=6" } }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", diff --git a/package.json b/package.json index f01bdcd..94cf397 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router": "^7.1.5", "zustand": "^5.0.3" }, "devDependencies": { From c46b3532dbd6a37677cd271d8808d6f8f3f1fafe Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Mon, 17 Feb 2025 13:23:50 -0800 Subject: [PATCH 06/19] refactor: extract Networks page and Message component --- src/App.tsx | 247 +++---------------------------------- src/components/Message.tsx | 23 ++++ src/components/index.ts | 1 + src/pages/Networks.tsx | 213 ++++++++++++++++++++++++++++++++ src/pages/index.ts | 1 + src/store/index.ts | 6 + 6 files changed, 262 insertions(+), 229 deletions(-) create mode 100644 src/components/Message.tsx create mode 100644 src/pages/Networks.tsx create mode 100644 src/pages/index.ts diff --git a/src/App.tsx b/src/App.tsx index 3689cb6..e5c092e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,32 +1,18 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; +import { MemoryRouter, Route, Routes } from "react-router"; import * as app from "@tauri-apps/api/app"; import * as log from "@tauri-apps/plugin-log"; -import * as path from "@tauri-apps/api/path"; import { arch, platform } from "@tauri-apps/plugin-os"; -import { download } from "@tauri-apps/plugin-upload"; -import { fetch } from "@tauri-apps/plugin-http"; -import { Child, Command } from "@tauri-apps/plugin-shell"; -import { mkdir, exists } from "@tauri-apps/plugin-fs"; -import { Footer } from "./components"; +import { Footer, Message } from "./components"; +import { Networks } from "./pages"; import { useStore } from "./store"; -import { getNetworks, getPlatformArch, urlNetwork } from "./utils"; +import { getNetworks, getPlatformArch } from "./utils"; import "./App.css"; function App() { - const [msg, setMsg] = useState(""); - const [msgType, setMsgType] = useState(""); // error, info, success - const [networkId, setNetworkId] = useState(""); - const [dlProgress, setDlProgress] = useState(0); - const [clientPid, setClientPid] = useState(0); - - const isConnected = useStore((s) => s.isConnected); - const isPlatformSupported = useStore((s) => s.isPlatformSupported); - const networks = useStore((s) => s.networks); - const platformArch = useStore((s) => s.platformArch); - const setAppVersion = useStore((s) => s.setAppVersion); - const setIsConnected = useStore((s) => s.setIsConnected); const setIsPlatformSupported = useStore((s) => s.setIsPlatformSupported); + const setMessage = useStore((s) => s.setMessage); const setNetworks = useStore((s) => s.setNetworks); const setPlatformArch = useStore((s) => s.setPlatformArch); @@ -45,219 +31,22 @@ function App() { })(); } catch (error: any) { log.error(`${error}`); - setMsgType("error"); - setMsg(`${error}`); + setMessage("error", `${error}`); } }, []); - async function connect() { - try { - const pid = await clientStart(); - setClientPid(pid); - setMsgType("info"); - setMsg(""); - setIsConnected(true); - setNetworks(await getNetworks()); - } catch (error: any) { - log.error(`${error}`); - setMsgType("error"); - setMsg(`${error}`); - } - } - - async function disconnect() { - try { - await clientStop(); - setClientPid(0); - setMsgType("info"); - setMsg("Disconnected from Network"); - setIsConnected(false); - } catch (error: any) { - log.error(`${error}`); - setMsgType("error"); - setMsg(`${error}`); - } - } - - async function clientStop() { - if (clientPid > 0) { - const c = new Child(clientPid); - c.kill(); - } - } - - async function clientStart() { - const urlClientCfg = `${urlNetwork}/${networkId}/client.toml`; - const urlWalletshield = `${urlNetwork}/${networkId}/walletshield-${platformArch}`; - const appLocalDataDirPath = await path.appLocalDataDir(); - const dirNetworks = await path.join(appLocalDataDirPath, "networks"); - const dirNetwork = await path.join(dirNetworks, networkId); - const fileClientCfg = await path.join(dirNetwork, "client.toml"); - const fileWalletshield = - (await path.join(dirNetwork, "walletshield")) + - (platform() === "windows" ? ".exe" : ""); - const updateInterval = 1; // download progress update interval - - //////////////////////////////////////////////////////////////////////// - // check network existence - //////////////////////////////////////////////////////////////////////// - setMsgType(() => "info"); - setMsg(() => `Checking network...`); - const response = await fetch(urlClientCfg, { - method: "GET", - connectTimeout: 5000, - }); - if (!response.ok || response.body === null) { - log.warn(`Failed to download client config: ${response.statusText}`); - throw new Error("Invalid network id (or local network error)"); - } - - //////////////////////////////////////////////////////////////////////// - // save the network's client.toml in a network-specific directory - //////////////////////////////////////////////////////////////////////// - log.debug(`local network directory: ${dirNetwork}`); - if (!(await exists(dirNetwork))) - await mkdir(dirNetwork, { recursive: true }); - await download(urlClientCfg, fileClientCfg); - setMsgType(() => "success"); - setMsg(() => "Retrieved network client configuration"); - - //////////////////////////////////////////////////////////////////////// - // save the network's walletshield binary - //////////////////////////////////////////////////////////////////////// - if (!(await exists(fileWalletshield))) { - setMsgType(() => "info"); - setMsg(() => `Downloading network client...`); - await download( - urlWalletshield, - fileWalletshield, - ({ progressTotal, total }) => { - const percentComplete = Math.floor((progressTotal / total) * 100); - if ( - (dlProgress !== percentComplete && - percentComplete % updateInterval === 0) || - progressTotal === total - ) { - let msg = `Downloading client... ${percentComplete}%`; - if (progressTotal === total) - msg = `Downloaded client for OS: ${platformArch}`; - setMsg(() => msg); - setDlProgress(() => percentComplete); - } - }, - ); - } - - //////////////////////////////////////////////////////////////////////// - // prepare the walletshield binary for execution - //////////////////////////////////////////////////////////////////////// - if (platform() === "linux" || platform() === "macos") { - log.debug(`executing command: chmod +x walletshield`); - const output = await Command.create( - "chmod-walletshield", - ["+x", "walletshield"], - { - cwd: dirNetwork, - }, - ).execute(); - if (output.code !== 0) { - throw new Error(`Failed to chmod+x walletshield: ${output.stderr}`); - } - } - - //////////////////////////////////////////////////////////////////////// - // execute the walletshield client with the client.toml - //////////////////////////////////////////////////////////////////////// - setMsgType(() => "info"); - setMsg(() => `Starting network client...`); - const cmd = "walletshield"; - const args = ["-listen", ":7070", "-config", "client.toml"]; - const command = Command.create("walletshield-listen", args, { - cwd: dirNetwork, - env: { - PATH: dirNetwork, - }, - }); - log.debug(`spawning command: ${cmd} ${args.join(" ")}`); - command.on("close", (data) => { - log.debug(`closed: ${cmd} code=${data.code} signal=${data.signal}`); - setMsgType(() => "info"); - setMsg(() => `Network client stopped.`); - }); - command.on("error", (e) => log.error(`${cmd}: ${e.trim()}`)); - command.stdout.on("data", (d) => log.info(`${cmd}: ${d.trim()}`)); - command.stderr.on("data", (d) => log.error(`${cmd}: ${d.trim()}`)); - - const child = await command.spawn(); - return child.pid; - } - return ( -
-
-

Zero Knowledge Network

- - ZKN isConnected && disconnect()} - className={`logo ${isConnected ? "pulsing" : ""}`} - /> - - {isPlatformSupported && - (clientPid === 0 ? ( - <> -

Enter a network identifier for access.

-
{ - e.preventDefault(); - connect(); - - // blur the input field to clear visual artifact - e.currentTarget.querySelector("input")?.blur(); - }} - > - setNetworkId(e.currentTarget.value)} - placeholder="Enter a network id..." - maxLength={36} - minLength={5} - required - list="networks" - /> - - {networks.map((n) => ( - - -
- - ) : ( - <> -

- Connected Network: {networkId} -

- - - ))} - - {msg && ( -

- {msg} -

- )} -
-
-
+ +
+
+ + } /> + +
+ +
+
+
); } diff --git a/src/components/Message.tsx b/src/components/Message.tsx new file mode 100644 index 0000000..a751cfc --- /dev/null +++ b/src/components/Message.tsx @@ -0,0 +1,23 @@ +import { useStore } from "../store"; + +export function Message() { + const msg = useStore((s) => s.message); + const msgType = useStore((s) => s.messageType); + + const msgClass = + { + error: "alert-error", + info: "alert-info", + success: "alert-success", + }[msgType] ?? ""; + + return ( + <> + {msg && ( +
+

{msg}

+
+ )} + + ); +} diff --git a/src/components/index.ts b/src/components/index.ts index e5ea0e5..84c60ca 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1 +1,2 @@ export { Footer } from "./Footer"; +export { Message } from "./Message"; diff --git a/src/pages/Networks.tsx b/src/pages/Networks.tsx new file mode 100644 index 0000000..45896c2 --- /dev/null +++ b/src/pages/Networks.tsx @@ -0,0 +1,213 @@ +import { useState } from "react"; +import * as log from "@tauri-apps/plugin-log"; +import * as path from "@tauri-apps/api/path"; +import { exists, mkdir } from "@tauri-apps/plugin-fs"; +import { fetch } from "@tauri-apps/plugin-http"; +import { platform } from "@tauri-apps/plugin-os"; +import { Child, Command } from "@tauri-apps/plugin-shell"; +import { download } from "@tauri-apps/plugin-upload"; +import { useStore } from "../store"; +import { getNetworks, urlNetwork } from "../utils"; + +export function Networks() { + const [clientPid, setClientPid] = useState(0); + const [dlProgress, setDlProgress] = useState(0); + const [networkId, setNetworkId] = useState(""); + + const isConnected = useStore((s) => s.isConnected); + const isPlatformSupported = useStore((s) => s.isPlatformSupported); + const networks = useStore((s) => s.networks); + const platformArch = useStore((s) => s.platformArch); + + const setIsConnected = useStore((s) => s.setIsConnected); + const setMessage = useStore((s) => s.setMessage); + const setNetworks = useStore((s) => s.setNetworks); + + async function connect() { + try { + const pid = await clientStart(); + setClientPid(pid); + setMessage("info", ""); + setIsConnected(true); + setNetworks(await getNetworks()); + } catch (error: any) { + log.error(`${error}`); + setMessage("error", `${error}`); + } + } + + async function disconnect() { + try { + await clientStop(); + setClientPid(0); + setMessage("info", "Disconnected from Network"); + setIsConnected(false); + } catch (error: any) { + log.error(`${error}`); + setMessage("error", `${error}`); + } + } + + async function clientStop() { + if (clientPid > 0) { + const c = new Child(clientPid); + c.kill(); + } + } + + async function clientStart() { + const urlClientCfg = `${urlNetwork}/${networkId}/client.toml`; + const urlWalletshield = `${urlNetwork}/${networkId}/walletshield-${platformArch}`; + const appLocalDataDirPath = await path.appLocalDataDir(); + const dirNetworks = await path.join(appLocalDataDirPath, "networks"); + const dirNetwork = await path.join(dirNetworks, networkId); + const fileClientCfg = await path.join(dirNetwork, "client.toml"); + const fileWalletshield = + (await path.join(dirNetwork, "walletshield")) + + (platform() === "windows" ? ".exe" : ""); + const updateInterval = 1; // download progress update interval + + //////////////////////////////////////////////////////////////////////// + // check network existence + //////////////////////////////////////////////////////////////////////// + setMessage("info", `Checking network...`); + const response = await fetch(urlClientCfg, { + method: "GET", + connectTimeout: 5000, + }); + if (!response.ok || response.body === null) { + log.warn(`Failed to download client config: ${response.statusText}`); + throw new Error("Invalid network id (or local network error)"); + } + + //////////////////////////////////////////////////////////////////////// + // save the network's client.toml in a network-specific directory + //////////////////////////////////////////////////////////////////////// + log.debug(`local network directory: ${dirNetwork}`); + if (!(await exists(dirNetwork))) + await mkdir(dirNetwork, { recursive: true }); + await download(urlClientCfg, fileClientCfg); + setMessage("info", "Retrieved network client configuration"); + + //////////////////////////////////////////////////////////////////////// + // save the network's walletshield binary + //////////////////////////////////////////////////////////////////////// + if (!(await exists(fileWalletshield))) { + setMessage("info", "Downloading network client..."); + await download( + urlWalletshield, + fileWalletshield, + ({ progressTotal, total }) => { + const percentComplete = Math.floor((progressTotal / total) * 100); + if ( + (dlProgress !== percentComplete && + percentComplete % updateInterval === 0) || + progressTotal === total + ) { + let msg = `Downloading client... ${percentComplete}%`; + if (progressTotal === total) + msg = `Downloaded client for OS: ${platformArch}`; + setMessage("info", msg); + setDlProgress(() => percentComplete); + } + }, + ); + } + + //////////////////////////////////////////////////////////////////////// + // prepare the walletshield binary for execution + //////////////////////////////////////////////////////////////////////// + if (platform() === "linux" || platform() === "macos") { + log.debug(`executing command: chmod +x walletshield`); + const output = await Command.create( + "chmod-walletshield", + ["+x", "walletshield"], + { + cwd: dirNetwork, + }, + ).execute(); + if (output.code !== 0) { + throw new Error(`Failed to chmod+x walletshield: ${output.stderr}`); + } + } + + //////////////////////////////////////////////////////////////////////// + // execute the walletshield client with the client.toml + //////////////////////////////////////////////////////////////////////// + setMessage("info", "Starting network client..."); + const cmd = "walletshield"; + const args = ["-listen", ":7070", "-config", "client.toml"]; + const command = Command.create("walletshield-listen", args, { + cwd: dirNetwork, + env: { + PATH: dirNetwork, + }, + }); + log.debug(`spawning command: ${cmd} ${args.join(" ")}`); + command.on("close", (data) => { + log.debug(`closed: ${cmd} code=${data.code} signal=${data.signal}`); + setMessage("info", "Network client stopped."); + }); + command.on("error", (e) => log.error(`${cmd}: ${e.trim()}`)); + command.stdout.on("data", (d) => log.info(`${cmd}: ${d.trim()}`)); + command.stderr.on("data", (d) => log.error(`${cmd}: ${d.trim()}`)); + + const child = await command.spawn(); + return child.pid; + } + + return ( +
+

Zero Knowledge Network

+ + ZKN isConnected && disconnect()} + className={`logo ${isConnected ? "pulsing" : ""}`} + /> + + {isPlatformSupported && + (clientPid === 0 ? ( + <> +

Enter a network identifier for access.

+
{ + e.preventDefault(); + connect(); + + // blur the input field to clear visual artifact + e.currentTarget.querySelector("input")?.blur(); + }} + > + setNetworkId(e.currentTarget.value)} + placeholder="Enter a network id..." + maxLength={36} + minLength={5} + required + list="networks" + /> + + {networks.map((n) => ( + + +
+ + ) : ( + <> +

Connected Network: {networkId}

+ + + ))} +
+ ); +} diff --git a/src/pages/index.ts b/src/pages/index.ts new file mode 100644 index 0000000..2163e28 --- /dev/null +++ b/src/pages/index.ts @@ -0,0 +1 @@ +export { Networks } from "./Networks"; diff --git a/src/store/index.ts b/src/store/index.ts index 3ca96c2..277c926 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -7,6 +7,8 @@ export const useStore = create( appVersion: "", isConnected: false, isPlatformSupported: false, + message: "", + messageType: "", networks: [] as string[], platformArch: "", }, @@ -15,6 +17,10 @@ export const useStore = create( setIsConnected: (isConnected: boolean) => set({ isConnected }), setIsPlatformSupported: (isPlatformSupported: boolean) => set({ isPlatformSupported }), + setMessage: ( + messageType: "error" | "info" | "success", + message: string, + ) => set({ message, messageType }), setNetworks: (networks: string[]) => set({ networks }), setPlatformArch: (platformArch: string) => set({ platformArch }), }), From 40092f3f42be87a07e31aa78c7c07f63a6d2a636 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Mon, 17 Feb 2025 14:03:37 -0800 Subject: [PATCH 07/19] feat: manage network connection state in store --- src/pages/Networks.tsx | 11 +++++++++-- src/store/index.ts | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pages/Networks.tsx b/src/pages/Networks.tsx index 45896c2..f79f6fd 100644 --- a/src/pages/Networks.tsx +++ b/src/pages/Networks.tsx @@ -10,17 +10,20 @@ import { useStore } from "../store"; import { getNetworks, urlNetwork } from "../utils"; export function Networks() { - const [clientPid, setClientPid] = useState(0); const [dlProgress, setDlProgress] = useState(0); const [networkId, setNetworkId] = useState(""); + const clientPid = useStore((s) => s.clientPid); const isConnected = useStore((s) => s.isConnected); const isPlatformSupported = useStore((s) => s.isPlatformSupported); + const networkConnected = useStore((s) => s.networkConnected); const networks = useStore((s) => s.networks); const platformArch = useStore((s) => s.platformArch); + const setClientPid = useStore((s) => s.setClientPid); const setIsConnected = useStore((s) => s.setIsConnected); const setMessage = useStore((s) => s.setMessage); + const setNetworkConnected = useStore((s) => s.setNetworkConnected); const setNetworks = useStore((s) => s.setNetworks); async function connect() { @@ -29,6 +32,7 @@ export function Networks() { setClientPid(pid); setMessage("info", ""); setIsConnected(true); + setNetworkConnected(networkId); setNetworks(await getNetworks()); } catch (error: any) { log.error(`${error}`); @@ -42,6 +46,7 @@ export function Networks() { setClientPid(0); setMessage("info", "Disconnected from Network"); setIsConnected(false); + setNetworkConnected(""); } catch (error: any) { log.error(`${error}`); setMessage("error", `${error}`); @@ -202,7 +207,9 @@ export function Networks() { ) : ( <> -

Connected Network: {networkId}

+

+ Connected Network: {networkConnected} +

diff --git a/src/store/index.ts b/src/store/index.ts index 277c926..dd90643 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -5,15 +5,18 @@ export const useStore = create( combine( { appVersion: "", + clientPid: 0, isConnected: false, isPlatformSupported: false, message: "", messageType: "", + networkConnected: "", networks: [] as string[], platformArch: "", }, (set) => ({ setAppVersion: (appVersion: string) => set({ appVersion }), + setClientPid: (clientPid: number) => set({ clientPid }), setIsConnected: (isConnected: boolean) => set({ isConnected }), setIsPlatformSupported: (isPlatformSupported: boolean) => set({ isPlatformSupported }), @@ -21,6 +24,8 @@ export const useStore = create( messageType: "error" | "info" | "success", message: string, ) => set({ message, messageType }), + setNetworkConnected: (networkConnected: string) => + set({ networkConnected }), setNetworks: (networks: string[]) => set({ networks }), setPlatformArch: (platformArch: string) => set({ platformArch }), }), From 2931a23d125ebc8e096108f6752377097b14ba61 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:52:26 -0800 Subject: [PATCH 08/19] feat: add IconBars3 component --- src/components/icons.tsx | 16 ++++++++++++++++ src/components/index.ts | 2 ++ 2 files changed, 18 insertions(+) create mode 100644 src/components/icons.tsx diff --git a/src/components/icons.tsx b/src/components/icons.tsx new file mode 100644 index 0000000..f82b271 --- /dev/null +++ b/src/components/icons.tsx @@ -0,0 +1,16 @@ +// https://heroicons.com/ + +export const IconBars3 = () => ( + + + +); diff --git a/src/components/index.ts b/src/components/index.ts index 84c60ca..fb554ab 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,4 @@ export { Footer } from "./Footer"; export { Message } from "./Message"; + +export * from "./icons.tsx"; From 8234aa00b5cb0344aa4ec701f95695a479e5fd24 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:01:51 -0800 Subject: [PATCH 09/19] feat: add and integrate Header component --- src/App.tsx | 5 +++-- src/components/Header.tsx | 45 +++++++++++++++++++++++++++++++++++++++ src/components/index.ts | 1 + src/pages/Networks.tsx | 4 +--- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/components/Header.tsx diff --git a/src/App.tsx b/src/App.tsx index e5c092e..fef7c85 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,8 +3,8 @@ import { MemoryRouter, Route, Routes } from "react-router"; import * as app from "@tauri-apps/api/app"; import * as log from "@tauri-apps/plugin-log"; import { arch, platform } from "@tauri-apps/plugin-os"; -import { Footer, Message } from "./components"; import { Networks } from "./pages"; +import { Footer, Header, Message } from "./components"; import { useStore } from "./store"; import { getNetworks, getPlatformArch } from "./utils"; import "./App.css"; @@ -38,7 +38,8 @@ function App() { return (
-
+
+
} /> diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..301b8b9 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,45 @@ +import { Link } from "react-router"; +import { IconBars3 } from "."; + +export function Header() { + // https://v5.daisyui.com/components/drawer/#drawer-that-opens-from-right-side-of-page + const SideBar = () => ( +
+ +
+ +
+
+ +
    +
  • + Networks +
  • +
+
+
+ ); + + // https://v5.daisyui.com/components/navbar/#navbar-with-dropdown-center-logo-and-icon + return ( +
+
+ +
+
+ Zero Knowledge Network +
+
+
+ ); +} diff --git a/src/components/index.ts b/src/components/index.ts index fb554ab..5fbe34f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,4 +1,5 @@ export { Footer } from "./Footer"; +export { Header } from "./Header"; export { Message } from "./Message"; export * from "./icons.tsx"; diff --git a/src/pages/Networks.tsx b/src/pages/Networks.tsx index f79f6fd..4db0e5f 100644 --- a/src/pages/Networks.tsx +++ b/src/pages/Networks.tsx @@ -162,9 +162,7 @@ export function Networks() { } return ( -
-

Zero Knowledge Network

- +
ZKN Date: Mon, 17 Feb 2025 16:05:16 -0800 Subject: [PATCH 10/19] feat: close header sidebar on link click --- src/components/Header.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 301b8b9..7a9a985 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,11 +1,25 @@ +import { useRef } from "react"; import { Link } from "react-router"; import { IconBars3 } from "."; export function Header() { + const drawerRef = useRef(null); + + const closeDrawer = () => { + if (drawerRef.current) { + drawerRef.current.checked = false; + } + }; + // https://v5.daisyui.com/components/drawer/#drawer-that-opens-from-right-side-of-page const SideBar = () => (
- +
  • - Networks + + Networks +
From 03643489e76b633576979488908c1dc572b84d94 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:23:00 -0800 Subject: [PATCH 11/19] feat: add components: IconCog IconGlobe --- src/components/icons.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/components/icons.tsx b/src/components/icons.tsx index f82b271..b761c78 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -14,3 +14,29 @@ export const IconBars3 = () => ( /> ); + +export const IconCog = () => ( + + + +); + +export const IconGlobe = () => ( + + + +); From 0ea9fabb7eb5a4eb94fe5fc89d95824beeb60d90 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:30:35 -0800 Subject: [PATCH 12/19] feat: clear message on sidebar link click add icons for sidebar items --- src/components/Header.tsx | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 7a9a985..5c0216a 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,16 +1,42 @@ import { useRef } from "react"; import { Link } from "react-router"; -import { IconBars3 } from "."; +import { IconBars3, IconGlobe } from "."; +import { useStore } from "../store"; export function Header() { const drawerRef = useRef(null); + const setMessage = useStore((s) => s.setMessage); + const closeDrawer = () => { if (drawerRef.current) { drawerRef.current.checked = false; } }; + const handleLink = () => { + closeDrawer(); + setMessage("info", ""); // clear any messages + }; + + // SideBar nav item + const NavItem = ({ + to, + icon, + label, + }: { + to: string; + icon: JSX.Element; + label: string; + }) => ( +
  • + + {icon} + {label} + +
  • + ); + // https://v5.daisyui.com/components/drawer/#drawer-that-opens-from-right-side-of-page const SideBar = () => (
    @@ -36,11 +62,7 @@ export function Header() { className="drawer-overlay" >
      -
    • - - Networks - -
    • + } />
    From 1d3ced1e06a45078ea92b8737ba7707918a581ae Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:46:13 -0800 Subject: [PATCH 13/19] chore(capabilities): shell: add validator for walletshield listen arg --- src-tauri/capabilities/default.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 9fae946..c93d3e4 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -50,7 +50,12 @@ { "name": "walletshield-listen", "cmd": "walletshield", - "args": ["-listen", ":7070", "-config", "client.toml"], + "args": [ + "-listen", + { "validator": "^(\\S*:\\d+)$" }, + "-config", + "client.toml" + ], "sidecar": false } ] From 84d90f32afc9dbb920d7ec37d2f032446e3578c9 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:49:28 -0800 Subject: [PATCH 14/19] feat: add settings page to config walletshield listen address --- src/App.tsx | 3 +- src/components/Header.tsx | 3 +- src/pages/Networks.tsx | 6 +++- src/pages/Settings.tsx | 75 +++++++++++++++++++++++++++++++++++++++ src/pages/index.ts | 1 + src/store/index.ts | 3 ++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/pages/Settings.tsx diff --git a/src/App.tsx b/src/App.tsx index fef7c85..58f2bd7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,8 +3,8 @@ import { MemoryRouter, Route, Routes } from "react-router"; import * as app from "@tauri-apps/api/app"; import * as log from "@tauri-apps/plugin-log"; import { arch, platform } from "@tauri-apps/plugin-os"; -import { Networks } from "./pages"; import { Footer, Header, Message } from "./components"; +import { Networks, Settings } from "./pages"; import { useStore } from "./store"; import { getNetworks, getPlatformArch } from "./utils"; import "./App.css"; @@ -42,6 +42,7 @@ function App() {
    } /> + } />
    diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 5c0216a..c8da98c 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,6 +1,6 @@ import { useRef } from "react"; import { Link } from "react-router"; -import { IconBars3, IconGlobe } from "."; +import { IconBars3, IconCog, IconGlobe } from "."; import { useStore } from "../store"; export function Header() { @@ -63,6 +63,7 @@ export function Header() { >
      } /> + } />
    diff --git a/src/pages/Networks.tsx b/src/pages/Networks.tsx index 4db0e5f..493164b 100644 --- a/src/pages/Networks.tsx +++ b/src/pages/Networks.tsx @@ -19,6 +19,9 @@ export function Networks() { const networkConnected = useStore((s) => s.networkConnected); const networks = useStore((s) => s.networks); const platformArch = useStore((s) => s.platformArch); + const walletshieldListenAddress = useStore( + (s) => s.walletshieldListenAddress, + ); const setClientPid = useStore((s) => s.setClientPid); const setIsConnected = useStore((s) => s.setIsConnected); @@ -141,7 +144,8 @@ export function Networks() { //////////////////////////////////////////////////////////////////////// setMessage("info", "Starting network client..."); const cmd = "walletshield"; - const args = ["-listen", ":7070", "-config", "client.toml"]; + const listen = walletshieldListenAddress ?? ":7070"; + const args = ["-listen", listen, "-config", "client.toml"]; const command = Command.create("walletshield-listen", args, { cwd: dirNetwork, env: { diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx new file mode 100644 index 0000000..50c4b38 --- /dev/null +++ b/src/pages/Settings.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "react"; +import { useStore } from "../store"; + +export function Settings() { + const [listenAddress, setListenAddress] = useState(""); + + const walletshieldListenAddress = useStore( + (s) => s.walletshieldListenAddress, + ); + + const setMessage = useStore((s) => s.setMessage); + const setWalletshieldListenAddress = useStore( + (s) => s.setWalletshieldListenAddress, + ); + + useEffect(() => { + setListenAddress(walletshieldListenAddress); + }, []); + + const handleReset = () => { + setListenAddress(""); + setWalletshieldListenAddress(""); + setMessage("success", "Settings reset to default."); + }; + + const handleApply = () => { + setWalletshieldListenAddress(listenAddress); + setMessage("success", "Settings saved."); + }; + + return ( +
    +
    { + e.preventDefault(); + handleApply(); + }} + > +
    + Walletshield + + + setListenAddress(e.target.value)} + pattern="^((\d{1,3}\.){3}\d{1,3}|[a-zA-Z0-9.-]+)?:(\d{1,5})$" + required + /> +

    + Where the Walletshield listens for connections. +

    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + ); +} diff --git a/src/pages/index.ts b/src/pages/index.ts index 2163e28..d0b73ad 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1 +1,2 @@ export { Networks } from "./Networks"; +export { Settings } from "./Settings"; diff --git a/src/store/index.ts b/src/store/index.ts index dd90643..7051be7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -13,6 +13,7 @@ export const useStore = create( networkConnected: "", networks: [] as string[], platformArch: "", + walletshieldListenAddress: "", }, (set) => ({ setAppVersion: (appVersion: string) => set({ appVersion }), @@ -28,6 +29,8 @@ export const useStore = create( set({ networkConnected }), setNetworks: (networks: string[]) => set({ networks }), setPlatformArch: (platformArch: string) => set({ platformArch }), + setWalletshieldListenAddress: (walletshieldListenAddress: string) => + set({ walletshieldListenAddress }), }), ), ); From 22833c769a9802dd18ce8e7bf55c30b4df6749a4 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 17:30:44 -0800 Subject: [PATCH 15/19] chore: $ cargo tauri add store --- package-lock.json | 9 +++++++++ package.json | 1 + src-tauri/Cargo.lock | 17 +++++++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 1 + src-tauri/src/lib.rs | 1 + 6 files changed, 30 insertions(+) diff --git a/package-lock.json b/package-lock.json index 43674ad..b138b15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "^2.2.0", "@tauri-apps/plugin-shell": "^2.2.0", + "@tauri-apps/plugin-store": "^2.2.0", "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1464,6 +1465,14 @@ "@tauri-apps/api": "^2.0.0" } }, + "node_modules/@tauri-apps/plugin-store": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-store/-/plugin-store-2.2.0.tgz", + "integrity": "sha512-hJTRtuJis4w5fW1dkcgftsYxKXK0+DbAqurZ3CURHG5WkAyyZgbxpeYctw12bbzF9ZbZREXZklPq8mocCC3Sgg==", + "dependencies": { + "@tauri-apps/api": "^2.0.0" + } + }, "node_modules/@tauri-apps/plugin-upload": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-upload/-/plugin-upload-2.2.1.tgz", diff --git a/package.json b/package.json index 94cf397..a171528 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-os": "^2.2.0", "@tauri-apps/plugin-shell": "^2.2.0", + "@tauri-apps/plugin-store": "^2.2.0", "@tauri-apps/plugin-upload": "^2.2.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a8544d0..a527313 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4408,6 +4408,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-store" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0c08fae6995909f5e9a0da6038273b750221319f2c0f3b526d6de1cde21505" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.11", + "tokio", + "tracing", +] + [[package]] name = "tauri-plugin-upload" version = "2.2.1" @@ -5983,6 +5999,7 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-os", "tauri-plugin-shell", + "tauri-plugin-store", "tauri-plugin-upload", "time", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a476f65..ca00df0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -29,4 +29,5 @@ tauri-plugin-shell = "2" tauri-plugin-fs = "2" tauri-plugin-log = "2" time = { version = "0.3", features = ["formatting"] } +tauri-plugin-store = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index c93d3e4..e7e7783 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -12,6 +12,7 @@ "os:allow-platform", "shell:allow-kill", "shell:default", + "store:default", "upload:default", { "identifier": "fs:allow-exists", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1993c06..4ec6ede 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,6 +7,7 @@ fn network_connect(network_id: &str) -> String { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::new().build()) .plugin( tauri_plugin_log::Builder::new() // https://tauri.app/plugin/logging/#formatting From 1e75dbbd4a9c97cc6d7971790dd3d849c7382a08 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:33:33 -0800 Subject: [PATCH 16/19] feat: add persistent settings --- src/store/index.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index 7051be7..6575d10 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,9 @@ +import { LazyStore } from "@tauri-apps/plugin-store"; import { create } from "zustand"; import { combine } from "zustand/middleware"; +const store = new LazyStore("settings.json"); + export const useStore = create( combine( { @@ -29,8 +32,22 @@ export const useStore = create( set({ networkConnected }), setNetworks: (networks: string[]) => set({ networks }), setPlatformArch: (platformArch: string) => set({ platformArch }), - setWalletshieldListenAddress: (walletshieldListenAddress: string) => - set({ walletshieldListenAddress }), + + setWalletshieldListenAddress: async ( + walletshieldListenAddress: string, + ) => { + set({ walletshieldListenAddress }); + await store.set("walletshieldListenAddress", walletshieldListenAddress); + await store.save(); + }, + + loadPersistedSettings: async () => { + const wla = await store.get("walletshieldListenAddress"); + if (wla) set({ walletshieldListenAddress: wla }); + }, }), ), ); + +// Load persisted settings on app start +useStore.getState().loadPersistedSettings(); From 19dec462d004f6de8b30c29bb4e74c6e635df198 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:37:16 -0800 Subject: [PATCH 17/19] chore: set package version to 0.1.0-dev (for development) released versions are set by ci --- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a527313..5c9641e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5987,7 +5987,7 @@ dependencies = [ [[package]] name = "zkn-client" -version = "0.1.0" +version = "0.1.0-dev" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ca00df0..b724ae7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zkn-client" -version = "0.1.0" +version = "0.1.0-dev" description = "A Tauri App" authors = ["you"] edition = "2021" From f75391d36cf8899917e58e4a3f638cf1a20733d4 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Tue, 18 Feb 2025 23:02:12 -0800 Subject: [PATCH 18/19] refactor: add default walletshield address constant --- src/pages/Networks.tsx | 10 +++++++--- src/pages/Settings.tsx | 3 ++- src/utils/index.ts | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/Networks.tsx b/src/pages/Networks.tsx index 493164b..7f75c04 100644 --- a/src/pages/Networks.tsx +++ b/src/pages/Networks.tsx @@ -7,7 +7,11 @@ import { platform } from "@tauri-apps/plugin-os"; import { Child, Command } from "@tauri-apps/plugin-shell"; import { download } from "@tauri-apps/plugin-upload"; import { useStore } from "../store"; -import { getNetworks, urlNetwork } from "../utils"; +import { + defaultWalletshieldListenAddress, + getNetworks, + urlNetwork, +} from "../utils"; export function Networks() { const [dlProgress, setDlProgress] = useState(0); @@ -144,8 +148,8 @@ export function Networks() { //////////////////////////////////////////////////////////////////////// setMessage("info", "Starting network client..."); const cmd = "walletshield"; - const listen = walletshieldListenAddress ?? ":7070"; - const args = ["-listen", listen, "-config", "client.toml"]; + const wla = walletshieldListenAddress ?? defaultWalletshieldListenAddress; + const args = ["-listen", wla, "-config", "client.toml"]; const command = Command.create("walletshield-listen", args, { cwd: dirNetwork, env: { diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 50c4b38..af49275 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { useStore } from "../store"; +import { defaultWalletshieldListenAddress } from "../utils"; export function Settings() { const [listenAddress, setListenAddress] = useState(""); @@ -43,7 +44,7 @@ export function Settings() { setListenAddress(e.target.value)} pattern="^((\d{1,3}\.){3}\d{1,3}|[a-zA-Z0-9.-]+)?:(\d{1,5})$" diff --git a/src/utils/index.ts b/src/utils/index.ts index 45802bc..a4ecda5 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,8 @@ import { BaseDirectory, readDir } from "@tauri-apps/plugin-fs"; import { arch, platform } from "@tauri-apps/plugin-os"; +export const defaultWalletshieldListenAddress = ":7070"; + export const urlNetwork = "https://test.net.zknet.io"; // Map the os platform and architecture to a supported ZKN format From 299f803089b3652c04109b45099d3b87c78478a6 Mon Sep 17 00:00:00 2001 From: Xendarboh <1435589+xendarboh@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:50:16 -0800 Subject: [PATCH 19/19] style: set scrollbar-gutter to prevent layout shift --- src/App.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/App.css b/src/App.css index 1353fb0..e594e82 100644 --- a/src/App.css +++ b/src/App.css @@ -44,6 +44,18 @@ -moz-osx-font-smoothing: grayscale; } +/* + * Set scrollbar-gutter to prevent sidebar-induced layout shift on Windows + * https://github.com/saadeghi/daisyui/issues/2859#issuecomment-2383862183 + * https://github.com/saadeghi/daisyui/discussions/3246#discussioncomment-11738876 + */ +html { + scrollbar-gutter: auto !important; +} +html:has(body.content-overflow-y) { + scrollbar-gutter: stable !important; +} + .logo { @apply h-48 p-6 transition duration-1000; will-change: filter;