+66
.github/workflows/publish.yaml
+66
.github/workflows/publish.yaml
···
1
+
name: "publish"
2
+
3
+
on:
4
+
workflow_dispatch:
5
+
push:
6
+
branches:
7
+
- release
8
+
9
+
jobs:
10
+
publish-tauri:
11
+
permissions:
12
+
contents: write
13
+
strategy:
14
+
fail-fast: false
15
+
matrix:
16
+
include:
17
+
- platform: "macos-latest" # for Arm based macs (M1 and above).
18
+
args: "--target aarch64-apple-darwin"
19
+
- platform: "macos-latest" # for Intel based macs.
20
+
args: "--target x86_64-apple-darwin"
21
+
- platform: "ubuntu-22.04"
22
+
args: ""
23
+
- platform: "windows-latest"
24
+
args: ""
25
+
26
+
runs-on: ${{ matrix.platform }}
27
+
steps:
28
+
- uses: actions/checkout@v4
29
+
30
+
- name: install dependencies (ubuntu only)
31
+
if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
32
+
run: |
33
+
sudo apt-get update
34
+
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
35
+
36
+
- name: setup node
37
+
uses: actions/setup-node@v4
38
+
with:
39
+
node-version: lts/*
40
+
cache: "bun" # Set this to npm, yarn or pnpm.
41
+
42
+
- name: install Rust stable
43
+
uses: dtolnay/rust-toolchain@stable # Set this to dtolnay/rust-toolchain@nightly
44
+
with:
45
+
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
46
+
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
47
+
48
+
- name: Rust cache
49
+
uses: swatinem/rust-cache@v2
50
+
with:
51
+
workspaces: "./src-tauri -> target"
52
+
53
+
- name: install frontend dependencies
54
+
# If you don't have `beforeBuildCommand` configured you may want to build your frontend here too.
55
+
run: bun install # change this to npm or pnpm depending on which one you use.
56
+
57
+
- uses: tauri-apps/tauri-action@v0
58
+
env:
59
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
+
with:
61
+
tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
62
+
releaseName: "App v__VERSION__"
63
+
releaseBody: "See the assets to download this version and install."
64
+
releaseDraft: true
65
+
prerelease: false
66
+
args: ${{ matrix.args }}
+14
bun.lock
+14
bun.lock
···
9
9
"@atproto/jwk-webcrypto": "^0.1.9",
10
10
"@atproto/oauth-client-browser": "^0.3.27",
11
11
"@radix-ui/react-avatar": "^1.1.10",
12
+
"@radix-ui/react-checkbox": "^1.3.2",
12
13
"@radix-ui/react-dropdown-menu": "^2.1.15",
14
+
"@radix-ui/react-label": "^2.1.7",
13
15
"@radix-ui/react-progress": "^1.1.7",
14
16
"@radix-ui/react-scroll-area": "^1.2.9",
15
17
"@radix-ui/react-slot": "^1.2.3",
18
+
"@radix-ui/react-switch": "^1.2.5",
16
19
"@tailwindcss/vite": "^4.1.11",
17
20
"@tauri-apps/api": "^2",
18
21
"@tauri-apps/plugin-autostart": "~2",
···
20
23
"@tauri-apps/plugin-fs": "~2",
21
24
"@tauri-apps/plugin-opener": "^2",
22
25
"@tauri-apps/plugin-store": "~2",
26
+
"@tauri-apps/plugin-updater": "~2",
23
27
"antd": "^5.26.4",
24
28
"class-variance-authority": "^0.7.1",
25
29
"clsx": "^2.1.1",
···
411
415
412
416
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="],
413
417
418
+
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="],
419
+
414
420
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
415
421
416
422
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
···
428
434
"@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
429
435
430
436
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
437
+
438
+
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
431
439
432
440
"@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew=="],
433
441
···
447
455
448
456
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
449
457
458
+
"@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ=="],
459
+
450
460
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
451
461
452
462
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
···
458
468
"@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="],
459
469
460
470
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
471
+
472
+
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
461
473
462
474
"@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="],
463
475
···
628
640
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ=="],
629
641
630
642
"@tauri-apps/plugin-store": ["@tauri-apps/plugin-store@2.3.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-mre8er0nXPhyEWQzWCpUd+UnEoBQYcoA5JYlwpwOV9wcxKqlXTGfminpKsE37ic8NUb2BIZqf0QQ9/U3ib2+/A=="],
643
+
644
+
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="],
631
645
632
646
"@theguild/remark-mermaid": ["@theguild/remark-mermaid@0.2.0", "", { "dependencies": { "mermaid": "^11.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-o8n57TJy0OI4PCrNw8z6S+vpHtrwoQZzTA5Y3fL0U1NDRIoMg/78duWgEBFsCZcWM1G6zjE91yg1aKCsDwgE2Q=="],
633
647
+4
package.json
+4
package.json
···
15
15
"@atproto/jwk-webcrypto": "^0.1.9",
16
16
"@atproto/oauth-client-browser": "^0.3.27",
17
17
"@radix-ui/react-avatar": "^1.1.10",
18
+
"@radix-ui/react-checkbox": "^1.3.2",
18
19
"@radix-ui/react-dropdown-menu": "^2.1.15",
20
+
"@radix-ui/react-label": "^2.1.7",
19
21
"@radix-ui/react-progress": "^1.1.7",
20
22
"@radix-ui/react-scroll-area": "^1.2.9",
21
23
"@radix-ui/react-slot": "^1.2.3",
24
+
"@radix-ui/react-switch": "^1.2.5",
22
25
"@tailwindcss/vite": "^4.1.11",
23
26
"@tauri-apps/api": "^2",
24
27
"@tauri-apps/plugin-autostart": "~2",
···
26
29
"@tauri-apps/plugin-fs": "~2",
27
30
"@tauri-apps/plugin-opener": "^2",
28
31
"@tauri-apps/plugin-store": "~2",
32
+
"@tauri-apps/plugin-updater": "~2",
29
33
"antd": "^5.26.4",
30
34
"class-variance-authority": "^0.7.1",
31
35
"clsx": "^2.1.1",
+365
src-tauri/Cargo.lock
+365
src-tauri/Cargo.lock
···
63
63
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
64
64
65
65
[[package]]
66
+
name = "arbitrary"
67
+
version = "1.4.1"
68
+
source = "registry+https://github.com/rust-lang/crates.io-index"
69
+
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
70
+
dependencies = [
71
+
"derive_arbitrary",
72
+
]
73
+
74
+
[[package]]
66
75
name = "async-broadcast"
67
76
version = "0.7.2"
68
77
source = "registry+https://github.com/rust-lang/crates.io-index"
···
238
247
"tauri-plugin-opener",
239
248
"tauri-plugin-single-instance",
240
249
"tauri-plugin-store",
250
+
"tauri-plugin-updater",
241
251
]
242
252
243
253
[[package]]
···
742
752
]
743
753
744
754
[[package]]
755
+
name = "derive_arbitrary"
756
+
version = "1.4.1"
757
+
source = "registry+https://github.com/rust-lang/crates.io-index"
758
+
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
759
+
dependencies = [
760
+
"proc-macro2",
761
+
"quote",
762
+
"syn 2.0.104",
763
+
]
764
+
765
+
[[package]]
745
766
name = "derive_more"
746
767
version = "0.99.20"
747
768
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1020
1041
]
1021
1042
1022
1043
[[package]]
1044
+
name = "filetime"
1045
+
version = "0.2.25"
1046
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1047
+
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
1048
+
dependencies = [
1049
+
"cfg-if",
1050
+
"libc",
1051
+
"libredox",
1052
+
"windows-sys 0.59.0",
1053
+
]
1054
+
1055
+
[[package]]
1023
1056
name = "flate2"
1024
1057
version = "1.1.2"
1025
1058
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1302
1335
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
1303
1336
dependencies = [
1304
1337
"cfg-if",
1338
+
"js-sys",
1305
1339
"libc",
1306
1340
"wasi 0.11.1+wasi-snapshot-preview1",
1341
+
"wasm-bindgen",
1307
1342
]
1308
1343
1309
1344
[[package]]
···
1313
1348
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1314
1349
dependencies = [
1315
1350
"cfg-if",
1351
+
"js-sys",
1316
1352
"libc",
1317
1353
"r-efi",
1318
1354
"wasi 0.14.2+wasi-0.2.4",
1355
+
"wasm-bindgen",
1319
1356
]
1320
1357
1321
1358
[[package]]
···
1586
1623
]
1587
1624
1588
1625
[[package]]
1626
+
name = "hyper-rustls"
1627
+
version = "0.27.7"
1628
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1629
+
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
1630
+
dependencies = [
1631
+
"http",
1632
+
"hyper",
1633
+
"hyper-util",
1634
+
"rustls",
1635
+
"rustls-pki-types",
1636
+
"tokio",
1637
+
"tokio-rustls",
1638
+
"tower-service",
1639
+
"webpki-roots",
1640
+
]
1641
+
1642
+
[[package]]
1589
1643
name = "hyper-util"
1590
1644
version = "0.1.15"
1591
1645
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1993
2047
dependencies = [
1994
2048
"bitflags 2.9.1",
1995
2049
"libc",
2050
+
"redox_syscall",
1996
2051
]
1997
2052
1998
2053
[[package]]
···
2024
2079
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
2025
2080
2026
2081
[[package]]
2082
+
name = "lru-slab"
2083
+
version = "0.1.2"
2084
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2085
+
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
2086
+
2087
+
[[package]]
2027
2088
name = "mac"
2028
2089
version = "0.1.1"
2029
2090
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2080
2141
version = "0.3.17"
2081
2142
source = "registry+https://github.com/rust-lang/crates.io-index"
2082
2143
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
2144
+
2145
+
[[package]]
2146
+
name = "minisign-verify"
2147
+
version = "0.2.4"
2148
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2149
+
checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43"
2083
2150
2084
2151
[[package]]
2085
2152
name = "miniz_oxide"
···
2380
2447
]
2381
2448
2382
2449
[[package]]
2450
+
name = "objc2-osa-kit"
2451
+
version = "0.3.1"
2452
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2453
+
checksum = "26bb88504b5a050dbba515d2414607bf5e57dd56b107bc5f0351197a3e7bdc5d"
2454
+
dependencies = [
2455
+
"bitflags 2.9.1",
2456
+
"objc2 0.6.1",
2457
+
"objc2-app-kit",
2458
+
"objc2-foundation 0.3.1",
2459
+
]
2460
+
2461
+
[[package]]
2383
2462
name = "objc2-quartz-core"
2384
2463
version = "0.2.2"
2385
2464
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2483
2562
]
2484
2563
2485
2564
[[package]]
2565
+
name = "osakit"
2566
+
version = "0.3.1"
2567
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2568
+
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
2569
+
dependencies = [
2570
+
"objc2 0.6.1",
2571
+
"objc2-foundation 0.3.1",
2572
+
"objc2-osa-kit",
2573
+
"serde",
2574
+
"serde_json",
2575
+
"thiserror 2.0.12",
2576
+
]
2577
+
2578
+
[[package]]
2486
2579
name = "pango"
2487
2580
version = "0.18.3"
2488
2581
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2859
2952
]
2860
2953
2861
2954
[[package]]
2955
+
name = "quinn"
2956
+
version = "0.11.8"
2957
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2958
+
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
2959
+
dependencies = [
2960
+
"bytes",
2961
+
"cfg_aliases",
2962
+
"pin-project-lite",
2963
+
"quinn-proto",
2964
+
"quinn-udp",
2965
+
"rustc-hash",
2966
+
"rustls",
2967
+
"socket2",
2968
+
"thiserror 2.0.12",
2969
+
"tokio",
2970
+
"tracing",
2971
+
"web-time",
2972
+
]
2973
+
2974
+
[[package]]
2975
+
name = "quinn-proto"
2976
+
version = "0.11.12"
2977
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2978
+
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
2979
+
dependencies = [
2980
+
"bytes",
2981
+
"getrandom 0.3.3",
2982
+
"lru-slab",
2983
+
"rand 0.9.1",
2984
+
"ring",
2985
+
"rustc-hash",
2986
+
"rustls",
2987
+
"rustls-pki-types",
2988
+
"slab",
2989
+
"thiserror 2.0.12",
2990
+
"tinyvec",
2991
+
"tracing",
2992
+
"web-time",
2993
+
]
2994
+
2995
+
[[package]]
2996
+
name = "quinn-udp"
2997
+
version = "0.5.13"
2998
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2999
+
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
3000
+
dependencies = [
3001
+
"cfg_aliases",
3002
+
"libc",
3003
+
"once_cell",
3004
+
"socket2",
3005
+
"tracing",
3006
+
"windows-sys 0.59.0",
3007
+
]
3008
+
3009
+
[[package]]
2862
3010
name = "quote"
2863
3011
version = "1.0.40"
2864
3012
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2899
3047
]
2900
3048
2901
3049
[[package]]
3050
+
name = "rand"
3051
+
version = "0.9.1"
3052
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3053
+
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
3054
+
dependencies = [
3055
+
"rand_chacha 0.9.0",
3056
+
"rand_core 0.9.3",
3057
+
]
3058
+
3059
+
[[package]]
2902
3060
name = "rand_chacha"
2903
3061
version = "0.2.2"
2904
3062
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2919
3077
]
2920
3078
2921
3079
[[package]]
3080
+
name = "rand_chacha"
3081
+
version = "0.9.0"
3082
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3083
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
3084
+
dependencies = [
3085
+
"ppv-lite86",
3086
+
"rand_core 0.9.3",
3087
+
]
3088
+
3089
+
[[package]]
2922
3090
name = "rand_core"
2923
3091
version = "0.5.1"
2924
3092
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2934
3102
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
2935
3103
dependencies = [
2936
3104
"getrandom 0.2.16",
3105
+
]
3106
+
3107
+
[[package]]
3108
+
name = "rand_core"
3109
+
version = "0.9.3"
3110
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3111
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
3112
+
dependencies = [
3113
+
"getrandom 0.3.3",
2937
3114
]
2938
3115
2939
3116
[[package]]
···
3054
3231
"http-body",
3055
3232
"http-body-util",
3056
3233
"hyper",
3234
+
"hyper-rustls",
3057
3235
"hyper-util",
3058
3236
"js-sys",
3059
3237
"log",
3060
3238
"percent-encoding",
3061
3239
"pin-project-lite",
3240
+
"quinn",
3241
+
"rustls",
3242
+
"rustls-pki-types",
3062
3243
"serde",
3063
3244
"serde_json",
3064
3245
"serde_urlencoded",
3065
3246
"sync_wrapper",
3066
3247
"tokio",
3248
+
"tokio-rustls",
3067
3249
"tokio-util",
3068
3250
"tower",
3069
3251
"tower-http",
···
3073
3255
"wasm-bindgen-futures",
3074
3256
"wasm-streams",
3075
3257
"web-sys",
3258
+
"webpki-roots",
3259
+
]
3260
+
3261
+
[[package]]
3262
+
name = "ring"
3263
+
version = "0.17.14"
3264
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3265
+
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
3266
+
dependencies = [
3267
+
"cc",
3268
+
"cfg-if",
3269
+
"getrandom 0.2.16",
3270
+
"libc",
3271
+
"untrusted",
3272
+
"windows-sys 0.52.0",
3076
3273
]
3077
3274
3078
3275
[[package]]
···
3092
3289
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
3093
3290
3094
3291
[[package]]
3292
+
name = "rustc-hash"
3293
+
version = "2.1.1"
3294
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3295
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3296
+
3297
+
[[package]]
3095
3298
name = "rustc_version"
3096
3299
version = "0.4.1"
3097
3300
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3114
3317
]
3115
3318
3116
3319
[[package]]
3320
+
name = "rustls"
3321
+
version = "0.23.29"
3322
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3323
+
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
3324
+
dependencies = [
3325
+
"once_cell",
3326
+
"ring",
3327
+
"rustls-pki-types",
3328
+
"rustls-webpki",
3329
+
"subtle",
3330
+
"zeroize",
3331
+
]
3332
+
3333
+
[[package]]
3334
+
name = "rustls-pki-types"
3335
+
version = "1.12.0"
3336
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3337
+
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
3338
+
dependencies = [
3339
+
"web-time",
3340
+
"zeroize",
3341
+
]
3342
+
3343
+
[[package]]
3344
+
name = "rustls-webpki"
3345
+
version = "0.103.4"
3346
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3347
+
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
3348
+
dependencies = [
3349
+
"ring",
3350
+
"rustls-pki-types",
3351
+
"untrusted",
3352
+
]
3353
+
3354
+
[[package]]
3117
3355
name = "rustversion"
3118
3356
version = "1.0.21"
3119
3357
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3535
3773
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
3536
3774
3537
3775
[[package]]
3776
+
name = "subtle"
3777
+
version = "2.6.1"
3778
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3779
+
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
3780
+
3781
+
[[package]]
3538
3782
name = "swift-rs"
3539
3783
version = "1.0.7"
3540
3784
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3648
3892
"proc-macro2",
3649
3893
"quote",
3650
3894
"syn 2.0.104",
3895
+
]
3896
+
3897
+
[[package]]
3898
+
name = "tar"
3899
+
version = "0.4.44"
3900
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3901
+
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
3902
+
dependencies = [
3903
+
"filetime",
3904
+
"libc",
3905
+
"xattr",
3651
3906
]
3652
3907
3653
3908
[[package]]
···
3897
4152
]
3898
4153
3899
4154
[[package]]
4155
+
name = "tauri-plugin-updater"
4156
+
version = "2.9.0"
4157
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4158
+
checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
4159
+
dependencies = [
4160
+
"base64 0.22.1",
4161
+
"dirs 6.0.0",
4162
+
"flate2",
4163
+
"futures-util",
4164
+
"http",
4165
+
"infer",
4166
+
"log",
4167
+
"minisign-verify",
4168
+
"osakit",
4169
+
"percent-encoding",
4170
+
"reqwest",
4171
+
"semver",
4172
+
"serde",
4173
+
"serde_json",
4174
+
"tar",
4175
+
"tauri",
4176
+
"tauri-plugin",
4177
+
"tempfile",
4178
+
"thiserror 2.0.12",
4179
+
"time",
4180
+
"tokio",
4181
+
"url",
4182
+
"windows-sys 0.60.2",
4183
+
"zip",
4184
+
]
4185
+
4186
+
[[package]]
3900
4187
name = "tauri-runtime"
3901
4188
version = "2.7.0"
3902
4189
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4109
4396
]
4110
4397
4111
4398
[[package]]
4399
+
name = "tinyvec"
4400
+
version = "1.9.0"
4401
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4402
+
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
4403
+
dependencies = [
4404
+
"tinyvec_macros",
4405
+
]
4406
+
4407
+
[[package]]
4408
+
name = "tinyvec_macros"
4409
+
version = "0.1.1"
4410
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4411
+
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
4412
+
4413
+
[[package]]
4112
4414
name = "tokio"
4113
4415
version = "1.46.1"
4114
4416
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4135
4437
"proc-macro2",
4136
4438
"quote",
4137
4439
"syn 2.0.104",
4440
+
]
4441
+
4442
+
[[package]]
4443
+
name = "tokio-rustls"
4444
+
version = "0.26.2"
4445
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4446
+
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
4447
+
dependencies = [
4448
+
"rustls",
4449
+
"tokio",
4138
4450
]
4139
4451
4140
4452
[[package]]
···
4433
4745
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
4434
4746
4435
4747
[[package]]
4748
+
name = "untrusted"
4749
+
version = "0.9.0"
4750
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4751
+
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
4752
+
4753
+
[[package]]
4436
4754
name = "url"
4437
4755
version = "2.5.4"
4438
4756
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4647
4965
]
4648
4966
4649
4967
[[package]]
4968
+
name = "web-time"
4969
+
version = "1.1.0"
4970
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4971
+
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
4972
+
dependencies = [
4973
+
"js-sys",
4974
+
"wasm-bindgen",
4975
+
]
4976
+
4977
+
[[package]]
4650
4978
name = "webkit2gtk"
4651
4979
version = "2.0.1"
4652
4980
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4688
5016
"pkg-config",
4689
5017
"soup3-sys",
4690
5018
"system-deps",
5019
+
]
5020
+
5021
+
[[package]]
5022
+
name = "webpki-roots"
5023
+
version = "1.0.1"
5024
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5025
+
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
5026
+
dependencies = [
5027
+
"rustls-pki-types",
4691
5028
]
4692
5029
4693
5030
[[package]]
···
5242
5579
]
5243
5580
5244
5581
[[package]]
5582
+
name = "xattr"
5583
+
version = "1.5.1"
5584
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5585
+
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
5586
+
dependencies = [
5587
+
"libc",
5588
+
"rustix",
5589
+
]
5590
+
5591
+
[[package]]
5245
5592
name = "yoke"
5246
5593
version = "0.8.0"
5247
5594
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5367
5714
]
5368
5715
5369
5716
[[package]]
5717
+
name = "zeroize"
5718
+
version = "1.8.1"
5719
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5720
+
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
5721
+
5722
+
[[package]]
5370
5723
name = "zerotrie"
5371
5724
version = "0.2.2"
5372
5725
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5397
5750
"proc-macro2",
5398
5751
"quote",
5399
5752
"syn 2.0.104",
5753
+
]
5754
+
5755
+
[[package]]
5756
+
name = "zip"
5757
+
version = "4.3.0"
5758
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5759
+
checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b"
5760
+
dependencies = [
5761
+
"arbitrary",
5762
+
"crc32fast",
5763
+
"indexmap 2.10.0",
5764
+
"memchr",
5400
5765
]
5401
5766
5402
5767
[[package]]
+1
src-tauri/Cargo.toml
+1
src-tauri/Cargo.toml
+4
-1
src-tauri/capabilities/default.json
+4
-1
src-tauri/capabilities/default.json
+2
-1
src-tauri/capabilities/desktop.json
+2
-1
src-tauri/capabilities/desktop.json
+5
-1
src-tauri/src/lib.rs
+5
-1
src-tauri/src/lib.rs
···
9
9
#[cfg_attr(mobile, tauri::mobile_entry_point)]
10
10
pub fn run() {
11
11
let mut builder = tauri::Builder::default()
12
-
.plugin(tauri_plugin_autostart::init())
12
+
.plugin(tauri_plugin_updater::Builder::new().build())
13
+
.plugin(tauri_plugin_autostart::init(
14
+
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
15
+
None,
16
+
))
13
17
.plugin(tauri_plugin_fs::init())
14
18
.plugin(tauri_plugin_store::Builder::new().build())
15
19
.plugin(tauri_plugin_deep_link::init())
+71
-5
src/App.tsx
+71
-5
src/App.tsx
···
1
1
import { useState, useEffect } from "react";
2
-
import { invoke } from "@tauri-apps/api/core";
3
2
import "./App.css";
4
3
import { Button } from "./components/ui/button";
5
4
import LoginPage from "./routes/Login";
6
5
import { getCurrentWindow } from "@tauri-apps/api/window";
7
-
import { Agent } from "@atproto/api";
8
-
import { OAuthSession } from "@atproto/oauth-client-browser";
9
-
import { LoaderCircleIcon, LoaderIcon } from "lucide-react";
6
+
import { LoaderCircleIcon } from "lucide-react";
10
7
import { AuthProvider, useAuth } from "./Auth";
11
8
import { initializeLocalStorage } from "./localstorage_ployfill";
12
9
import { Home } from "./routes/Home";
13
10
import { ThemeProvider } from "./theme-provider";
14
11
import { Toaster } from "sonner";
15
12
import { ScrollArea } from "./components/ui/scroll-area";
13
+
import { BackupManager } from "./lib/backup";
14
+
import { settingsManager } from "./lib/settings";
16
15
17
16
function AppContent() {
18
-
const { isLoading, isAuthenticated, profile, client, login, logout } =
17
+
const { isLoading, isAuthenticated, profile, client, login, logout, agent } =
19
18
useAuth();
20
19
const appWindow = getCurrentWindow();
21
20
···
34
33
35
34
initStorage();
36
35
}, []);
36
+
37
+
// Auto-backup functionality
38
+
useEffect(() => {
39
+
if (!isAuthenticated || !agent) return;
40
+
41
+
let intervalId: ReturnType<typeof setInterval> | null = null;
42
+
43
+
const checkAndPerformBackup = async () => {
44
+
try {
45
+
const lastBackupDate = await settingsManager.getLastBackupDate();
46
+
const frequency = await settingsManager.getBackupFrequency();
47
+
48
+
if (!lastBackupDate) {
49
+
// No previous backup, so we should do one
50
+
await performBackup();
51
+
return;
52
+
}
53
+
54
+
const lastBackup = new Date(lastBackupDate);
55
+
const now = new Date();
56
+
const timeDiff = now.getTime() - lastBackup.getTime();
57
+
58
+
if (frequency === "daily") {
59
+
// Check if 24 hours have passed
60
+
const oneDay = 24 * 60 * 60 * 1000;
61
+
if (timeDiff >= oneDay) {
62
+
await performBackup();
63
+
}
64
+
} else if (frequency === "weekly") {
65
+
// Check if 7 days have passed
66
+
const oneWeek = 7 * 24 * 60 * 60 * 1000;
67
+
if (timeDiff >= oneWeek) {
68
+
await performBackup();
69
+
}
70
+
}
71
+
} catch (error) {
72
+
console.error("Error in automatic backup check:", error);
73
+
}
74
+
};
75
+
76
+
const performBackup = async () => {
77
+
try {
78
+
console.log("Automatic backup due, starting backup...");
79
+
const manager = new BackupManager(agent);
80
+
await manager.startBackup();
81
+
82
+
// Update the last backup date
83
+
await settingsManager.setLastBackupDate(new Date().toISOString());
84
+
85
+
console.log("Automatic backup completed successfully");
86
+
} catch (error) {
87
+
console.error("Automatic backup failed:", error);
88
+
}
89
+
};
90
+
91
+
// Check immediately when authenticated
92
+
checkAndPerformBackup();
93
+
94
+
// Set up interval to check every hour
95
+
intervalId = setInterval(checkAndPerformBackup, 60 * 60 * 1000);
96
+
97
+
return () => {
98
+
if (intervalId) {
99
+
clearInterval(intervalId);
100
+
}
101
+
};
102
+
}, [isAuthenticated, agent]);
37
103
38
104
return (
39
105
<main className="bg-background dark min-h-screen flex flex-col">
+1
-5
src/Auth.tsx
+1
-5
src/Auth.tsx
···
2
2
import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
3
3
import { OAuthClient, type OAuthSession } from "@atproto/oauth-client";
4
4
import { BrowserOAuthClient } from "@atproto/oauth-client-browser";
5
-
import { load, Store } from "@tauri-apps/plugin-store";
5
+
import { Store } from "@tauri-apps/plugin-store";
6
6
import {
7
7
createContext,
8
8
ReactNode,
···
10
10
useEffect,
11
11
useState,
12
12
} from "react";
13
-
14
-
interface StoredSession {
15
-
did: string;
16
-
}
17
13
18
14
interface AuthContextType {
19
15
isLoading: boolean;
+30
src/components/ui/checkbox.tsx
+30
src/components/ui/checkbox.tsx
···
1
+
import * as React from "react"
2
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+
import { CheckIcon } from "lucide-react"
4
+
5
+
import { cn } from "@/lib/utils"
6
+
7
+
function Checkbox({
8
+
className,
9
+
...props
10
+
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
11
+
return (
12
+
<CheckboxPrimitive.Root
13
+
data-slot="checkbox"
14
+
className={cn(
15
+
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
16
+
className
17
+
)}
18
+
{...props}
19
+
>
20
+
<CheckboxPrimitive.Indicator
21
+
data-slot="checkbox-indicator"
22
+
className="flex items-center justify-center text-current transition-none"
23
+
>
24
+
<CheckIcon className="size-3.5" />
25
+
</CheckboxPrimitive.Indicator>
26
+
</CheckboxPrimitive.Root>
27
+
)
28
+
}
29
+
30
+
export { Checkbox }
+22
src/components/ui/label.tsx
+22
src/components/ui/label.tsx
···
1
+
import * as React from "react"
2
+
import * as LabelPrimitive from "@radix-ui/react-label"
3
+
4
+
import { cn } from "@/lib/utils"
5
+
6
+
function Label({
7
+
className,
8
+
...props
9
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
10
+
return (
11
+
<LabelPrimitive.Root
12
+
data-slot="label"
13
+
className={cn(
14
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
15
+
className
16
+
)}
17
+
{...props}
18
+
/>
19
+
)
20
+
}
21
+
22
+
export { Label }
+29
src/components/ui/switch.tsx
+29
src/components/ui/switch.tsx
···
1
+
import * as React from "react"
2
+
import * as SwitchPrimitive from "@radix-ui/react-switch"
3
+
4
+
import { cn } from "@/lib/utils"
5
+
6
+
function Switch({
7
+
className,
8
+
...props
9
+
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
10
+
return (
11
+
<SwitchPrimitive.Root
12
+
data-slot="switch"
13
+
className={cn(
14
+
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
15
+
className
16
+
)}
17
+
{...props}
18
+
>
19
+
<SwitchPrimitive.Thumb
20
+
data-slot="switch-thumb"
21
+
className={cn(
22
+
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
23
+
)}
24
+
/>
25
+
</SwitchPrimitive.Root>
26
+
)
27
+
}
28
+
29
+
export { Switch }
+92
src/lib/autoBackup.ts
+92
src/lib/autoBackup.ts
···
1
+
import { Agent } from "@atproto/api";
2
+
import { BackupManager } from "./backup";
3
+
import { settingsManager } from "./settings";
4
+
5
+
export class AutoBackupScheduler {
6
+
private agent: Agent;
7
+
private intervalId: NodeJS.Timeout | null = null;
8
+
private isRunning = false;
9
+
10
+
constructor(agent: Agent) {
11
+
this.agent = agent;
12
+
}
13
+
14
+
start(): void {
15
+
if (this.isRunning) return;
16
+
17
+
this.isRunning = true;
18
+
// Check every hour if a backup is needed
19
+
this.intervalId = setInterval(() => {
20
+
this.checkAndPerformBackup();
21
+
}, 60 * 60 * 1000); // 1 hour
22
+
23
+
// Also check immediately when starting
24
+
this.checkAndPerformBackup();
25
+
}
26
+
27
+
stop(): void {
28
+
if (this.intervalId) {
29
+
clearInterval(this.intervalId);
30
+
this.intervalId = null;
31
+
}
32
+
this.isRunning = false;
33
+
}
34
+
35
+
private async checkAndPerformBackup(): Promise<void> {
36
+
try {
37
+
const shouldBackup = await this.shouldPerformBackup();
38
+
39
+
if (shouldBackup) {
40
+
console.log("Automatic backup due, starting backup...");
41
+
await this.performBackup();
42
+
}
43
+
} catch (error) {
44
+
console.error("Error in automatic backup check:", error);
45
+
}
46
+
}
47
+
48
+
private async shouldPerformBackup(): Promise<boolean> {
49
+
try {
50
+
const lastBackupDate = await settingsManager.getLastBackupDate();
51
+
const frequency = await settingsManager.getBackupFrequency();
52
+
53
+
if (!lastBackupDate) {
54
+
// No previous backup, so we should do one
55
+
return true;
56
+
}
57
+
58
+
const lastBackup = new Date(lastBackupDate);
59
+
const now = new Date();
60
+
const timeDiff = now.getTime() - lastBackup.getTime();
61
+
62
+
if (frequency === "daily") {
63
+
// Check if 24 hours have passed
64
+
const oneDay = 24 * 60 * 60 * 1000;
65
+
return timeDiff >= oneDay;
66
+
} else if (frequency === "weekly") {
67
+
// Check if 7 days have passed
68
+
const oneWeek = 7 * 24 * 60 * 60 * 1000;
69
+
return timeDiff >= oneWeek;
70
+
}
71
+
72
+
return false;
73
+
} catch (error) {
74
+
console.error("Error checking if backup is due:", error);
75
+
return false;
76
+
}
77
+
}
78
+
79
+
private async performBackup(): Promise<void> {
80
+
try {
81
+
const manager = new BackupManager(this.agent);
82
+
await manager.startBackup();
83
+
84
+
// Update the last backup date
85
+
await settingsManager.setLastBackupDate(new Date().toISOString());
86
+
87
+
console.log("Automatic backup completed successfully");
88
+
} catch (error) {
89
+
console.error("Automatic backup failed:", error);
90
+
}
91
+
}
92
+
}
+64
src/lib/settings.ts
+64
src/lib/settings.ts
···
1
+
import { Store } from "@tauri-apps/plugin-store";
2
+
3
+
export interface AppSettings {
4
+
backupFrequency: "daily" | "weekly";
5
+
lastBackupDate?: string; // ISO string
6
+
}
7
+
8
+
const DEFAULT_SETTINGS: AppSettings = {
9
+
backupFrequency: "daily",
10
+
};
11
+
12
+
class SettingsManager {
13
+
private store: Store | null = null;
14
+
15
+
private async getStore(): Promise<Store> {
16
+
if (!this.store) {
17
+
this.store = await Store.load("settings.json", { autoSave: true });
18
+
}
19
+
return this.store;
20
+
}
21
+
22
+
async getSettings(): Promise<AppSettings> {
23
+
try {
24
+
const store = await this.getStore();
25
+
const settings = (await store.get("settings")) as AppSettings | null;
26
+
return settings || DEFAULT_SETTINGS;
27
+
} catch (error) {
28
+
console.error("Failed to load settings:", error);
29
+
return DEFAULT_SETTINGS;
30
+
}
31
+
}
32
+
33
+
async updateSettings(settings: Partial<AppSettings>): Promise<void> {
34
+
try {
35
+
const store = await this.getStore();
36
+
const currentSettings = await this.getSettings();
37
+
const newSettings = { ...currentSettings, ...settings };
38
+
await store.set("settings", newSettings);
39
+
} catch (error) {
40
+
console.error("Failed to save settings:", error);
41
+
throw error;
42
+
}
43
+
}
44
+
45
+
async updateBackupFrequency(frequency: "daily" | "weekly"): Promise<void> {
46
+
await this.updateSettings({ backupFrequency: frequency });
47
+
}
48
+
49
+
async setLastBackupDate(date: string): Promise<void> {
50
+
await this.updateSettings({ lastBackupDate: date });
51
+
}
52
+
53
+
async getLastBackupDate(): Promise<string | undefined> {
54
+
const settings = await this.getSettings();
55
+
return settings.lastBackupDate;
56
+
}
57
+
58
+
async getBackupFrequency(): Promise<"daily" | "weekly"> {
59
+
const settings = await this.getSettings();
60
+
return settings.backupFrequency;
61
+
}
62
+
}
63
+
64
+
export const settingsManager = new SettingsManager();
+1
-1
src/lib/stats.ts
+1
-1
src/lib/stats.ts
+17
-14
src/routes/Home.tsx
+17
-14
src/routes/Home.tsx
···
5
5
DropdownMenu,
6
6
DropdownMenuContent,
7
7
DropdownMenuItem,
8
-
DropdownMenuLabel,
9
-
DropdownMenuSeparator,
10
8
DropdownMenuTrigger,
11
9
} from "@/components/ui/dropdown-menu";
12
10
import { openPath } from "@tauri-apps/plugin-opener";
13
-
import { appDataDir, documentDir } from "@tauri-apps/api/path";
14
11
import { createBackupDir, getBackupDir } from "@/lib/paths";
15
12
import { useEffect, useState, useRef } from "react";
16
13
import {
17
14
History,
18
15
LoaderCircleIcon,
19
16
ChevronDown,
20
-
ChevronUp,
21
17
FolderOpen,
22
-
Download,
23
18
Database,
24
19
FileText,
25
20
HardDrive,
26
-
Calendar,
27
21
Package,
28
-
Zap,
29
22
Heart,
30
23
Users,
31
24
User,
25
+
Settings as SettingsIcon,
32
26
} from "lucide-react";
33
27
import { BackupManager, Metadata } from "@/lib/backup";
34
28
import { useAuth } from "@/Auth";
35
29
import { toast } from "sonner";
30
+
import { settingsManager } from "@/lib/settings";
36
31
import { Badge } from "@/components/ui/badge";
37
-
import {
38
-
Card,
39
-
CardContent,
40
-
CardDescription,
41
-
CardHeader,
42
-
CardTitle,
43
-
} from "@/components/ui/card";
44
32
import { Progress } from "@/components/ui/progress";
33
+
import Settings from "./Settings";
45
34
46
35
export function Home({
47
36
profile,
···
52
41
}) {
53
42
const [isDirLoading, setDirLoading] = useState(false);
54
43
const [refreshTrigger, setRefreshTrigger] = useState(0);
44
+
const [showSettings, setShowSettings] = useState(false);
55
45
56
46
const handleBackupComplete = () => {
57
47
setRefreshTrigger((prev) => prev + 1);
58
48
};
49
+
50
+
if (showSettings) {
51
+
return <Settings onBack={() => setShowSettings(false)} />;
52
+
}
59
53
60
54
return (
61
55
<div className="p-4 mt-10">
···
86
80
</DropdownMenuContent>
87
81
</DropdownMenu>
88
82
</div>
83
+
<Button
84
+
variant="ghost"
85
+
size="sm"
86
+
onClick={() => setShowSettings(true)}
87
+
className="text-white/80 hover:text-white"
88
+
>
89
+
<SettingsIcon className="w-4 h-4" />
90
+
</Button>
89
91
</div>
90
92
91
93
<div className="bg-card rounded-lg p-4 mb-4">
···
140
142
}
141
143
const manager = new BackupManager(agent!!);
142
144
await manager.startBackup();
145
+
await settingsManager.setLastBackupDate(new Date().toISOString());
143
146
toast("Backup complete!");
144
147
onBackupComplete(); // Trigger refresh
145
148
} catch (err: any) {
+2
-10
src/routes/Login.tsx
+2
-10
src/routes/Login.tsx
···
2
2
import { Input } from "@/components/ui/input";
3
3
import { Button } from "@/components/ui/button";
4
4
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5
-
import {
6
-
AtpSessionEvent,
7
-
Agent,
8
-
CredentialSession,
9
-
AtpSessionData,
10
-
} from "@atproto/api";
11
5
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
12
6
import { openUrl } from "@tauri-apps/plugin-opener";
13
7
import { OAuthClient, type OAuthSession } from "@atproto/oauth-client";
14
8
import { SquareArrowOutUpRight } from "lucide-react";
15
-
16
-
type LoginMethod = "credential" | "oauth";
9
+
import { enable } from "@tauri-apps/plugin-autostart";
17
10
18
11
interface LoginPageProps {
19
12
onLogin: (session: OAuthSession) => void;
···
25
18
client: oauthClient,
26
19
}: LoginPageProps) {
27
20
const [identifier, setIdentifier] = useState("");
28
-
const [password, setPassword] = useState("");
29
21
const [loading, setLoading] = useState(false);
30
22
const [error, setError] = useState("");
31
-
const [loginMethod, setLoginMethod] = useState<LoginMethod>("credential");
32
23
const processingOAuthRef = useRef(false);
33
24
34
25
// Initialize OAuth client
···
57
48
// Process the OAuth callback with the URLSearchParams directly
58
49
const session = await oauthClient.callback(url.searchParams);
59
50
console.log("OAuth callback successful!", session);
51
+
enable();
60
52
onLogin(session.session);
61
53
setLoading(false);
62
54
} catch (err) {
+131
src/routes/Settings.tsx
+131
src/routes/Settings.tsx
···
1
+
import { useEffect, useState } from "react";
2
+
import { Button } from "@/components/ui/button";
3
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4
+
import { ArrowLeft, Zap, Calendar, ChevronDown } from "lucide-react";
5
+
import { Switch } from "@/components/ui/switch";
6
+
import { Label } from "@/components/ui/label";
7
+
import {
8
+
DropdownMenu,
9
+
DropdownMenuContent,
10
+
DropdownMenuLabel,
11
+
DropdownMenuRadioGroup,
12
+
DropdownMenuRadioItem,
13
+
DropdownMenuSeparator,
14
+
DropdownMenuTrigger,
15
+
} from "@/components/ui/dropdown-menu";
16
+
import { enable, isEnabled, disable } from "@tauri-apps/plugin-autostart";
17
+
import { settingsManager } from "@/lib/settings";
18
+
19
+
interface SettingsProps {
20
+
onBack: () => void;
21
+
}
22
+
23
+
export default function Settings({ onBack }: SettingsProps) {
24
+
const [autostart, setAutostart] = useState(false);
25
+
const [backupFrequency, setBackupFrequency] = useState<"daily" | "weekly">(
26
+
"daily"
27
+
);
28
+
29
+
useEffect(() => {
30
+
(async () => {
31
+
setAutostart(await isEnabled());
32
+
// Load saved backup frequency
33
+
const frequency = await settingsManager.getBackupFrequency();
34
+
setBackupFrequency(frequency);
35
+
})();
36
+
}, []);
37
+
38
+
const handleBackupFrequencyChange = async (frequency: "daily" | "weekly") => {
39
+
setBackupFrequency(frequency);
40
+
await settingsManager.updateBackupFrequency(frequency);
41
+
};
42
+
43
+
return (
44
+
<div className="p-4 mt-10">
45
+
<div className="flex items-center gap-2 mb-4">
46
+
<Button
47
+
variant="ghost"
48
+
size="sm"
49
+
onClick={onBack}
50
+
className="text-white/80 hover:text-white"
51
+
>
52
+
<ArrowLeft className="w-4 h-4 mr-2" />
53
+
Back
54
+
</Button>
55
+
</div>
56
+
57
+
<div className="space-y-4">
58
+
{/* Autostart Setting */}
59
+
<Card className="bg-card border-white/10">
60
+
<CardHeader>
61
+
<CardTitle className="text-white flex items-center gap-2">
62
+
<Zap className="w-5 h-5" />
63
+
App Settings
64
+
</CardTitle>
65
+
</CardHeader>
66
+
<CardContent className="space-y-4">
67
+
<div className="flex items-center justify-between p-3 rounded-lg bg-white/5">
68
+
<Label htmlFor="autostart" className="cursor-pointer">
69
+
Autostart the app
70
+
</Label>
71
+
<Switch
72
+
id="autostart"
73
+
checked={autostart}
74
+
onCheckedChange={async (checked) => {
75
+
if (checked) enable();
76
+
else disable();
77
+
setAutostart(await isEnabled());
78
+
}}
79
+
className="data-[state=checked]:bg-primary data-[state=checked]:border-primary cursor-pointer"
80
+
/>
81
+
</div>
82
+
</CardContent>
83
+
</Card>
84
+
85
+
{/* Backup Settings */}
86
+
<Card className="bg-card border-white/10">
87
+
<CardHeader>
88
+
<CardTitle className="text-white flex items-center gap-2">
89
+
<Calendar className="w-5 h-5" />
90
+
Backup Settings
91
+
</CardTitle>
92
+
</CardHeader>
93
+
<CardContent className="space-y-4">
94
+
<div>
95
+
<p className="text-white font-medium mb-3">Backup Frequency</p>
96
+
<div className="space-y-2">
97
+
<DropdownMenu>
98
+
<DropdownMenuTrigger asChild>
99
+
<Button
100
+
variant="outline"
101
+
className="capitalize cursor-pointer"
102
+
>
103
+
{backupFrequency} <ChevronDown />
104
+
</Button>
105
+
</DropdownMenuTrigger>
106
+
<DropdownMenuContent className="w-56 ml-7">
107
+
<DropdownMenuLabel>Backup Frequency</DropdownMenuLabel>
108
+
<DropdownMenuSeparator />
109
+
<DropdownMenuRadioGroup
110
+
value={backupFrequency}
111
+
onValueChange={(val) =>
112
+
handleBackupFrequencyChange(val as "daily" | "weekly")
113
+
}
114
+
>
115
+
<DropdownMenuRadioItem value="daily">
116
+
Daily
117
+
</DropdownMenuRadioItem>
118
+
<DropdownMenuRadioItem value="weekly">
119
+
Weekly
120
+
</DropdownMenuRadioItem>
121
+
</DropdownMenuRadioGroup>
122
+
</DropdownMenuContent>
123
+
</DropdownMenu>
124
+
</div>
125
+
</div>
126
+
</CardContent>
127
+
</Card>
128
+
</div>
129
+
</div>
130
+
);
131
+
}