One-click backups for AT Protocol

feat: add updater and settings etc

Turtlepaw 00f1a51a fe28dcb6

+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
··· 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
··· 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
··· 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
··· 31 31 32 32 [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] 33 33 tauri-plugin-autostart = "2" 34 + tauri-plugin-updater = "2"
+4 -1
src-tauri/capabilities/default.json
··· 47 47 ] 48 48 }, 49 49 "fs:scope-document-recursive", 50 - "fs:allow-document-write-recursive" 50 + "fs:allow-document-write-recursive", 51 + "autostart:allow-enable", 52 + "autostart:allow-disable", 53 + "autostart:allow-is-enabled" 51 54 ] 52 55 }
+2 -1
src-tauri/capabilities/desktop.json
··· 9 9 "main" 10 10 ], 11 11 "permissions": [ 12 - "autostart:default" 12 + "autostart:default", 13 + "updater:default" 13 14 ] 14 15 }
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 - import { CarReader, RepoReader } from "@atcute/car/v4"; 1 + import { RepoReader } from "@atcute/car/v4"; 2 2 3 3 export interface CarStats { 4 4 totalBlocks: number;
+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 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
··· 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 + }