+5
bun.lock
+5
bun.lock
···
16
16
"@radix-ui/react-scroll-area": "^1.2.9",
17
17
"@radix-ui/react-slot": "^1.2.3",
18
18
"@radix-ui/react-switch": "^1.2.5",
19
+
"@radix-ui/react-tooltip": "^1.2.7",
19
20
"@tailwindcss/vite": "^4.1.11",
20
21
"@tauri-apps/api": "^2",
21
22
"@tauri-apps/plugin-autostart": "~2",
···
460
461
461
462
"@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=="],
462
463
464
+
"@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.7", "", { "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-dismissable-layer": "1.1.10", "@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-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "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-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw=="],
465
+
463
466
"@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=="],
464
467
465
468
"@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=="],
···
477
480
"@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=="],
478
481
479
482
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@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-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
483
+
484
+
"@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "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-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="],
480
485
481
486
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
482
487
+4
-2
package.json
+4
-2
package.json
···
22
22
"@radix-ui/react-scroll-area": "^1.2.9",
23
23
"@radix-ui/react-slot": "^1.2.3",
24
24
"@radix-ui/react-switch": "^1.2.5",
25
+
"@radix-ui/react-tooltip": "^1.2.7",
25
26
"@tailwindcss/vite": "^4.1.11",
26
27
"@tauri-apps/api": "^2",
27
28
"@tauri-apps/plugin-autostart": "~2",
···
60
61
},
61
62
"trustedDependencies": [
62
63
"@tailwindcss/oxide"
63
-
]
64
-
}
64
+
],
65
+
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
66
+
}
+3
-4
src-tauri/Cargo.toml
+3
-4
src-tauri/Cargo.toml
···
16
16
17
17
[build-dependencies]
18
18
tauri-build = { version = "2", features = [] }
19
-
#tauri = { version = "2.0.0", features = [ "tray-icon", "api-all", "devtools" ] }
19
+
#tauri = { version = "2.0.0", features = [ "tray-i+con", "api-all", "devtools" ] }
20
20
21
21
[dependencies]
22
22
tauri = { version = "2", features = ["tray-icon", "devtools"] }
···
31
31
tokio = { version = "1", features = ["full"] }
32
32
chrono = { version = "0.4", features = ["serde"] }
33
33
tauri-plugin-websocket = "2"
34
-
35
-
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
36
-
tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
34
+
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
37
35
38
36
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
39
37
tauri-plugin-autostart = "2"
38
+
tauri-plugin-single-instance = "2"
40
39
tauri-plugin-updater = "2"
41
40
42
41
[profile.release]
+1
-1
src-tauri/src/background.rs
+1
-1
src-tauri/src/background.rs
+48
-48
src-tauri/src/lib.rs
+48
-48
src-tauri/src/lib.rs
···
17
17
18
18
#[cfg_attr(mobile, tauri::mobile_entry_point)]
19
19
pub fn run() {
20
-
let mut builder = tauri::Builder::default()
20
+
let builder = tauri::Builder::default()
21
21
.plugin(tauri_plugin_websocket::init())
22
22
.plugin(tauri_plugin_shell::init())
23
23
.plugin(tauri_plugin_process::init())
···
30
30
.plugin(tauri_plugin_store::Builder::new().build())
31
31
.plugin(tauri_plugin_deep_link::init())
32
32
.plugin(tauri_plugin_opener::init())
33
+
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
34
+
println!("A new app instance was opened with {argv:?} and the deep link event was already triggered.");
35
+
36
+
let _ = app.get_webview_window("main")
37
+
.expect("no main window")
38
+
.set_focus();
39
+
}))
33
40
.invoke_handler(tauri::generate_handler![
34
41
greet,
35
42
start_background_scheduler,
36
43
stop_background_scheduler
37
44
])
38
-
.on_menu_event(|app, event| match event.id.as_ref() {
39
-
"quit" => {
40
-
std::process::exit(0);
41
-
}
42
-
"show" => {
43
-
let window = app.get_webview_window("main").unwrap();
44
-
window.show().unwrap();
45
-
window.set_focus().unwrap();
46
-
}
47
-
"hide" => {
48
-
let window = app.get_webview_window("main").unwrap();
49
-
window.hide().unwrap();
50
-
}
51
-
"backup_now" => {
52
-
// Emit event to trigger backup
53
-
app.emit("perform-backup", ()).unwrap();
54
-
}
55
-
_ => {
56
-
println!("menu item {:?} not handled", event.id);
57
-
}
58
-
})
59
-
.on_tray_icon_event(|tray, event| match event {
60
-
TrayIconEvent::Click {
61
-
button: MouseButton::Left,
62
-
button_state: MouseButtonState::Up,
63
-
..
64
-
} => {
65
-
println!("left click pressed and released");
66
-
// in this example, let's show and focus the main window when the tray is clicked
67
-
let app = tray.app_handle();
68
-
if let Some(window) = app.get_webview_window("main") {
69
-
let _ = window.show();
70
-
let _ = window.set_focus();
71
-
}
72
-
}
73
-
_ => {
74
-
println!("unhandled event {event:?}");
75
-
}
76
-
})
45
+
// .on_menu_event(|app, event| match event.id.as_ref() {
46
+
// "quit" => {
47
+
// std::process::exit(0);
48
+
// }
49
+
// "show" => {
50
+
// let window = app.get_webview_window("main").unwrap();
51
+
// window.show().unwrap();
52
+
// window.set_focus().unwrap();
53
+
// }
54
+
// "hide" => {
55
+
// let window = app.get_webview_window("main").unwrap();
56
+
// window.hide().unwrap();
57
+
// }
58
+
// "backup_now" => {
59
+
// // Emit event to trigger backup
60
+
// app.emit("perform-backup", ()).unwrap();
61
+
// }
62
+
// _ => {
63
+
// println!("menu item {:?} not handled", event.id);
64
+
// }
65
+
// })
66
+
// .on_tray_icon_event(|tray, event| match event {
67
+
// TrayIconEvent::Click {
68
+
// button: MouseButton::Left,
69
+
// button_state: MouseButtonState::Up,
70
+
// ..
71
+
// } => {
72
+
// println!("left click pressed and released");
73
+
// // in this example, let's show and focus the main window when the tray is clicked
74
+
// let app = tray.app_handle();
75
+
// if let Some(window) = app.get_webview_window("main") {
76
+
// let _ = window.show();
77
+
// let _ = window.set_focus();
78
+
// }
79
+
// }
80
+
// _ => {
81
+
// println!("unhandled event {event:?}");
82
+
// }
83
+
// })
77
84
.setup(|app| {
78
85
#[cfg(any(windows, target_os = "linux"))]
79
86
{
80
87
app.deep_link().register_all()?;
81
88
}
82
-
let tray = create_system_tray(app);
89
+
//let tray = create_system_tray(app);
83
90
84
91
Ok(())
85
92
});
86
-
87
-
#[cfg(desktop)]
88
-
{
89
-
builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
90
-
println!("A new app instance was opened with {argv:?} and the deep link event was already triggered.");
91
-
}));
92
-
}
93
93
94
94
builder
95
95
.run(tauri::generate_context!())
+61
src/components/ui/tooltip.tsx
+61
src/components/ui/tooltip.tsx
···
1
+
"use client";
2
+
3
+
import * as React from "react";
4
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5
+
6
+
import { cn } from "@/lib/utils";
7
+
8
+
function TooltipProvider({
9
+
delayDuration = 0,
10
+
...props
11
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+
return (
13
+
<TooltipPrimitive.Provider
14
+
data-slot="tooltip-provider"
15
+
delayDuration={delayDuration}
16
+
{...props}
17
+
/>
18
+
);
19
+
}
20
+
21
+
function Tooltip({
22
+
...props
23
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+
return (
25
+
<TooltipProvider>
26
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
27
+
</TooltipProvider>
28
+
);
29
+
}
30
+
31
+
function TooltipTrigger({
32
+
...props
33
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
35
+
}
36
+
37
+
function TooltipContent({
38
+
className,
39
+
sideOffset = 0,
40
+
children,
41
+
...props
42
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
43
+
return (
44
+
<TooltipPrimitive.Portal>
45
+
<TooltipPrimitive.Content
46
+
data-slot="tooltip-content"
47
+
sideOffset={sideOffset}
48
+
className={cn(
49
+
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
50
+
className
51
+
)}
52
+
{...props}
53
+
>
54
+
{children}
55
+
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
56
+
</TooltipPrimitive.Content>
57
+
</TooltipPrimitive.Portal>
58
+
);
59
+
}
60
+
61
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+2
-2
src/lib/stats.ts
+2
-2
src/lib/stats.ts
···
13
13
export async function getCarStats(carData: Uint8Array): Promise<CarStats> {
14
14
try {
15
15
// Parse the CAR file
16
-
await using repo = RepoReader.fromUint8Array(carData);
16
+
const repo = RepoReader.fromUint8Array(carData);
17
17
18
18
let totalBlocks = 0;
19
19
let totalSize = 0;
···
29
29
// Try to decode the block as a record
30
30
if (record) {
31
31
// Count different record types
32
-
const type = (record.record as any)["$type"]
32
+
const type = (record.record as any)["$type"];
33
33
if (type) {
34
34
recordTypes[type] = (recordTypes[type] || 0) + 1;
35
35
recordCount++;
+10
src/routes/Home.tsx
+10
src/routes/Home.tsx
···
22
22
Heart,
23
23
History,
24
24
Images,
25
+
Info,
25
26
LoaderCircleIcon,
26
27
Package,
27
28
Settings as SettingsIcon,
···
31
32
import { useEffect, useRef, useState } from "react";
32
33
import { toast } from "sonner";
33
34
import Settings from "./Settings";
35
+
import {
36
+
Tooltip,
37
+
TooltipContent,
38
+
TooltipTrigger,
39
+
} from "@/components/ui/tooltip.tsx";
34
40
35
41
export function Home({
36
42
profile,
···
288
294
<div className="flex items-center gap-2 mb-4">
289
295
<History className="w-5 h-5" />
290
296
<p className="text-white text-lg font-semibold">Previous backups</p>
297
+
298
+
<p className="text-white/60">
299
+
(only the 3 most recent backups are saved)
300
+
</p>
291
301
</div>
292
302
293
303
{backups.length === 0 ? (