Build the Synapsis Tauri onboarding and authentication shell with the official wordmark and a fluid stable-topology signal mesh
Hop-State: A_06FPFZF1AB46ARJ4HD8ZQH8 Hop-Proposal: R_06FPFZEBFD47ZTA2XKJ2Q0G Hop-Task: T_06FPFXR242APZTT77HV934R Hop-Attempt: AT_06FPFXR241F9H1V2J0RCGGG
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Synapsis Desktop
|
||||||
|
|
||||||
|
A Tauri desktop client for the Synapsis federated social network. The first-run experience follows the same node-first model as the native iOS app:
|
||||||
|
|
||||||
|
1. Choose and verify a Synapsis node.
|
||||||
|
2. Sign in with an existing account or create a new account on that node.
|
||||||
|
3. Enter the signed-in desktop shell.
|
||||||
|
|
||||||
|
Node and authentication requests run in Rust so independent nodes do not need to opt into browser CORS. Session cookies are held by the native HTTP client and are not exposed to the React webview.
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
|
Requirements: Bun, Rust, and the platform prerequisites for Tauri 2.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun install
|
||||||
|
bun run tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The standalone frontend preview is available with `bun run dev`, but node connection is intentionally disabled outside Tauri because the native command layer owns networking.
|
||||||
|
|
||||||
|
## Validate
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun run build
|
||||||
|
cargo test --manifest-path src-tauri/Cargo.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current scope
|
||||||
|
|
||||||
|
- Animated signal-field launch and onboarding visuals.
|
||||||
|
- Node URL normalization and `/api/node` verification.
|
||||||
|
- Existing-session detection while the app is running.
|
||||||
|
- Cookie-based sign in, account creation, and sign out.
|
||||||
|
- A desktop navigation shell ready for the feed and other iOS features.
|
||||||
|
|
||||||
|
The node address persists between launches. Authentication cookies currently live for the duration of the application process; durable, encrypted session restoration is a later security-focused addition.
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "synapsis-desktop",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
|
"@types/react": "^19.1.8",
|
||||||
|
"@types/react-dom": "^19.1.6",
|
||||||
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"vite": "^7.0.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||||
|
|
||||||
|
"@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
|
||||||
|
|
||||||
|
"@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||||
|
|
||||||
|
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||||
|
|
||||||
|
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
|
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
|
||||||
|
|
||||||
|
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.2", "", { "os": "android", "cpu": "arm" }, "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.2", "", { "os": "none", "cpu": "arm64" }, "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="],
|
||||||
|
|
||||||
|
"@tauri-apps/api": ["@tauri-apps/api@2.11.1", "", {}, "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli": ["@tauri-apps/cli@2.11.4", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.4", "@tauri-apps/cli-darwin-x64": "2.11.4", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", "@tauri-apps/cli-linux-arm64-musl": "2.11.4", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", "@tauri-apps/cli-linux-x64-gnu": "2.11.4", "@tauri-apps/cli-linux-x64-musl": "2.11.4", "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", "@tauri-apps/cli-win32-x64-msvc": "2.11.4" }, "bin": { "tauri": "tauri.js" } }, "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.4", "", { "os": "linux", "cpu": "arm" }, "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.4", "", { "os": "linux", "cpu": "none" }, "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.4", "", { "os": "linux", "cpu": "x64" }, "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA=="],
|
||||||
|
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw=="],
|
||||||
|
|
||||||
|
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||||
|
|
||||||
|
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||||
|
|
||||||
|
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||||
|
|
||||||
|
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||||
|
|
||||||
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="],
|
||||||
|
|
||||||
|
"browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||||
|
|
||||||
|
"caniuse-lite": ["caniuse-lite@1.0.30001805", "", {}, "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA=="],
|
||||||
|
|
||||||
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
|
"electron-to-chromium": ["electron-to-chromium@1.5.392", "", {}, "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||||
|
|
||||||
|
"node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||||
|
|
||||||
|
"react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
|
||||||
|
|
||||||
|
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
|
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
|
||||||
|
|
||||||
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#070808" />
|
||||||
|
<title>Synapsis</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "synapsis-desktop",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"@tauri-apps/api": "^2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.1.8",
|
||||||
|
"@types/react-dom": "^19.1.6",
|
||||||
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"typescript": "~5.8.3",
|
||||||
|
"vite": "^7.0.4",
|
||||||
|
"@tauri-apps/cli": "^2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 4532 847" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g id="logo.png" transform="matrix(1,0,0,1,64.677826,46)">
|
||||||
|
<path d="M304.019,263.551C305.652,271.447 303.736,283.853 305.372,289.534C309.02,302.195 300.103,295.787 299.567,295.402C295.892,292.761 179.642,227.694 173.743,224.084C172.18,223.128 154.443,213.343 150.751,211.07C140.744,204.906 140.277,205.902 130.411,199.662C126.035,196.894 124.425,196.538 118.768,193.053C118.171,192.685 75.592,168.79 67.72,164.118C50.371,153.821 40.078,151.747 48.297,148.155C51.477,146.766 58.668,141.301 72.373,134.267C73.58,133.647 82.469,127.438 96.37,120.261C98.188,119.322 104.017,114.775 120.378,106.272C121.461,105.709 121.004,105.086 133.418,98.339C146.666,91.138 146.145,90.391 163.368,81.274C165.176,80.317 164.94,79.941 185.4,68.304C206.053,56.556 205.664,55.998 226.403,44.323C242.236,35.409 253.536,28.4 255.86,26.958C258.106,25.565 258.573,26.419 284.583,10.654C294.55,4.612 295.136,4.631 295.44,5.542C296.789,9.575 294.774,9.868 296.624,15.486C297.168,17.14 295.72,46.107 297.419,50.524C298.475,53.27 296.609,76.802 298.486,81.504C299.397,83.786 297.688,108.545 299.404,113.529C300.21,115.871 298.88,136.601 300.425,141.522C302.579,148.383 298.499,148.681 301.636,170.486C302.136,173.958 300.811,195.308 302.477,199.507C303.468,202.002 301.591,224.981 303.43,229.522C304.452,232.045 302.979,232.185 304.019,263.551Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
<path d="M431.157,290.099C430.505,290.683 417.455,299.199 416.24,300.073C410.454,304.238 410.117,301.928 410.1,301.911C409.223,300.98 409.627,300.735 409.292,247.501C409.273,244.423 408.806,244.548 408.333,241.515C407.885,238.638 409.246,142.669 407.611,138.467C406.701,136.128 408.338,54.956 406.598,50.47C405.316,47.163 407.533,31.627 406.398,25.509C405.516,20.757 406.222,5.206 406.302,3.442C406.392,1.46 408.021,2.026 428.641,14.229C437.763,19.627 541.782,80.104 545.29,81.907C547.301,82.94 547.09,83.235 569.633,96.254C576.025,99.946 647.235,141.072 649.401,142.638C651.816,144.384 656.029,145.526 650.383,149.314C646.012,152.246 645.797,151.776 595.12,185.025C594.19,185.635 594.165,185.471 593.258,186.112C590.743,187.889 573.995,198.033 572.47,199.464C571.467,200.405 509.123,240.164 503.602,243.685C497.799,247.386 432.243,289.194 431.157,290.099Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
<path d="M33.196,269.863C35.094,271.32 53.244,280.215 58.604,284.36C59.591,285.123 60.061,284.284 72.29,291.86C72.708,292.12 78.887,295.201 86.291,299.853C89.193,301.676 94.674,304.083 102.284,308.859C110.072,313.747 110.497,312.868 118.277,317.868C119.996,318.974 120.342,318.299 140.627,330.255C145.794,333.3 189.693,358.096 191.285,358.909C195.409,361.014 215.67,373.434 230.262,380.952C231.907,381.799 248.026,390.104 250.133,392.776C252.561,395.854 247.834,394.016 208.151,418.039C206.78,418.869 206.719,418.711 190.606,427.709C174.878,436.491 170.409,438.814 168.63,439.738C167.644,440.251 167.798,440.488 156.591,446.686C61.751,499.128 62.031,499.508 53.615,503.733C52.269,504.409 33.347,515.25 22.665,520.796C19.384,522.499 6.684,530.214 5.305,531.051C-1.012,534.889 4.833,512.016 2.562,505.484C2.153,504.307 2.353,504.259 2.304,317.5C2.292,275.117 3.173,275.182 3.17,271.501C3.159,258.312 -0.553,250 7.247,254.933C10.234,256.821 32.436,269.163 33.196,269.863Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
<path d="M615.834,474.047C613.965,472.666 613.886,472.955 611.739,472.048C608.124,470.522 608.854,469.422 605.317,467.875C600.769,465.886 600.806,465.953 596.573,463.364C591.476,460.246 557.039,441.084 556.4,440.685C544.299,433.139 532.485,426.863 530.364,425.736C511.75,415.847 474.229,393.735 472.224,392.965C465.615,390.425 471.159,388.345 478.594,383.673C480.359,382.564 500.048,370.192 500.746,369.958C502.006,369.536 657.092,274.386 662.366,271.253C679.367,261.152 700.878,246.866 703.462,246.854C704.487,246.849 704.766,247.506 704.745,298.5C704.669,486.271 704.865,486.247 704.454,487.489C702.634,492.996 704.799,514.494 704.661,521.532C704.599,524.695 700.566,522.09 696.409,519.669C677.735,508.793 660.007,499.345 615.834,474.047Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
<path d="M304.007,677.487C303.299,704.156 305.311,704.237 302.808,715.521C301.909,719.575 304.513,755.179 300.568,755.816C299,756.069 223.586,711.063 217.58,708.352C215.199,707.277 202.655,699.526 201.372,698.733C190.449,691.984 176.482,684.739 172.388,681.657C170.844,680.495 170.424,681.416 151.379,669.722C141.349,663.562 70.832,623.138 70.589,622.456C70.344,621.765 69.857,620.391 77.43,616.361C87.379,611.065 112.843,595.213 122.372,590.262C123.516,589.668 123.193,589.134 136.343,582.226C137.425,581.658 137.047,581.069 149.385,574.278C152.149,572.756 151.869,572.356 183.378,554.281C185.591,553.011 206.973,540.745 210.741,537.896C211.596,537.25 211.701,537.437 222.393,531.297C224.61,530.023 240.546,520.87 249.874,514.976C258.239,509.689 277.147,499.627 307.685,480.9C311.561,478.524 309.72,487.101 309.682,493.501C309.614,505.156 310.12,505.167 309.175,510.411C308.706,513.019 309.341,513.039 307.903,543.51C307.181,558.821 308.884,573.611 306.792,582.517C306.468,583.899 307.061,615.423 306.469,617.491C305.179,621.995 305.598,659.749 304.007,677.487Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
<path d="M412.978,728.547C413.291,713.672 413.478,712.999 412.374,707.511C412.343,707.358 412.064,697.943 412.575,696.522C414.366,691.542 412.539,539.146 413.605,536.529C414.689,533.87 414.367,524.717 414.35,488.489C414.347,482.676 427.213,492.857 433.657,496.203C455.256,507.42 454.781,508.223 456.662,509.188C464.859,513.39 464.477,513.967 472.648,518.216C484.746,524.505 619.364,601.245 628.276,605.916C634.395,609.122 639.867,612.68 644.238,614.942C649.789,617.815 635.639,622.259 632.602,624.641C630.054,626.639 444.729,734.101 441.668,735.806C427.184,743.876 427.558,744.422 426.277,745.072C414.002,751.297 412.422,754.809 412.303,751.568C412.262,750.443 411.909,740.751 412.413,737.493C413.104,733.03 412.057,733.002 412.978,728.547Z" style="fill:rgb(254,254,254);fill-opacity:0.99;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.383006,0,0,1.383006,-551.567585,-462.336524)">
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1085,854)">
|
||||||
|
<path d="M0.185,0.007C0.145,0.007 0.113,-0.006 0.088,-0.033C0.062,-0.06 0.05,-0.094 0.05,-0.137L0.05,-0.152C0.05,-0.159 0.053,-0.162 0.06,-0.162L0.077,-0.162C0.084,-0.162 0.087,-0.159 0.087,-0.152L0.087,-0.141C0.087,-0.107 0.096,-0.08 0.115,-0.059C0.133,-0.037 0.156,-0.027 0.184,-0.027C0.213,-0.027 0.236,-0.037 0.255,-0.057C0.274,-0.077 0.283,-0.103 0.283,-0.136C0.283,-0.157 0.278,-0.175 0.269,-0.191C0.259,-0.207 0.247,-0.22 0.234,-0.231C0.221,-0.242 0.197,-0.259 0.164,-0.284C0.137,-0.304 0.115,-0.321 0.1,-0.335C0.085,-0.349 0.072,-0.365 0.063,-0.384C0.054,-0.402 0.049,-0.423 0.049,-0.448C0.049,-0.49 0.061,-0.523 0.086,-0.547C0.11,-0.571 0.141,-0.583 0.18,-0.583C0.221,-0.583 0.254,-0.57 0.279,-0.543C0.304,-0.516 0.316,-0.481 0.316,-0.438L0.316,-0.42C0.316,-0.413 0.313,-0.41 0.306,-0.41L0.287,-0.41C0.28,-0.41 0.277,-0.413 0.277,-0.42L0.277,-0.436C0.277,-0.469 0.268,-0.496 0.25,-0.517C0.232,-0.538 0.209,-0.549 0.18,-0.549C0.153,-0.549 0.131,-0.54 0.114,-0.522C0.097,-0.504 0.088,-0.479 0.088,-0.446C0.088,-0.419 0.095,-0.397 0.111,-0.379C0.126,-0.361 0.153,-0.338 0.193,-0.31C0.227,-0.286 0.252,-0.267 0.269,-0.252C0.286,-0.237 0.299,-0.221 0.308,-0.204C0.317,-0.186 0.322,-0.165 0.322,-0.14C0.322,-0.096 0.309,-0.06 0.284,-0.034C0.258,-0.007 0.225,0.007 0.185,0.007Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1073.2,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1420.5625,854)">
|
||||||
|
<path d="M0.167,-0C0.16,-0 0.157,-0.003 0.157,-0.01L0.157,-0.265C0.157,-0.268 0.157,-0.269 0.156,-0.27L0.038,-0.564L0.037,-0.568C0.037,-0.573 0.04,-0.576 0.046,-0.576L0.066,-0.576C0.071,-0.576 0.075,-0.573 0.077,-0.568L0.174,-0.316C0.175,-0.314 0.176,-0.313 0.177,-0.313C0.178,-0.313 0.179,-0.314 0.18,-0.316L0.276,-0.568C0.278,-0.573 0.282,-0.576 0.287,-0.576L0.307,-0.576C0.31,-0.576 0.313,-0.575 0.314,-0.573C0.315,-0.57 0.315,-0.567 0.314,-0.564L0.197,-0.27C0.196,-0.269 0.196,-0.268 0.196,-0.265L0.196,-0.01C0.196,-0.003 0.193,-0 0.186,-0L0.167,-0Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1401.3875,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1740.6375,854)">
|
||||||
|
<path d="M0.31,-0.565C0.31,-0.572 0.313,-0.575 0.32,-0.575L0.339,-0.575C0.346,-0.575 0.349,-0.572 0.349,-0.565L0.349,-0.01C0.349,-0.003 0.346,-0 0.339,-0L0.318,-0C0.313,-0 0.309,-0.003 0.307,-0.008L0.114,-0.474C0.113,-0.475 0.112,-0.476 0.111,-0.476C0.11,-0.476 0.109,-0.475 0.109,-0.473L0.109,-0.01C0.109,-0.003 0.106,-0 0.099,-0L0.08,-0C0.073,-0 0.07,-0.003 0.07,-0.01L0.07,-0.565C0.07,-0.572 0.073,-0.575 0.08,-0.575L0.101,-0.575C0.106,-0.575 0.11,-0.572 0.112,-0.567L0.306,-0.099C0.307,-0.098 0.308,-0.097 0.309,-0.097C0.31,-0.097 0.311,-0.098 0.311,-0.1L0.31,-0.565Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,1740.6375,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2129.3,854)">
|
||||||
|
<path d="M0.316,-0C0.31,-0 0.306,-0.003 0.305,-0.009L0.281,-0.116C0.281,-0.117 0.281,-0.118 0.28,-0.119C0.279,-0.12 0.277,-0.12 0.276,-0.12L0.103,-0.12C0.102,-0.12 0.1,-0.12 0.1,-0.119C0.098,-0.118 0.098,-0.117 0.098,-0.116L0.074,-0.009C0.073,-0.003 0.07,-0 0.063,-0L0.044,-0C0.041,-0 0.038,-0.001 0.036,-0.003C0.035,-0.005 0.035,-0.008 0.036,-0.011L0.166,-0.567C0.167,-0.573 0.171,-0.576 0.177,-0.576L0.201,-0.576C0.207,-0.576 0.211,-0.573 0.212,-0.567L0.345,-0.011L0.346,-0.007C0.346,-0.002 0.343,-0 0.337,-0L0.316,-0ZM0.108,-0.157C0.107,-0.156 0.107,-0.155 0.108,-0.154C0.11,-0.153 0.111,-0.153 0.112,-0.153L0.268,-0.153C0.269,-0.153 0.271,-0.153 0.272,-0.154C0.273,-0.155 0.273,-0.156 0.272,-0.157L0.192,-0.508C0.191,-0.51 0.191,-0.511 0.19,-0.511C0.189,-0.511 0.189,-0.51 0.188,-0.508L0.108,-0.157Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2129.3,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2489.2,854)">
|
||||||
|
<path d="M0.204,-0.578C0.243,-0.578 0.275,-0.564 0.3,-0.535C0.325,-0.506 0.337,-0.469 0.337,-0.422C0.337,-0.377 0.325,-0.34 0.3,-0.312C0.276,-0.284 0.244,-0.27 0.205,-0.27L0.108,-0.27C0.105,-0.27 0.104,-0.269 0.104,-0.266L0.104,-0.01C0.104,-0.003 0.101,-0 0.094,-0L0.075,-0C0.068,-0 0.065,-0.003 0.065,-0.01L0.065,-0.568C0.065,-0.575 0.068,-0.578 0.075,-0.578L0.204,-0.578ZM0.2,-0.303C0.229,-0.303 0.253,-0.314 0.271,-0.336C0.289,-0.357 0.298,-0.386 0.298,-0.422C0.298,-0.459 0.289,-0.488 0.271,-0.51C0.253,-0.532 0.229,-0.543 0.2,-0.543L0.108,-0.543C0.105,-0.543 0.104,-0.542 0.104,-0.539L0.104,-0.307C0.104,-0.304 0.105,-0.303 0.108,-0.303L0.2,-0.303Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2489.2,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2840.9875,854)">
|
||||||
|
<path d="M0.185,0.007C0.145,0.007 0.113,-0.006 0.088,-0.033C0.062,-0.06 0.05,-0.094 0.05,-0.137L0.05,-0.152C0.05,-0.159 0.053,-0.162 0.06,-0.162L0.077,-0.162C0.084,-0.162 0.087,-0.159 0.087,-0.152L0.087,-0.141C0.087,-0.107 0.096,-0.08 0.115,-0.059C0.133,-0.037 0.156,-0.027 0.184,-0.027C0.213,-0.027 0.236,-0.037 0.255,-0.057C0.274,-0.077 0.283,-0.103 0.283,-0.136C0.283,-0.157 0.278,-0.175 0.269,-0.191C0.259,-0.207 0.247,-0.22 0.234,-0.231C0.221,-0.242 0.197,-0.259 0.164,-0.284C0.137,-0.304 0.115,-0.321 0.1,-0.335C0.085,-0.349 0.072,-0.365 0.063,-0.384C0.054,-0.402 0.049,-0.423 0.049,-0.448C0.049,-0.49 0.061,-0.523 0.086,-0.547C0.11,-0.571 0.141,-0.583 0.18,-0.583C0.221,-0.583 0.254,-0.57 0.279,-0.543C0.304,-0.516 0.316,-0.481 0.316,-0.438L0.316,-0.42C0.316,-0.413 0.313,-0.41 0.306,-0.41L0.287,-0.41C0.28,-0.41 0.277,-0.413 0.277,-0.42L0.277,-0.436C0.277,-0.469 0.268,-0.496 0.25,-0.517C0.232,-0.538 0.209,-0.549 0.18,-0.549C0.153,-0.549 0.131,-0.54 0.114,-0.522C0.097,-0.504 0.088,-0.479 0.088,-0.446C0.088,-0.419 0.095,-0.397 0.111,-0.379C0.126,-0.361 0.153,-0.338 0.193,-0.31C0.227,-0.286 0.252,-0.267 0.269,-0.252C0.286,-0.237 0.299,-0.221 0.308,-0.204C0.317,-0.186 0.322,-0.165 0.322,-0.14C0.322,-0.096 0.309,-0.06 0.284,-0.034C0.258,-0.007 0.225,0.007 0.185,0.007Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,2840.9875,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,3188.35,854)">
|
||||||
|
<path d="M0.08,-0C0.073,-0 0.07,-0.003 0.07,-0.01L0.07,-0.565C0.07,-0.572 0.073,-0.575 0.08,-0.575L0.099,-0.575C0.106,-0.575 0.109,-0.572 0.109,-0.565L0.109,-0.01C0.109,-0.003 0.106,-0 0.099,-0L0.08,-0Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,3188.35,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,3400.0125,854)">
|
||||||
|
<path d="M0.185,0.007C0.145,0.007 0.113,-0.006 0.088,-0.033C0.062,-0.06 0.05,-0.094 0.05,-0.137L0.05,-0.152C0.05,-0.159 0.053,-0.162 0.06,-0.162L0.077,-0.162C0.084,-0.162 0.087,-0.159 0.087,-0.152L0.087,-0.141C0.087,-0.107 0.096,-0.08 0.115,-0.059C0.133,-0.037 0.156,-0.027 0.184,-0.027C0.213,-0.027 0.236,-0.037 0.255,-0.057C0.274,-0.077 0.283,-0.103 0.283,-0.136C0.283,-0.157 0.278,-0.175 0.269,-0.191C0.259,-0.207 0.247,-0.22 0.234,-0.231C0.221,-0.242 0.197,-0.259 0.164,-0.284C0.137,-0.304 0.115,-0.321 0.1,-0.335C0.085,-0.349 0.072,-0.365 0.063,-0.384C0.054,-0.402 0.049,-0.423 0.049,-0.448C0.049,-0.49 0.061,-0.523 0.086,-0.547C0.11,-0.571 0.141,-0.583 0.18,-0.583C0.221,-0.583 0.254,-0.57 0.279,-0.543C0.304,-0.516 0.316,-0.481 0.316,-0.438L0.316,-0.42C0.316,-0.413 0.313,-0.41 0.306,-0.41L0.287,-0.41C0.28,-0.41 0.277,-0.413 0.277,-0.42L0.277,-0.436C0.277,-0.469 0.268,-0.496 0.25,-0.517C0.232,-0.538 0.209,-0.549 0.18,-0.549C0.153,-0.549 0.131,-0.54 0.114,-0.522C0.097,-0.504 0.088,-0.479 0.088,-0.446C0.088,-0.419 0.095,-0.397 0.111,-0.379C0.126,-0.361 0.153,-0.338 0.193,-0.31C0.227,-0.286 0.252,-0.267 0.269,-0.252C0.286,-0.237 0.299,-0.221 0.308,-0.204C0.317,-0.186 0.322,-0.165 0.322,-0.14C0.322,-0.096 0.309,-0.06 0.284,-0.034C0.258,-0.007 0.225,0.007 0.185,0.007Z" style="fill:white;fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,3400.0125,854)">
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(737.5,0,0,737.5,3747.375,854)">
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Generated by Tauri
|
||||||
|
# will have schema files for capabilities auto-completion
|
||||||
|
/gen/schemas
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "synapsis-desktop"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Desktop client for the Synapsis federated social network"
|
||||||
|
authors = ["Gnosys Labs"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `_lib` suffix may seem redundant but it is necessary
|
||||||
|
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||||
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||||
|
name = "synapsis_desktop_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
reqwest = { version = "0.12", features = ["cookies", "json", "rustls-tls"] }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capability for the main window",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 990 B |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<rect width="1024" height="1024" rx="224" fill="#080909"/>
|
||||||
|
<g fill="none" stroke="#f7f7f3" stroke-width="28" stroke-linejoin="round">
|
||||||
|
<path d="M512 166 318 278 512 390Z" fill="#f7f7f3"/>
|
||||||
|
<path d="m540 166 194 112-194 112Z"/>
|
||||||
|
<path d="m290 326 194 112-194 112Z"/>
|
||||||
|
<path d="m762 326-194 112 194 112Z" fill="#f7f7f3"/>
|
||||||
|
<path d="m512 486-194 112 194 112Z"/>
|
||||||
|
<path d="m540 486 194 112-194 112Z" fill="#f7f7f3"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,256 @@
|
|||||||
|
use reqwest::{Client, Method, StatusCode, Url};
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
use std::time::Duration;
|
||||||
|
use tauri::State;
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct NodeInfo {
|
||||||
|
name: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
accent_color: Option<String>,
|
||||||
|
domain: Option<String>,
|
||||||
|
logo_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct User {
|
||||||
|
id: String,
|
||||||
|
handle: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
avatar_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SessionPayload {
|
||||||
|
user: Option<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ConnectionPayload {
|
||||||
|
node_url: String,
|
||||||
|
node: NodeInfo,
|
||||||
|
user: Option<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct AuthPayload {
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ErrorPayload {
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct RegistrationRequest<'a> {
|
||||||
|
handle: &'a str,
|
||||||
|
display_name: &'a str,
|
||||||
|
email: &'a str,
|
||||||
|
password: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_node(input: &str) -> Result<Url, String> {
|
||||||
|
let trimmed = input.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return Err("Enter a valid Synapsis node URL.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let candidate = if trimmed.contains("://") {
|
||||||
|
trimmed.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("https://{trimmed}")
|
||||||
|
};
|
||||||
|
let mut url = Url::parse(&candidate).map_err(|_| "Enter a valid Synapsis node URL.")?;
|
||||||
|
if url.scheme() != "http" && url.scheme() != "https" {
|
||||||
|
return Err("Synapsis nodes must use an http or https URL.".into());
|
||||||
|
}
|
||||||
|
if url.host_str().is_none() {
|
||||||
|
return Err("Enter a valid Synapsis node URL.".into());
|
||||||
|
}
|
||||||
|
if url.host_str() == Some("www.synapsis.social") {
|
||||||
|
url.set_host(Some("synapsis.social"))
|
||||||
|
.map_err(|_| "Enter a valid Synapsis node URL.")?;
|
||||||
|
}
|
||||||
|
url.set_username("").ok();
|
||||||
|
url.set_password(None).ok();
|
||||||
|
url.set_path("/");
|
||||||
|
url.set_query(None);
|
||||||
|
url.set_fragment(None);
|
||||||
|
Ok(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint(base: &Url, path: &str) -> Result<Url, String> {
|
||||||
|
base.join(path)
|
||||||
|
.map_err(|_| "The selected node address is invalid.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn decode_response<T: DeserializeOwned>(response: reqwest::Response) -> Result<T, String> {
|
||||||
|
let status = response.status();
|
||||||
|
let bytes = response.bytes().await.map_err(|error| error.to_string())?;
|
||||||
|
if !status.is_success() {
|
||||||
|
let message = serde_json::from_slice::<ErrorPayload>(&bytes)
|
||||||
|
.ok()
|
||||||
|
.and_then(|payload| payload.error)
|
||||||
|
.unwrap_or_else(|| format!("Request failed ({status})."));
|
||||||
|
return Err(message);
|
||||||
|
}
|
||||||
|
serde_json::from_slice(&bytes).map_err(|_| "The node returned an unexpected response.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get<T: DeserializeOwned>(client: &Client, base: &Url, path: &str) -> Result<T, String> {
|
||||||
|
let response = client
|
||||||
|
.get(endpoint(base, path)?)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|error| format!("Couldn’t reach this node: {error}"))?;
|
||||||
|
decode_response(response).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post<T: DeserializeOwned, B: Serialize>(
|
||||||
|
client: &Client,
|
||||||
|
base: &Url,
|
||||||
|
path: &str,
|
||||||
|
body: &B,
|
||||||
|
) -> Result<T, String> {
|
||||||
|
let response = client
|
||||||
|
.request(Method::POST, endpoint(base, path)?)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.json(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|error| format!("Couldn’t reach this node: {error}"))?;
|
||||||
|
decode_response(response).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn connect(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
node_url: String,
|
||||||
|
) -> Result<ConnectionPayload, String> {
|
||||||
|
let base = normalize_node(&node_url)?;
|
||||||
|
let node = get::<NodeInfo>(&state.client, &base, "/api/node").await?;
|
||||||
|
let session = get::<SessionPayload>(&state.client, &base, "/api/auth/me")
|
||||||
|
.await
|
||||||
|
.unwrap_or(SessionPayload { user: None });
|
||||||
|
Ok(ConnectionPayload {
|
||||||
|
node_url: base.to_string(),
|
||||||
|
node,
|
||||||
|
user: session.user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn login(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
node_url: String,
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<AuthPayload, String> {
|
||||||
|
let base = normalize_node(&node_url)?;
|
||||||
|
let payload = post::<SessionPayload, _>(
|
||||||
|
&state.client,
|
||||||
|
&base,
|
||||||
|
"/api/auth/login",
|
||||||
|
&serde_json::json!({ "email": email.trim(), "password": password }),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
payload
|
||||||
|
.user
|
||||||
|
.map(|user| AuthPayload { user })
|
||||||
|
.ok_or_else(|| "The node returned an unexpected response.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn register(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
node_url: String,
|
||||||
|
handle: String,
|
||||||
|
display_name: String,
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
) -> Result<AuthPayload, String> {
|
||||||
|
let base = normalize_node(&node_url)?;
|
||||||
|
let body = RegistrationRequest {
|
||||||
|
handle: handle.trim().trim_start_matches('@'),
|
||||||
|
display_name: display_name.trim(),
|
||||||
|
email: email.trim(),
|
||||||
|
password: &password,
|
||||||
|
};
|
||||||
|
let payload =
|
||||||
|
post::<SessionPayload, _>(&state.client, &base, "/api/auth/register", &body).await?;
|
||||||
|
payload
|
||||||
|
.user
|
||||||
|
.map(|user| AuthPayload { user })
|
||||||
|
.ok_or_else(|| "The node returned an unexpected response.".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn logout(state: State<'_, AppState>, node_url: String) -> Result<(), String> {
|
||||||
|
let base = normalize_node(&node_url)?;
|
||||||
|
let response = state
|
||||||
|
.client
|
||||||
|
.post(endpoint(&base, "/api/auth/logout")?)
|
||||||
|
.json(&serde_json::json!({}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|error| format!("Couldn’t reach this node: {error}"))?;
|
||||||
|
if response.status().is_success() || response.status() == StatusCode::UNAUTHORIZED {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Log out failed ({}).", response.status()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
let client = Client::builder()
|
||||||
|
.cookie_store(true)
|
||||||
|
.timeout(Duration::from_secs(20))
|
||||||
|
.user_agent("Synapsis Desktop/0.1")
|
||||||
|
.build()
|
||||||
|
.expect("failed to build Synapsis HTTP client");
|
||||||
|
|
||||||
|
tauri::Builder::default()
|
||||||
|
.manage(AppState { client })
|
||||||
|
.invoke_handler(tauri::generate_handler![connect, login, register, logout])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running Synapsis");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalizes_bare_node_names() {
|
||||||
|
assert_eq!(
|
||||||
|
normalize_node("synapsis.social").unwrap().as_str(),
|
||||||
|
"https://synapsis.social/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strips_paths_credentials_and_fragments() {
|
||||||
|
assert_eq!(
|
||||||
|
normalize_node("https://person:secret@example.com/somewhere?q=1#fragment")
|
||||||
|
.unwrap()
|
||||||
|
.as_str(),
|
||||||
|
"https://example.com/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_non_web_schemes() {
|
||||||
|
assert!(normalize_node("file:///tmp/node").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
synapsis_desktop_lib::run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "Synapsis",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "xyz.gnosyslabs.synapsis",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "bun run dev",
|
||||||
|
"devUrl": "http://localhost:1430",
|
||||||
|
"beforeBuildCommand": "bun run build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Synapsis",
|
||||||
|
"width": 1080,
|
||||||
|
"height": 720,
|
||||||
|
"minWidth": 720,
|
||||||
|
"minHeight": 560,
|
||||||
|
"center": true,
|
||||||
|
"hiddenTitle": true,
|
||||||
|
"titleBarStyle": "Overlay"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": "default-src 'self'; connect-src ipc: http://ipc.localhost; img-src 'self' asset: https: http: data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@600;700;800&family=Roboto+Mono:wght@500;600&display=swap');
|
||||||
|
|
||||||
|
:root { font-family: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif; color: #f5f5f2; background: #070808; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; }
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html, body, #root { width: 100%; min-width: 720px; height: 100%; min-height: 560px; margin: 0; overflow: hidden; }
|
||||||
|
button, input { font: inherit; }
|
||||||
|
button { color: inherit; }
|
||||||
|
button:focus-visible, input:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
|
||||||
|
|
||||||
|
.onboarding-shell { width: 100%; height: 100%; display: grid; grid-template-columns: minmax(300px, 45%) minmax(420px, 55%); background: #0b0c0c; }
|
||||||
|
.signal-panel { position: relative; min-width: 0; overflow: hidden; border-right: 1px solid rgba(255,255,255,.1); }
|
||||||
|
.signal-field { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||||
|
.signal-shade { position: absolute; inset: 0; background: radial-gradient(circle at 55% 45%, transparent 0, rgba(0,0,0,.08) 38%, rgba(0,0,0,.74) 100%), linear-gradient(180deg, rgba(0,0,0,.12), rgba(0,0,0,.4)); pointer-events: none; }
|
||||||
|
.panel-brand, .sidebar-brand, .launch-mark { position: absolute; top: 52px; left: 48px; z-index: 2; display: flex; align-items: center; }
|
||||||
|
.wordmark { display: block; width: auto; object-fit: contain; }
|
||||||
|
.panel-wordmark { width: 146px; height: 28px; }
|
||||||
|
.signal-copy { position: absolute; z-index: 2; left: 48px; right: 36px; bottom: 104px; }
|
||||||
|
.eyebrow { margin: 0 0 25px; color: rgba(255,255,255,.62); font: 600 10px/1 "Roboto Mono", monospace; letter-spacing: 2.1px; }
|
||||||
|
.eyebrow i { display: inline-block; width: 5px; height: 5px; margin-right: 8px; border-radius: 50%; background: #fff; box-shadow: 0 0 9px #fff; vertical-align: 1px; }
|
||||||
|
.signal-copy h2 { margin: 0; max-width: 480px; font-family: Manrope, sans-serif; font-size: clamp(37px, 4vw, 61px); line-height: .99; letter-spacing: -3.4px; }
|
||||||
|
.panel-status { position: absolute; z-index: 2; bottom: 34px; left: 48px; right: 38px; display: flex; justify-content: space-between; color: rgba(255,255,255,.3); font: 500 9px "Roboto Mono", monospace; letter-spacing: 1.4px; }
|
||||||
|
|
||||||
|
.form-panel { position: relative; overflow-y: auto; background: radial-gradient(circle at 85% 10%, rgba(255,255,255,.035), transparent 31%), #0c0d0d; }
|
||||||
|
.drag-strip { position: fixed; z-index: 1; top: 0; right: 0; width: 55%; height: 36px; }
|
||||||
|
.back-button { position: absolute; z-index: 2; top: 46px; left: 46px; display: flex; align-items: center; gap: 7px; padding: 7px 0; border: 0; background: transparent; color: rgba(255,255,255,.55); font-size: 13px; cursor: pointer; }
|
||||||
|
.back-button:hover { color: #fff; }
|
||||||
|
.form-wrap { width: min(440px, calc(100% - 80px)); min-height: calc(100% - 66px); margin: 0 auto; display: flex; align-items: center; padding: 78px 0 68px; }
|
||||||
|
.form-wrap form { width: 100%; }
|
||||||
|
.step-label { display: flex; align-items: center; gap: 12px; margin-bottom: 35px; color: rgba(255,255,255,.43); font: 600 9px "Roboto Mono", monospace; letter-spacing: 1.6px; }
|
||||||
|
.step-label span:first-child { color: #fff; }
|
||||||
|
.step-label i { width: 27px; height: 1px; background: rgba(255,255,255,.22); }
|
||||||
|
.form-wrap h1 { margin: 0 0 20px; font-family: Manrope, sans-serif; font-size: clamp(44px, 5vw, 64px); line-height: .95; letter-spacing: -4px; }
|
||||||
|
.lede { max-width: 400px; margin: 0 0 42px; color: rgba(255,255,255,.5); font-size: 15px; line-height: 1.65; }
|
||||||
|
.lede.compact { margin-bottom: 27px; }
|
||||||
|
label { display: block; margin: 0 0 9px; color: rgba(255,255,255,.75); font-size: 12px; font-weight: 600; }
|
||||||
|
.url-field, .handle-field { display: flex; align-items: center; height: 56px; border: 1px solid rgba(255,255,255,.16); border-radius: 8px; background: rgba(255,255,255,.045); transition: border-color .2s, background .2s; }
|
||||||
|
.url-field:focus-within, .handle-field:focus-within { border-color: rgba(255,255,255,.72); background: rgba(255,255,255,.065); }
|
||||||
|
.url-field span, .handle-field span { padding-left: 17px; color: rgba(255,255,255,.35); font: 500 14px "Roboto Mono", monospace; }
|
||||||
|
.url-field input, .handle-field input { min-width: 0; flex: 1; height: 100%; padding: 0 17px 0 2px; border: 0; outline: 0; color: #fff; background: transparent; font: 500 14px "Roboto Mono", monospace; }
|
||||||
|
.text-field { width: 100%; height: 52px; margin: 0 0 17px; padding: 0 15px; border: 1px solid rgba(255,255,255,.14); border-radius: 8px; outline: 0; color: #fff; background: rgba(255,255,255,.045); transition: border-color .2s, background .2s; }
|
||||||
|
.text-field:focus { border-color: rgba(255,255,255,.68); background: rgba(255,255,255,.065); }
|
||||||
|
.text-field::placeholder { color: rgba(255,255,255,.24); }
|
||||||
|
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; }
|
||||||
|
.handle-field { height: 52px; margin-bottom: 17px; }
|
||||||
|
.field-note { margin: -8px 0 18px; color: rgba(255,255,255,.34); font-size: 11px; }
|
||||||
|
.primary-button, .secondary-button { width: 100%; height: 54px; display: flex; align-items: center; justify-content: center; gap: 10px; border-radius: 8px; font-size: 13px; font-weight: 700; cursor: pointer; transition: transform .15s, background .15s, opacity .15s; }
|
||||||
|
.primary-button { margin-top: 18px; border: 0; color: #080909; background: #f4f4f0; }
|
||||||
|
.primary-button:hover:not(:disabled) { background: #fff; transform: translateY(-1px); }
|
||||||
|
.primary-button:active:not(:disabled) { transform: translateY(0); }
|
||||||
|
.primary-button:disabled { cursor: default; opacity: .48; }
|
||||||
|
.secondary-button { border: 1px solid rgba(255,255,255,.17); background: transparent; }
|
||||||
|
.secondary-button:hover { background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.28); }
|
||||||
|
.privacy-note { margin: 20px 0 0; text-align: center; color: rgba(255,255,255,.28); font-size: 10px; }
|
||||||
|
.lock-dot { margin-right: 5px; font-size: 7px; }
|
||||||
|
.connection-label { display: flex; align-items: center; gap: 8px; margin-bottom: 31px; color: rgba(255,255,255,.48); font: 600 9px "Roboto Mono", monospace; letter-spacing: 1.2px; }
|
||||||
|
.connection-label > span { display: grid; place-items: center; width: 18px; height: 18px; border: 1px solid rgba(255,255,255,.4); border-radius: 50%; color: #fff; }
|
||||||
|
.divider { display: flex; align-items: center; gap: 12px; margin: 25px 0 18px; color: rgba(255,255,255,.27); font: 600 9px "Roboto Mono", monospace; letter-spacing: 1.5px; }
|
||||||
|
.divider i { flex: 1; height: 1px; background: rgba(255,255,255,.09); }
|
||||||
|
.error-message { margin: 13px 0 0; padding: 10px 12px; border: 1px solid rgba(255,112,112,.22); border-radius: 7px; color: #ff9c9c; background: rgba(255,60,60,.07); font-size: 12px; line-height: 1.4; }
|
||||||
|
.step-footer { position: absolute; left: 44px; right: 44px; bottom: 25px; display: flex; justify-content: space-between; color: rgba(255,255,255,.25); font: 500 8px "Roboto Mono", monospace; letter-spacing: 1.2px; }
|
||||||
|
.step-dots { display: flex; gap: 5px; }
|
||||||
|
.step-dots i { width: 17px; height: 2px; background: rgba(255,255,255,.12); }
|
||||||
|
.step-dots i.active { background: rgba(255,255,255,.75); }
|
||||||
|
.spinner { width: 17px; height: 17px; display: inline-block; border: 2px solid rgba(255,255,255,.2); border-top-color: #fff; border-radius: 50%; animation: spin .75s linear infinite; }
|
||||||
|
.spinner.dark { width: 15px; height: 15px; border-color: rgba(0,0,0,.18); border-top-color: #111; }
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.launch-screen { position: relative; width: 100%; height: 100%; display: grid; place-items: center; background: #060707; }
|
||||||
|
.launch-screen::after { content: ""; position: absolute; inset: 0; background: radial-gradient(circle, transparent, rgba(0,0,0,.6)); }
|
||||||
|
.launch-mark { left: 50%; top: 50%; transform: translate(-50%, -50%); }
|
||||||
|
.launch-wordmark { width: 176px; height: 33px; }
|
||||||
|
.launch-screen > .spinner { position: absolute; z-index: 3; bottom: 12%; }
|
||||||
|
|
||||||
|
.app-shell { width: 100%; height: 100%; display: grid; grid-template-columns: 238px 1fr; background: #0a0b0b; }
|
||||||
|
.sidebar { position: relative; display: flex; flex-direction: column; padding: 104px 18px 18px; border-right: 1px solid rgba(255,255,255,.09); background: #080909; }
|
||||||
|
.sidebar-brand { top: 43px; left: 26px; }
|
||||||
|
.sidebar-wordmark { width: 137px; height: 26px; }
|
||||||
|
.sidebar nav { display: flex; flex-direction: column; gap: 4px; }
|
||||||
|
.nav-item, .node-switcher, .profile-button { display: flex; align-items: center; width: 100%; border: 0; border-radius: 8px; background: transparent; color: rgba(255,255,255,.52); cursor: pointer; }
|
||||||
|
.nav-item { gap: 13px; height: 44px; padding: 0 13px; font-size: 13px; }
|
||||||
|
.nav-item:hover, .nav-item.active { color: #fff; background: rgba(255,255,255,.055); }
|
||||||
|
.compose-button { height: 44px; display: flex; align-items: center; justify-content: center; gap: 9px; margin-top: 22px; border: 0; border-radius: 8px; color: #0a0b0b; background: #f4f4ef; font-size: 12px; font-weight: 700; cursor: pointer; }
|
||||||
|
.sidebar-bottom { margin-top: auto; }
|
||||||
|
.node-switcher { gap: 10px; padding: 11px 10px; }
|
||||||
|
.node-switcher:hover, .profile-button:hover { background: rgba(255,255,255,.05); }
|
||||||
|
.node-switcher > span, .profile-button > span:not(.avatar) { min-width: 0; flex: 1; display: flex; flex-direction: column; align-items: flex-start; }
|
||||||
|
.node-switcher strong, .profile-button strong { max-width: 130px; overflow: hidden; color: rgba(255,255,255,.83); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.node-switcher small, .profile-button small { max-width: 130px; overflow: hidden; color: rgba(255,255,255,.3); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.profile-button { gap: 10px; margin-top: 7px; padding: 9px; border-top: 1px solid rgba(255,255,255,.06); border-radius: 0 0 8px 8px; text-align: left; }
|
||||||
|
.avatar { width: 30px; height: 30px; display: grid; flex: 0 0 auto; place-items: center; border: 1px solid rgba(255,255,255,.22); border-radius: 50%; background: rgba(255,255,255,.08); font-size: 11px; font-weight: 700; }
|
||||||
|
.home-content { min-width: 0; display: flex; flex-direction: column; }
|
||||||
|
.home-header { height: 70px; display: flex; align-items: center; justify-content: space-between; padding: 18px 28px 0; border-bottom: 1px solid rgba(255,255,255,.08); }
|
||||||
|
.home-header h1 { margin: 0; font: 700 17px Manrope, sans-serif; }
|
||||||
|
.connected-pill { display: flex; align-items: center; gap: 7px; color: rgba(255,255,255,.4); font: 500 8px "Roboto Mono", monospace; letter-spacing: 1px; }
|
||||||
|
.connected-pill i { width: 5px; height: 5px; border-radius: 50%; background: #78dc9b; box-shadow: 0 0 8px rgba(120,220,155,.7); }
|
||||||
|
.home-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 35px; text-align: center; }
|
||||||
|
.home-empty .eyebrow { margin: 26px 0 17px; }
|
||||||
|
.home-empty h2 { margin: 0; font: 700 clamp(32px, 4vw, 50px)/1.05 Manrope, sans-serif; letter-spacing: -2.8px; }
|
||||||
|
.home-empty > p:last-child { max-width: 470px; margin: 20px 0 0; color: rgba(255,255,255,.4); font-size: 13px; line-height: 1.7; }
|
||||||
|
.home-empty strong { color: rgba(255,255,255,.68); }
|
||||||
|
.orbit-mark { position: relative; width: 76px; height: 76px; }
|
||||||
|
.orbit-mark span { position: absolute; inset: 8px; border: 1px solid rgba(255,255,255,.16); border-radius: 50%; }
|
||||||
|
.orbit-mark span:nth-child(2) { inset: 20px; border-color: rgba(255,255,255,.35); }
|
||||||
|
.orbit-mark i { position: absolute; left: 50%; top: 0; width: 5px; height: 5px; border-radius: 50%; background: #fff; box-shadow: 0 0 8px #fff; animation: orbit 5s linear infinite; transform-origin: 0 38px; }
|
||||||
|
@keyframes orbit { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
@media (max-width: 850px) { .onboarding-shell { grid-template-columns: 38% 62%; } .signal-copy h2 { font-size: 38px; letter-spacing: -2.4px; } .form-wrap { width: calc(100% - 54px); } .panel-brand, .signal-copy { left: 32px; } .panel-status { left: 32px; } }
|
||||||
|
@media (max-height: 670px) { .form-wrap { align-items: flex-start; padding-top: 78px; } .form-wrap h1 { font-size: 42px; } .lede { margin-bottom: 24px; } .signal-copy { bottom: 74px; } .signal-copy h2 { font-size: 39px; } .field-grid { gap: 9px; } .text-field { height: 46px; margin-bottom: 11px; } .handle-field { height: 46px; margin-bottom: 11px; } .primary-button { height: 49px; margin-top: 12px; } }
|
||||||
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; } }
|
||||||
@@ -0,0 +1,449 @@
|
|||||||
|
import { FormEvent, useEffect, useRef, useState } from "react";
|
||||||
|
import { invoke, isTauri } from "@tauri-apps/api/core";
|
||||||
|
import "./App.css";
|
||||||
|
|
||||||
|
type Screen = "node" | "login" | "register" | "home";
|
||||||
|
|
||||||
|
type NodeInfo = {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
accentColor?: string;
|
||||||
|
domain?: string;
|
||||||
|
logoUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: string;
|
||||||
|
handle: string;
|
||||||
|
displayName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Connection = {
|
||||||
|
nodeUrl: string;
|
||||||
|
node: NodeInfo;
|
||||||
|
user: User | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AuthResponse = { user: User };
|
||||||
|
|
||||||
|
const DEFAULT_NODE = "synapsis.social";
|
||||||
|
const SAVED_NODE_KEY = "synapsis.nodeUrl";
|
||||||
|
|
||||||
|
function readableError(error: unknown) {
|
||||||
|
if (typeof error === "string") return error;
|
||||||
|
if (error instanceof Error) return error.message;
|
||||||
|
return "Something went wrong. Please try again.";
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayHost(url: string) {
|
||||||
|
try {
|
||||||
|
return new URL(url).host;
|
||||||
|
} catch {
|
||||||
|
return url.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Icon({ name, size = 20 }: { name: string; size?: number }) {
|
||||||
|
const paths: Record<string, React.ReactNode> = {
|
||||||
|
arrow: <><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></>,
|
||||||
|
back: <><path d="m15 18-6-6 6-6"/></>,
|
||||||
|
check: <><path d="m5 12 4 4L19 6"/></>,
|
||||||
|
home: <><path d="m3 11 9-8 9 8"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/></>,
|
||||||
|
search: <><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></>,
|
||||||
|
bell: <><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9"/><path d="M10 21h4"/></>,
|
||||||
|
message: <><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></>,
|
||||||
|
plus: <><path d="M12 5v14M5 12h14"/></>,
|
||||||
|
node: <><circle cx="12" cy="12" r="2"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="5" cy="18" r="2"/><circle cx="19" cy="18" r="2"/><path d="m7 7 3.5 3M17 7l-3.5 3M7 17l3.5-3M17 17l-3.5-3"/></>,
|
||||||
|
logout: <><path d="M10 17l5-5-5-5M15 12H3"/><path d="M14 3h5a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5"/></>,
|
||||||
|
};
|
||||||
|
return <svg aria-hidden="true" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">{paths[name]}</svg>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wordmark({ className = "" }: { className?: string }) {
|
||||||
|
return <img className={`wordmark ${className}`} src="/logotext.svg" alt="Synapsis" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignalField() {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
if (!context) return;
|
||||||
|
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
|
let animation = 0;
|
||||||
|
let width = 0;
|
||||||
|
let height = 0;
|
||||||
|
let points: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
driftX: number;
|
||||||
|
driftY: number;
|
||||||
|
phaseX: number;
|
||||||
|
phaseY: number;
|
||||||
|
speedX: number;
|
||||||
|
speedY: number;
|
||||||
|
radius: number;
|
||||||
|
}[] = [];
|
||||||
|
let triangles: [number, number, number][] = [];
|
||||||
|
|
||||||
|
function random(seed: number) {
|
||||||
|
const value = Math.sin(seed * 999.17) * 43758.5453;
|
||||||
|
return value - Math.floor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const scale = window.devicePixelRatio || 1;
|
||||||
|
width = canvas!.clientWidth;
|
||||||
|
height = canvas!.clientHeight;
|
||||||
|
canvas!.width = width * scale;
|
||||||
|
canvas!.height = height * scale;
|
||||||
|
context!.setTransform(scale, 0, 0, scale, 0, 0);
|
||||||
|
const count = Math.min(64, Math.max(34, Math.round(width / 18)));
|
||||||
|
points = Array.from({ length: count }, (_, index) => ({
|
||||||
|
x: random(index + 11) * width,
|
||||||
|
y: random(index + 97) * height,
|
||||||
|
driftX: 9 + random(index + 207) * 16,
|
||||||
|
driftY: 9 + random(index + 307) * 16,
|
||||||
|
phaseX: random(index + 507) * Math.PI * 2,
|
||||||
|
phaseY: random(index + 607) * Math.PI * 2,
|
||||||
|
speedX: 0.055 + random(index + 707) * 0.055,
|
||||||
|
speedY: 0.05 + random(index + 807) * 0.06,
|
||||||
|
radius: 0.7 + random(index + 407) * 1.15,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Keep the mesh topology stable. The previous version rebuilt the
|
||||||
|
// nearest-neighbour fan every frame, which made edges visibly pop.
|
||||||
|
triangles = [];
|
||||||
|
for (let anchor = 0; anchor < points.length; anchor += 3) {
|
||||||
|
const ring = points
|
||||||
|
.map((point, index) => ({
|
||||||
|
index,
|
||||||
|
distance: Math.hypot(point.x - points[anchor].x, point.y - points[anchor].y),
|
||||||
|
angle: Math.atan2(point.y - points[anchor].y, point.x - points[anchor].x),
|
||||||
|
}))
|
||||||
|
.filter((item) => item.index !== anchor && item.distance < 230)
|
||||||
|
.sort((left, right) => left.distance - right.distance)
|
||||||
|
.slice(0, 6)
|
||||||
|
.sort((left, right) => left.angle - right.angle);
|
||||||
|
for (let index = 0; index < ring.length - 1; index += 2) {
|
||||||
|
if (Math.hypot(
|
||||||
|
points[ring[index].index].x - points[ring[index + 1].index].x,
|
||||||
|
points[ring[index].index].y - points[ring[index + 1].index].y,
|
||||||
|
) < 220) {
|
||||||
|
triangles.push([anchor, ring[index].index, ring[index + 1].index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(milliseconds: number) {
|
||||||
|
const time = reducedMotion ? 0 : milliseconds / 1000;
|
||||||
|
context!.clearRect(0, 0, width, height);
|
||||||
|
context!.fillStyle = "#060707";
|
||||||
|
context!.fillRect(0, 0, width, height);
|
||||||
|
const positions = points.map((point) => ({
|
||||||
|
...point,
|
||||||
|
px: point.x + Math.sin(time * point.speedX + point.phaseX) * point.driftX,
|
||||||
|
py: point.y + Math.sin(time * point.speedY + point.phaseY) * point.driftY,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (let left = 0; left < positions.length; left++) {
|
||||||
|
for (let right = left + 1; right < positions.length; right++) {
|
||||||
|
const dx = positions[left].px - positions[right].px;
|
||||||
|
const dy = positions[left].py - positions[right].py;
|
||||||
|
const distance = Math.hypot(dx, dy);
|
||||||
|
if (distance < 112) {
|
||||||
|
context!.strokeStyle = `rgba(255,255,255,${(1 - distance / 112) * 0.1})`;
|
||||||
|
context!.lineWidth = 0.7;
|
||||||
|
context!.beginPath();
|
||||||
|
context!.moveTo(positions[left].px, positions[left].py);
|
||||||
|
context!.lineTo(positions[right].px, positions[right].py);
|
||||||
|
context!.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signals = [0, 1].map((index) => ({
|
||||||
|
x: width * (0.5 + 0.39 * Math.sin(time * (index ? 0.082 : 0.105) + index * 2.1)),
|
||||||
|
y: height * (0.5 + 0.4 * Math.sin(time * (index ? 0.112 : 0.078) + index * 1.3)),
|
||||||
|
intensity: 0.48 + 0.24 * Math.sin(time * 0.32 + index * Math.PI),
|
||||||
|
}));
|
||||||
|
|
||||||
|
triangles.forEach(([firstIndex, secondIndex, thirdIndex], triangleIndex) => {
|
||||||
|
const first = positions[firstIndex];
|
||||||
|
const second = positions[secondIndex];
|
||||||
|
const third = positions[thirdIndex];
|
||||||
|
const centerX = (first.px + second.px + third.px) / 3;
|
||||||
|
const centerY = (first.py + second.py + third.py) / 3;
|
||||||
|
const signalStrength = Math.max(...signals.map((signal) => {
|
||||||
|
const proximity = Math.max(0, 1 - Math.hypot(centerX - signal.x, centerY - signal.y) / 285);
|
||||||
|
const easedProximity = proximity * proximity * (3 - 2 * proximity);
|
||||||
|
return easedProximity * signal.intensity;
|
||||||
|
}));
|
||||||
|
const breath = 0.84 + 0.16 * Math.sin(time * 0.24 + triangleIndex * 0.41);
|
||||||
|
const alpha = signalStrength * breath;
|
||||||
|
if (alpha < 0.004) return;
|
||||||
|
context!.beginPath();
|
||||||
|
context!.moveTo(first.px, first.py);
|
||||||
|
context!.lineTo(second.px, second.py);
|
||||||
|
context!.lineTo(third.px, third.py);
|
||||||
|
context!.closePath();
|
||||||
|
context!.fillStyle = `rgba(255,255,255,${alpha * 0.03})`;
|
||||||
|
context!.strokeStyle = `rgba(255,255,255,${alpha * 0.38})`;
|
||||||
|
context!.lineWidth = 0.7;
|
||||||
|
context!.fill();
|
||||||
|
context!.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
positions.forEach((point) => {
|
||||||
|
const glow = Math.max(...signals.map((signal) => signal.intensity * Math.max(0, 1 - Math.hypot(point.px - signal.x, point.py - signal.y) / 200)));
|
||||||
|
context!.fillStyle = `rgba(255,255,255,${0.28 + glow * 0.58})`;
|
||||||
|
context!.beginPath();
|
||||||
|
context!.arc(point.px, point.py, point.radius + glow * 0.8, 0, Math.PI * 2);
|
||||||
|
context!.fill();
|
||||||
|
});
|
||||||
|
if (!reducedMotion) animation = requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(resize);
|
||||||
|
observer.observe(canvas);
|
||||||
|
resize();
|
||||||
|
if (reducedMotion) draw(0);
|
||||||
|
else animation = requestAnimationFrame(draw);
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
cancelAnimationFrame(animation);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <canvas ref={canvasRef} className="signal-field" aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [screen, setScreen] = useState<Screen>("node");
|
||||||
|
const [nodeInput, setNodeInput] = useState(DEFAULT_NODE);
|
||||||
|
const [nodeUrl, setNodeUrl] = useState("");
|
||||||
|
const [node, setNode] = useState<NodeInfo>({ name: "Synapsis" });
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [working, setWorking] = useState(false);
|
||||||
|
const [bootstrapping, setBootstrapping] = useState(true);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedNode = localStorage.getItem(SAVED_NODE_KEY);
|
||||||
|
if (!savedNode || !isTauri()) {
|
||||||
|
setBootstrapping(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invoke<Connection>("connect", { nodeUrl: savedNode })
|
||||||
|
.then((connection) => {
|
||||||
|
setNodeUrl(connection.nodeUrl);
|
||||||
|
setNode(connection.node);
|
||||||
|
setNodeInput(displayHost(connection.nodeUrl));
|
||||||
|
setUser(connection.user);
|
||||||
|
setScreen(connection.user ? "home" : "login");
|
||||||
|
})
|
||||||
|
.catch(() => setScreen("node"))
|
||||||
|
.finally(() => setBootstrapping(false));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function connect(event: FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!isTauri()) {
|
||||||
|
setError("Open this screen with “bun run tauri dev” to connect to a node.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWorking(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const connection = await invoke<Connection>("connect", { nodeUrl: nodeInput });
|
||||||
|
localStorage.setItem(SAVED_NODE_KEY, connection.nodeUrl);
|
||||||
|
setNodeUrl(connection.nodeUrl);
|
||||||
|
setNode(connection.node);
|
||||||
|
setUser(connection.user);
|
||||||
|
setScreen(connection.user ? "home" : "login");
|
||||||
|
} catch (caught) {
|
||||||
|
setError(readableError(caught));
|
||||||
|
} finally {
|
||||||
|
setWorking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login(event: FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
const data = new FormData(event.currentTarget);
|
||||||
|
setWorking(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const response = await invoke<AuthResponse>("login", {
|
||||||
|
nodeUrl,
|
||||||
|
email: data.get("email"),
|
||||||
|
password: data.get("password"),
|
||||||
|
});
|
||||||
|
setUser(response.user);
|
||||||
|
setScreen("home");
|
||||||
|
} catch (caught) {
|
||||||
|
setError(readableError(caught));
|
||||||
|
} finally {
|
||||||
|
setWorking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(event: FormEvent<HTMLFormElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
const data = new FormData(event.currentTarget);
|
||||||
|
const password = String(data.get("password"));
|
||||||
|
if (password !== String(data.get("confirmation"))) {
|
||||||
|
setError("Passwords do not match.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWorking(true);
|
||||||
|
setError("");
|
||||||
|
try {
|
||||||
|
const response = await invoke<AuthResponse>("register", {
|
||||||
|
nodeUrl,
|
||||||
|
displayName: data.get("displayName"),
|
||||||
|
handle: data.get("handle"),
|
||||||
|
email: data.get("email"),
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
setUser(response.user);
|
||||||
|
setScreen("home");
|
||||||
|
} catch (caught) {
|
||||||
|
setError(readableError(caught));
|
||||||
|
} finally {
|
||||||
|
setWorking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
setWorking(true);
|
||||||
|
try {
|
||||||
|
await invoke("logout", { nodeUrl });
|
||||||
|
} finally {
|
||||||
|
setUser(null);
|
||||||
|
setScreen("login");
|
||||||
|
setWorking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseAnotherNode() {
|
||||||
|
setError("");
|
||||||
|
setScreen("node");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bootstrapping) {
|
||||||
|
return <main className="launch-screen"><SignalField/><div className="launch-mark"><Wordmark className="launch-wordmark"/></div><span className="spinner" aria-label="Loading Synapsis"/></main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen === "home" && user) {
|
||||||
|
const name = user.displayName?.trim() || user.handle.replace(/^@/, "").split("@")[0];
|
||||||
|
return (
|
||||||
|
<main className="app-shell">
|
||||||
|
<aside className="sidebar" data-tauri-drag-region>
|
||||||
|
<div className="sidebar-brand"><Wordmark className="sidebar-wordmark"/></div>
|
||||||
|
<nav aria-label="Main navigation">
|
||||||
|
<button className="nav-item active"><Icon name="home"/><span>Home</span></button>
|
||||||
|
<button className="nav-item"><Icon name="search"/><span>Explore</span></button>
|
||||||
|
<button className="nav-item"><Icon name="bell"/><span>Notifications</span></button>
|
||||||
|
<button className="nav-item"><Icon name="message"/><span>Messages</span></button>
|
||||||
|
</nav>
|
||||||
|
<button className="compose-button"><Icon name="plus"/><span>New post</span></button>
|
||||||
|
<div className="sidebar-bottom">
|
||||||
|
<button className="node-switcher" onClick={chooseAnotherNode}><Icon name="node"/><span><strong>{node.name || "Synapsis"}</strong><small>{displayHost(nodeUrl)}</small></span></button>
|
||||||
|
<button className="profile-button" onClick={logout} disabled={working} title="Log out">
|
||||||
|
<span className="avatar">{name.slice(0, 1).toUpperCase()}</span><span><strong>{name}</strong><small>{user.handle.startsWith("@") ? user.handle : `@${user.handle}`}</small></span><Icon name="logout" size={17}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<section className="home-content">
|
||||||
|
<header className="home-header" data-tauri-drag-region><h1>Home</h1><span className="connected-pill"><i/>Connected</span></header>
|
||||||
|
<div className="home-empty">
|
||||||
|
<div className="orbit-mark"><span/><span/><i/></div>
|
||||||
|
<p className="eyebrow">SESSION ESTABLISHED</p>
|
||||||
|
<h2>Welcome to the network,<br/>{name}.</h2>
|
||||||
|
<p>Your desktop session is connected to <strong>{displayHost(nodeUrl)}</strong>. The timeline is the next piece to bring over from iOS.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="onboarding-shell">
|
||||||
|
<section className="signal-panel" data-tauri-drag-region>
|
||||||
|
<SignalField/>
|
||||||
|
<div className="signal-shade"/>
|
||||||
|
<div className="panel-brand"><Wordmark className="panel-wordmark"/></div>
|
||||||
|
<div className="signal-copy">
|
||||||
|
<p className="eyebrow"><i/> THE SIGNAL IS ALREADY MOVING</p>
|
||||||
|
<h2>Your node.<br/>Your network.<br/>Your identity.</h2>
|
||||||
|
</div>
|
||||||
|
<div className="panel-status"><span>DECENTRALIZED SOCIAL</span><span>DESKTOP / 01</span></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="form-panel">
|
||||||
|
<div className="drag-strip" data-tauri-drag-region/>
|
||||||
|
{screen !== "node" && <button className="back-button" onClick={() => { setError(""); setScreen(screen === "register" ? "login" : "node"); }}><Icon name="back" size={18}/>Back</button>}
|
||||||
|
<div className="form-wrap">
|
||||||
|
{screen === "node" && (
|
||||||
|
<form onSubmit={connect}>
|
||||||
|
<div className="step-label"><span>01</span><i/><span>CONNECT</span></div>
|
||||||
|
<h1>Choose your<br/>node.</h1>
|
||||||
|
<p className="lede">Synapsis is a network of independent communities. Enter the node where your account lives.</p>
|
||||||
|
<label htmlFor="node-url">Node URL</label>
|
||||||
|
<div className="url-field"><span>https://</span><input id="node-url" value={nodeInput.replace(/^https?:\/\//, "")} onChange={(event) => setNodeInput(event.target.value)} autoCapitalize="none" autoCorrect="off" spellCheck={false} autoFocus/></div>
|
||||||
|
{error && <p className="error-message" role="alert">{error}</p>}
|
||||||
|
<button className="primary-button" disabled={!nodeInput.trim() || working}>{working ? <><span className="spinner dark"/>Connecting…</> : <>Continue<Icon name="arrow" size={19}/></>}</button>
|
||||||
|
<p className="privacy-note"><span className="lock-dot">◆</span> Your credentials are sent only to the node you choose.</p>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{screen === "login" && (
|
||||||
|
<form onSubmit={login}>
|
||||||
|
<div className="connection-label"><span><Icon name="check" size={12}/></span>CONNECTED / {displayHost(nodeUrl).toUpperCase()}</div>
|
||||||
|
<h1>Welcome<br/>back.</h1>
|
||||||
|
<p className="lede">Sign in to continue on this node.</p>
|
||||||
|
<label htmlFor="email">Email</label>
|
||||||
|
<input className="text-field" id="email" name="email" type="email" placeholder="you@example.com" autoComplete="username" required autoFocus/>
|
||||||
|
<label htmlFor="password">Password</label>
|
||||||
|
<input className="text-field" id="password" name="password" type="password" placeholder="Enter your password" autoComplete="current-password" required/>
|
||||||
|
<p className="field-note">Use the email associated with your username.</p>
|
||||||
|
{error && <p className="error-message" role="alert">{error}</p>}
|
||||||
|
<button className="primary-button" disabled={working}>{working ? <><span className="spinner dark"/>Signing in…</> : <>Sign in<Icon name="arrow" size={19}/></>}</button>
|
||||||
|
<div className="divider"><i/><span>NEW HERE?</span><i/></div>
|
||||||
|
<button type="button" className="secondary-button" onClick={() => { setError(""); setScreen("register"); }}>Create account</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{screen === "register" && (
|
||||||
|
<form onSubmit={register}>
|
||||||
|
<div className="connection-label"><span><Icon name="check" size={12}/></span>CONNECTED / {displayHost(nodeUrl).toUpperCase()}</div>
|
||||||
|
<h1>Create your<br/>account.</h1>
|
||||||
|
<p className="lede compact">Create your identity on this node.</p>
|
||||||
|
<div className="field-grid">
|
||||||
|
<div><label htmlFor="display-name">Display name</label><input className="text-field" id="display-name" name="displayName" autoComplete="name" required autoFocus/></div>
|
||||||
|
<div><label htmlFor="handle">Username</label><div className="handle-field"><span>@</span><input id="handle" name="handle" autoCapitalize="none" autoComplete="username" required/></div></div>
|
||||||
|
</div>
|
||||||
|
<label htmlFor="register-email">Email</label>
|
||||||
|
<input className="text-field" id="register-email" name="email" type="email" autoComplete="email" required/>
|
||||||
|
<div className="field-grid">
|
||||||
|
<div><label htmlFor="new-password">Password</label><input className="text-field" id="new-password" name="password" type="password" minLength={8} autoComplete="new-password" required/></div>
|
||||||
|
<div><label htmlFor="confirmation">Confirm</label><input className="text-field" id="confirmation" name="confirmation" type="password" minLength={8} autoComplete="new-password" required/></div>
|
||||||
|
</div>
|
||||||
|
<p className="field-note">Use at least 8 characters.</p>
|
||||||
|
{error && <p className="error-message" role="alert">{error}</p>}
|
||||||
|
<button className="primary-button" disabled={working}>{working ? <><span className="spinner dark"/>Creating…</> : <>Create account<Icon name="arrow" size={19}/></>}</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="step-footer"><span>{screen === "node" ? "STEP 1 OF 2" : screen === "login" ? "STEP 2 OF 2" : "NEW ACCOUNT"}</span><span className="step-dots"><i className="active"/><i className={screen !== "node" ? "active" : ""}/></span></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
// @ts-expect-error process is a nodejs global
|
||||||
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [react()],
|
||||||
|
|
||||||
|
// 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: 1430,
|
||||||
|
strictPort: true,
|
||||||
|
host: host || false,
|
||||||
|
hmr: host
|
||||||
|
? {
|
||||||
|
protocol: "ws",
|
||||||
|
host,
|
||||||
|
port: 1431,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
watch: {
|
||||||
|
// 3. tell Vite to ignore watching `src-tauri`
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||