Added deeplinks, added qr code login, added settings, other stuff
This commit is contained in:
parent
462fc19591
commit
8ff8bd9528
32 changed files with 917 additions and 78 deletions
7
.vscode/extensions.json
vendored
7
.vscode/extensions.json
vendored
|
|
@ -1,3 +1,8 @@
|
|||
{
|
||||
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||
"recommendations": [
|
||||
"tauri-apps.tauri-vscode",
|
||||
"rust-lang.rust-analyzer",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"antfu.vite"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
"type": "module",
|
||||
"exports": {
|
||||
"./context": "./src/context.tsx",
|
||||
"./controls": "./src/windowControls.tsx"
|
||||
"./controls": "./src/windowControls.tsx",
|
||||
"./deeplinkHandler": "./src/deeplinkHandler.tsx",
|
||||
"./qrCodeScanner": "./src/qrCodeScanner.tsx"
|
||||
},
|
||||
"scripts": {
|
||||
"dev:mobile:raw": "tauri android dev",
|
||||
|
|
@ -22,8 +24,11 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-barcode-scanner": "~2",
|
||||
"@tauri-apps/plugin-deep-link": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tensamin/ui": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
|
|
|
|||
122
apps/tauri/src-tauri/Cargo.lock
generated
122
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -77,7 +77,7 @@ version = "1.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -88,7 +88,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -625,6 +625,26 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
|
|
@ -733,6 +753,12 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
|
|
@ -954,6 +980,15 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
|
|
@ -1653,6 +1688,12 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
|
|
@ -2700,6 +2741,16 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
|
|
@ -3389,6 +3440,16 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
|
|
@ -4235,6 +4296,41 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-barcode-scanner"
|
||||
version = "2.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "485cbcf227f04117e930be748ea71d835900466dcd1d455d5ec284d36107a305"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils 2.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.3"
|
||||
|
|
@ -4459,6 +4555,8 @@ dependencies = [
|
|||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build 2.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tauri-plugin-barcode-scanner",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-opener",
|
||||
]
|
||||
|
||||
|
|
@ -4533,6 +4631,15 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
|
|
@ -5426,6 +5533,17 @@ dependencies = [
|
|||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ tauri-build = { version = "2", features = [] }
|
|||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-deep-link = "2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
||||
version = "2"
|
||||
|
|
@ -42,6 +43,9 @@ version = "2"
|
|||
features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"]
|
||||
default-features = false
|
||||
|
||||
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||
tauri-plugin-barcode-scanner = "2"
|
||||
|
||||
[patch.crates-io.tauri]
|
||||
git = "https://github.com/tauri-apps/tauri"
|
||||
branch = "feat/cef"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-minimize"
|
||||
"core:window:allow-minimize",
|
||||
"core:event:default",
|
||||
"deep-link:default"
|
||||
]
|
||||
}
|
||||
15
apps/tauri/src-tauri/capabilities/mobile.json
Normal file
15
apps/tauri/src-tauri/capabilities/mobile.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"identifier": "mobile-capability",
|
||||
"platforms": [
|
||||
"android",
|
||||
"iOS"
|
||||
],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"barcode-scanner:default",
|
||||
"barcode-scanner:allow-scan",
|
||||
"barcode-scanner:allow-cancel"
|
||||
]
|
||||
}
|
||||
|
|
@ -22,6 +22,21 @@
|
|||
<!-- AndroidTV support -->
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||
<intent-filter >
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- ChromeOS ARC++ uses a different action for deep links -->
|
||||
<action android:name="org.chromium.arc.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="tensamin" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</intent-filter>
|
||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
|
|
|
|||
|
|
@ -6,8 +6,22 @@ fn greet(name: &str) -> String {
|
|||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
let builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_opener::init());
|
||||
|
||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
||||
|
||||
builder
|
||||
.setup(|_app| {
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
{
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
_app.deep_link().register_all()?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
|
|
|||
|
|
@ -34,5 +34,18 @@
|
|||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["tensamin"]
|
||||
},
|
||||
"mobile": [
|
||||
{
|
||||
"scheme": ["tensamin"],
|
||||
"appLink": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
apps/tauri/src/deeplinkHandler.tsx
Normal file
71
apps/tauri/src/deeplinkHandler.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link";
|
||||
|
||||
type DeeplinkContextValue = {
|
||||
deeplinks: readonly string[];
|
||||
};
|
||||
|
||||
export const deeplinkContext = createContext<DeeplinkContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function useDeeplinks() {
|
||||
const value = useContext(deeplinkContext);
|
||||
if (!value) {
|
||||
throw new Error("useDeeplinks must be used within DeeplinkProvider");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export default function DeeplinkProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [deeplinks, setDeeplinks] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
let unlisten: (() => void) | undefined;
|
||||
|
||||
getCurrent().then((links) => {
|
||||
if (mounted && links) {
|
||||
setDeeplinks((prev) => [...prev, ...links]);
|
||||
}
|
||||
});
|
||||
|
||||
onOpenUrl((links) => {
|
||||
if (mounted) {
|
||||
setDeeplinks((prev) => [...prev, ...links]);
|
||||
}
|
||||
}).then((fn) => {
|
||||
unlisten = fn;
|
||||
});
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
unlisten?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(deeplinks);
|
||||
}, [deeplinks]);
|
||||
|
||||
return (
|
||||
<deeplinkContext.Provider
|
||||
value={{
|
||||
deeplinks,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</deeplinkContext.Provider>
|
||||
);
|
||||
}
|
||||
38
apps/tauri/src/qrCodeScanner.tsx
Normal file
38
apps/tauri/src/qrCodeScanner.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
scan,
|
||||
Format,
|
||||
requestPermissions,
|
||||
} from "@tauri-apps/plugin-barcode-scanner";
|
||||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
|
||||
import { toast } from "@tensamin/shared/log";
|
||||
|
||||
export default function QrCodeScanner({
|
||||
onData,
|
||||
}: {
|
||||
onData: (data: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
requestPermissions()
|
||||
.catch((err) => {
|
||||
toast("error", err.message);
|
||||
})
|
||||
.then(() =>
|
||||
scan({ windowed: false, formats: [Format.QRCode] })
|
||||
.catch((err) => {
|
||||
toast("error", err.message);
|
||||
})
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
onData(data.content);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}}
|
||||
>
|
||||
Open QR Code Scanner
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,21 +17,22 @@
|
|||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tanstack/react-router": "^1.0.0",
|
||||
"@tanstack/react-virtual": "^3.0.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tensamin/call": "workspace:*",
|
||||
"@tensamin/chat": "workspace:*",
|
||||
"@tensamin/crypto": "workspace:*",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/storage": "workspace:*",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"@tensamin/ttp": "workspace:*",
|
||||
"@tensamin/ui": "workspace:*",
|
||||
"@tensamin/user": "workspace:*",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"@tauri-apps/api": "^2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"comlink": "^4.4.2",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^0.564.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"shadcn": "^4.0.7",
|
||||
|
|
@ -44,10 +45,11 @@
|
|||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"eslint": "^10.0.3",
|
||||
"globals": "^17.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
import { ArrowLeft, House, Phone, User } from "lucide-react";
|
||||
import { ArrowLeft, House, Phone, Settings, User } from "lucide-react";
|
||||
import { useLocation, useNavigate, useSearch } from "@tanstack/react-router";
|
||||
import { useCall } from "@tensamin/call/context";
|
||||
import Wrapper from "@tensamin/user/wrapper";
|
||||
|
|
@ -22,6 +22,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
|||
const { contacts } = useTTP();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { id } = useSearch({ strict: false });
|
||||
|
||||
const currentCalls = id ? contacts?.[id]?.calls || [] : [];
|
||||
|
|
@ -43,6 +44,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
|||
<ArrowLeft className="size-4.5" />
|
||||
</SidebarTrigger>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => navigate({ to: "/" })}
|
||||
className="w-9 h-9 aspect-square rounded-lg"
|
||||
|
|
@ -50,6 +52,15 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
|||
>
|
||||
<House className="size-4.5" />
|
||||
</Button>
|
||||
<Button
|
||||
// @ts-expect-error TanStack router doesn't properly detect the settings route
|
||||
onClick={() => navigate({ to: "/settings" })}
|
||||
className="w-9 h-9 aspect-square rounded-lg"
|
||||
variant="outline"
|
||||
>
|
||||
<Settings className="size-4.5" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{pathname === "/chat" && (
|
||||
<Wrapper
|
||||
|
|
@ -126,22 +137,36 @@ export function MobileNavbar() {
|
|||
|
||||
return (
|
||||
<div className="z-51 w-full pb-[env(safe-area-inset-bottom)] bg-card border-t">
|
||||
<div className="z-51 w-full h-14 flex justify-center items-center gap-8">
|
||||
<div className="z-51 w-full h-15 grid grid-cols-3 items-center place-items-center px-5">
|
||||
<SidebarTrigger
|
||||
className="text-foreground w-12! h-12! aspect-square rounded-xl"
|
||||
className="text-foreground w-12! h-12! aspect-square rounded-xl flex flex-col gap-1"
|
||||
variant="link"
|
||||
>
|
||||
<User className="size-5.5" />
|
||||
<User className="size-4.5" />
|
||||
<p className="text-xs">Chats</p>
|
||||
</SidebarTrigger>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigate({ to: "/" });
|
||||
setOpenMobile(false);
|
||||
}}
|
||||
className="text-foreground w-12 h-12 aspect-square rounded-xl"
|
||||
className="text-foreground w-12 h-12 aspect-square rounded-xl flex flex-col gap-1"
|
||||
variant="link"
|
||||
>
|
||||
<House className="size-5.5" />
|
||||
<House className="size-4.5" />
|
||||
<p className="text-xs">Home</p>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// @ts-expect-error TanStack router doesn't properly detect the settings route
|
||||
navigate({ to: "/settings" });
|
||||
setOpenMobile(false);
|
||||
}}
|
||||
className="text-foreground w-12 h-12 aspect-square rounded-xl flex flex-col gap-1"
|
||||
variant="link"
|
||||
>
|
||||
<Settings className="size-4.5" />
|
||||
<p className="text-xs">Settings</p>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { log, toast } from "@tensamin/shared/log";
|
|||
import { File } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { z } from "zod";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import QrCodeScanner from "@tensamin/tauri/qrCodeScanner";
|
||||
|
||||
const fetchedUser = z.object({
|
||||
id: z.uuidv4(),
|
||||
|
|
@ -145,8 +147,38 @@ export default function Form() {
|
|||
[save],
|
||||
);
|
||||
|
||||
const isTauriEnv = isTauri();
|
||||
|
||||
return (
|
||||
<div className="flex md:flex-row flex-col gap-15">
|
||||
{isTauriEnv ? (
|
||||
<>
|
||||
<QrCodeScanner
|
||||
onData={(data) => {
|
||||
if (!data.startsWith("tensamin://tu::")) {
|
||||
toast("error", "Invalid QR code");
|
||||
return;
|
||||
} else {
|
||||
const decoded = data.replace("tensamin://tu::", "");
|
||||
|
||||
try {
|
||||
const { userId, privateKey } = parseTuFileContent(decoded);
|
||||
save("session_id", Date.now());
|
||||
save("user_id", userId);
|
||||
save("private_key", privateKey);
|
||||
location.href = "/";
|
||||
} catch (error) {
|
||||
log(0, "login", "red", error);
|
||||
toast("error", "Failed to parse QR code data");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button onClick={() => uploadRef.current?.click()}>
|
||||
Select .tu file
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
onClick={() => uploadRef.current?.click()}
|
||||
className="flex flex-col gap-3 cursor-pointer w-55 aspect-square bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-3 items-center justify-center rounded-lg"
|
||||
|
|
@ -154,6 +186,7 @@ export default function Form() {
|
|||
<File className="text-foreground" size={27} />
|
||||
<p className="text-md">Select .tu file</p>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
accept=".tu"
|
||||
onChange={handleFileInputChange}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import {
|
|||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { useIsMobile } from "@tensamin/ui/utils";
|
||||
import { MobileNavbar } from "./navbar";
|
||||
import { useLocation } from "@tanstack/react-router";
|
||||
|
||||
import SidebarBox from "@tensamin/call/sidebarBox";
|
||||
import { useShowMobileNavbar } from "@/routes/app/layout";
|
||||
|
||||
/**
|
||||
* Renders the conversation sidebar with account summary and conversation list.
|
||||
|
|
@ -20,7 +20,7 @@ import SidebarBox from "@tensamin/call/sidebarBox";
|
|||
*/
|
||||
export default function Sidebar() {
|
||||
const isMobile = useIsMobile();
|
||||
const location = useLocation();
|
||||
const showMobileNavbar = useShowMobileNavbar();
|
||||
|
||||
return (
|
||||
<SidebarRoot>
|
||||
|
|
@ -42,7 +42,7 @@ export default function Sidebar() {
|
|||
</div>
|
||||
</div>
|
||||
</SidebarContent>
|
||||
{isMobile && location.pathname === "/chat" && (
|
||||
{isMobile && !showMobileNavbar && (
|
||||
<SidebarFooter className="p-0!">
|
||||
<MobileNavbar />
|
||||
</SidebarFooter>
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export default function Screen(props: { children: React.ReactNode }) {
|
|||
|
||||
return (
|
||||
<CreateScreen>
|
||||
<div className="h-full flex flex-col gap-15 p-10 md:p-40 w-full lg:w-2/3">
|
||||
<div className="h-full flex flex-col gap-15 p-10 py-20 md:p-40 w-full lg:w-2/3">
|
||||
<h1 className="text-3xl md:text-4xl font-bold">
|
||||
Privacy Policy & ToS
|
||||
<p className="text-muted-foreground text-[20px] font-normal pt-3">
|
||||
|
|
|
|||
35
apps/web/src/features/settings/components.tsx
Normal file
35
apps/web/src/features/settings/components.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Label } from "@tensamin/ui/cmp/label";
|
||||
import { Switch as UISwitch } from "@tensamin/ui/cmp/switch";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { settingsStorageDefaults } from "@tensamin/shared/settings";
|
||||
import { useStorage } from "@tensamin/storage/context";
|
||||
|
||||
export function Switch({
|
||||
label,
|
||||
id,
|
||||
}: {
|
||||
label: string;
|
||||
id: keyof typeof settingsStorageDefaults;
|
||||
}) {
|
||||
const { save, load } = useStorage();
|
||||
const [value, setValue] = useState<boolean>(settingsStorageDefaults[id]);
|
||||
|
||||
useEffect(() => {
|
||||
load(id).then((value) => setValue(value));
|
||||
}, [id, load]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<UISwitch
|
||||
id={id}
|
||||
checked={value}
|
||||
onCheckedChange={(value) => {
|
||||
setValue(value);
|
||||
save(id, value);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
apps/web/src/features/settings/layout.tsx
Normal file
64
apps/web/src/features/settings/layout.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
|
||||
import options from "@tensamin/shared/settings";
|
||||
import { cn, useIsMobile } from "@tensamin/ui/utils";
|
||||
import { Outlet, useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export default function Screen() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full">
|
||||
{!isMobile && <SettingsSidebar />}
|
||||
|
||||
{/* Page */}
|
||||
<div className="bg-background w-full h-full p-3">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SettingsSidebar() {
|
||||
const settingsOptions = options as Record<
|
||||
string,
|
||||
Record<string, Record<string, unknown>>
|
||||
>;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
isMobile ? "w-full" : "rounded-tl-2xl border-r bg-input/15 w-50",
|
||||
"flex flex-col gap-6 p-3",
|
||||
)}
|
||||
>
|
||||
{/* Settings */}
|
||||
{Object.keys(settingsOptions).map((category) => (
|
||||
// Category
|
||||
<div key={category} className="flex flex-col gap-1">
|
||||
<h2 className="font-bold text-xs uppercase">{category}</h2>
|
||||
{Object.keys(settingsOptions[category]).map((page) => (
|
||||
// Page
|
||||
<div key={page}>
|
||||
<Button
|
||||
className="w-full"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: "/settings/" + page.toLowerCase(),
|
||||
})
|
||||
}
|
||||
>
|
||||
{(page as string).charAt(0).toUpperCase() +
|
||||
(page as string).slice(1)}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -10,8 +10,9 @@ import "./index.css";
|
|||
|
||||
import NotFound from "@/routes/404";
|
||||
|
||||
import RootLayout from "./routes/layout";
|
||||
import AppLayout from "./routes/app/layout";
|
||||
import RootLayout from "@/routes/layout";
|
||||
import AppLayout from "@/routes/app/layout";
|
||||
import SettingsLayout from "@/features/settings/layout";
|
||||
|
||||
import Home from "@/routes/app/home";
|
||||
import ChatScreen from "@tensamin/chat/screen";
|
||||
|
|
@ -22,6 +23,7 @@ import ChatContext from "@tensamin/chat/context";
|
|||
import CallContext from "@tensamin/call/context";
|
||||
import SocketContext from "@tensamin/ttp/context";
|
||||
import UserContext from "@tensamin/user/context";
|
||||
import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
|
||||
|
||||
import { ThemeProvider } from "@tensamin/ui/theme";
|
||||
import z from "zod";
|
||||
|
|
@ -47,6 +49,7 @@ window.setLogLevelToMax = () => {
|
|||
|
||||
function RootShell() {
|
||||
return (
|
||||
<DeeplinkContext>
|
||||
<ThemeProvider>
|
||||
<div className="w-screen h-screen overflow-hidden">
|
||||
<RootLayout>
|
||||
|
|
@ -54,6 +57,7 @@ function RootShell() {
|
|||
</RootLayout>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</DeeplinkContext>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -89,10 +93,55 @@ const appRoute = createRoute({
|
|||
component: AppShell,
|
||||
});
|
||||
|
||||
const settingsRoute = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "settings",
|
||||
component: SettingsLayout,
|
||||
staticData: {
|
||||
showMobileNavbar: true,
|
||||
},
|
||||
});
|
||||
|
||||
type SettingsRouteModule = {
|
||||
default?: () => React.JSX.Element;
|
||||
component?: () => React.JSX.Element;
|
||||
};
|
||||
|
||||
const settingsRouteModules = import.meta.glob<SettingsRouteModule>(
|
||||
"./routes/settings/*.tsx",
|
||||
{ eager: true },
|
||||
);
|
||||
|
||||
const settingsChildren = Object.entries(settingsRouteModules).map(
|
||||
([filePath, module]) => {
|
||||
const fileName = filePath.split("/").pop()?.replace(".tsx", "") ?? "";
|
||||
const path = fileName === "index" ? "/" : fileName;
|
||||
const component = module.component ?? module.default;
|
||||
|
||||
if (!component) {
|
||||
throw new Error(
|
||||
`Settings route module "${filePath}" must export a default component`,
|
||||
);
|
||||
}
|
||||
|
||||
return createRoute({
|
||||
getParentRoute: () => settingsRoute,
|
||||
path,
|
||||
component,
|
||||
staticData: {
|
||||
showMobileNavbar: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const homeRoute = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/",
|
||||
component: Home,
|
||||
staticData: {
|
||||
showMobileNavbar: true,
|
||||
},
|
||||
});
|
||||
|
||||
const chatRoute = createRoute({
|
||||
|
|
@ -102,22 +151,36 @@ const chatRoute = createRoute({
|
|||
validateSearch: z.object({
|
||||
id: z.number().optional(),
|
||||
}),
|
||||
staticData: {
|
||||
showMobileNavbar: false,
|
||||
},
|
||||
});
|
||||
|
||||
const callRoute = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "call",
|
||||
component: CallScreen,
|
||||
staticData: {
|
||||
showMobileNavbar: false,
|
||||
},
|
||||
});
|
||||
|
||||
const loginRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "login",
|
||||
component: Login,
|
||||
staticData: {
|
||||
showMobileNavbar: false,
|
||||
},
|
||||
});
|
||||
|
||||
const routeTree = rootRoute.addChildren([
|
||||
appRoute.addChildren([homeRoute, chatRoute, callRoute]),
|
||||
appRoute.addChildren([
|
||||
homeRoute,
|
||||
chatRoute,
|
||||
callRoute,
|
||||
settingsRoute.addChildren(settingsChildren),
|
||||
]),
|
||||
loginRoute,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,31 +7,31 @@ import { SidebarProvider } from "@tensamin/ui/cmp/sidebar";
|
|||
import { useIsMobile, cn } from "@tensamin/ui/utils";
|
||||
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { useLocation } from "@tanstack/react-router";
|
||||
import { useLocation, useMatches } from "@tanstack/react-router";
|
||||
|
||||
/**
|
||||
* Executes Layout.
|
||||
* @param props Parameter props.
|
||||
* @returns unknown.
|
||||
*/
|
||||
export default function Layout(props: { children: ReactNode }) {
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
const isMobile = useIsMobile();
|
||||
const location = useLocation();
|
||||
const showMobileNavbar = useShowMobileNavbar();
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex bg-sidebar">
|
||||
<SidebarProvider>
|
||||
<Sidebar />
|
||||
<div
|
||||
// Background of ui that is overlapping with the system ui
|
||||
className={cn(
|
||||
"w-full h-full flex flex-col",
|
||||
isTauri() && isMobile && "pt-[env(safe-area-inset-top)]",
|
||||
isMobile && showMobileNavbar && "bg-background",
|
||||
)}
|
||||
>
|
||||
{!isMobile && <Navbar forMobile={false} />}
|
||||
{isMobile && location.pathname === "/chat" && (
|
||||
<Navbar forMobile={true} />
|
||||
)}
|
||||
{isMobile && !showMobileNavbar && <Navbar forMobile={true} />}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -39,11 +39,26 @@ export default function Layout(props: { children: ReactNode }) {
|
|||
!isMobile && "rounded-tl-3xl border-t border-l",
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
{isMobile && location.pathname !== "/chat" && <MobileNavbar />}
|
||||
|
||||
{isMobile && showMobileNavbar && <MobileNavbar />}
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function useShowMobileNavbar(): boolean {
|
||||
const matches = useMatches();
|
||||
const location = useLocation();
|
||||
|
||||
const value = matches.some(
|
||||
(match) =>
|
||||
match.pathname === location.pathname &&
|
||||
// @ts-expect-error Stuff
|
||||
match.staticData?.showMobileNavbar === true,
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
12
apps/web/src/routes/settings/chat.tsx
Normal file
12
apps/web/src/routes/settings/chat.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Switch } from "@/features/settings/components";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Switch
|
||||
label="Reverse Enter Key Behavior"
|
||||
id="settings.reverse_enter_behavior"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
apps/web/src/routes/settings/index.tsx
Normal file
7
apps/web/src/routes/settings/index.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { SettingsSidebar } from "@/features/settings/layout";
|
||||
import { useIsMobile } from "@tensamin/ui/utils";
|
||||
|
||||
export default function Page() {
|
||||
const isMobile = useIsMobile();
|
||||
return isMobile && <SettingsSidebar />;
|
||||
}
|
||||
111
apps/web/src/routes/settings/security.tsx
Normal file
111
apps/web/src/routes/settings/security.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useEffect, useState } from "react";
|
||||
import QRCode from "qrcode";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@tensamin/ui/cmp/alert-dialog";
|
||||
import { Button } from "@tensamin/ui/cmp/button";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<QrCodeLogin />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// QR Code Login
|
||||
const generateQR = async (text: string): Promise<string> => {
|
||||
try {
|
||||
const url = await QRCode.toDataURL(text, {
|
||||
errorCorrectionLevel: "H",
|
||||
margin: 1,
|
||||
color: {
|
||||
dark: "#000000FF",
|
||||
light: "#FFFFFFFF",
|
||||
},
|
||||
});
|
||||
return url;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
function QrCodeLogin() {
|
||||
const { load } = useStorage();
|
||||
const [userId, setUserId] = useState<number>(0);
|
||||
const [privateKey, setPrivateKey] = useState<string>("");
|
||||
const [qrCodeBase64, setQrCodeBase64] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
load("private_key").then((value) => {
|
||||
if (value) {
|
||||
setPrivateKey(value);
|
||||
}
|
||||
});
|
||||
load("user_id").then((value) => {
|
||||
if (value) {
|
||||
setUserId(value);
|
||||
}
|
||||
});
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userId && privateKey) {
|
||||
generateQR(`tensamin://tu::${userId}::${privateKey}`).then(
|
||||
setQrCodeBase64,
|
||||
);
|
||||
}
|
||||
}, [userId, privateKey]);
|
||||
|
||||
const [qrCodeVisible, setQrCodeVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative rounded-lg w-50 border-3 aspect-square overflow-hidden">
|
||||
{qrCodeBase64 && (
|
||||
<img className="w-full h-full" src={qrCodeBase64} alt="QR Code" />
|
||||
)}
|
||||
{!qrCodeVisible && (
|
||||
<>
|
||||
<div className="absolute top-0 left-0 w-full h-full backdrop-blur-sm bg-background/50" />
|
||||
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger render={<Button>Show QR Code</Button>} />
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will expose your private key! This is the same as
|
||||
sharing the .tu file! It's very tedious to change the
|
||||
private key, so only do this if you are sure no one can
|
||||
steal it!
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
render={
|
||||
<Button onClick={() => setQrCodeVisible(true)}>
|
||||
Show QR Code
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
78
bun.lock
78
bun.lock
|
|
@ -23,7 +23,10 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-barcode-scanner": "~2",
|
||||
"@tauri-apps/plugin-deep-link": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tensamin/shared": "workspace:*",
|
||||
"@tensamin/ui": "workspace:*",
|
||||
"lucide-react": "^1.7.0",
|
||||
"react": "^19.2.0",
|
||||
|
|
@ -57,6 +60,7 @@
|
|||
"comlink": "^4.4.2",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^0.564.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"shadcn": "^4.0.7",
|
||||
|
|
@ -70,6 +74,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
|
|
@ -198,6 +203,7 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.3.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
|
@ -601,6 +607,10 @@
|
|||
|
||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
|
||||
|
||||
"@tauri-apps/plugin-barcode-scanner": ["@tauri-apps/plugin-barcode-scanner@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-uXvyMI8UgQjSrGxzTU5isNoQarMGRxFmTmb4TsgiWZHf/g7LsIyAQCwoFShjax0fXCK5mdVKDOvlkfOr21fo6g=="],
|
||||
|
||||
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="],
|
||||
|
||||
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
|
||||
|
||||
"@tensamin/call": ["@tensamin/call@workspace:packages/call"],
|
||||
|
|
@ -647,6 +657,8 @@
|
|||
|
||||
"@types/node": ["@types/node@25.4.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw=="],
|
||||
|
||||
"@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
|
@ -721,6 +733,8 @@
|
|||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
|
@ -733,7 +747,7 @@
|
|||
|
||||
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
"cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
|
|
@ -775,6 +789,8 @@
|
|||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||
|
||||
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
|
@ -793,6 +809,8 @@
|
|||
|
||||
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||
|
||||
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
|
@ -803,7 +821,7 @@
|
|||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
|
|
@ -1147,6 +1165,8 @@
|
|||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
|
@ -1171,6 +1191,8 @@
|
|||
|
||||
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||
|
||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||
|
|
@ -1189,6 +1211,8 @@
|
|||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||
|
||||
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
|
@ -1209,6 +1233,8 @@
|
|||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||
|
||||
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
|
@ -1247,6 +1273,8 @@
|
|||
|
||||
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"shadcn": ["shadcn@4.0.7", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-QwnD52EtnZo575yG/mk4fp6n0Qu8HZTVj10dsbg1O8DBLZPZG1V7AGOgTQSTjdsl8Jtye8GJfoKFz5npBkfExg=="],
|
||||
|
|
@ -1279,7 +1307,7 @@
|
|||
|
||||
"strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
|
||||
|
||||
|
|
@ -1381,6 +1409,8 @@
|
|||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||
|
|
@ -1389,13 +1419,13 @@
|
|||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
"y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
"yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
"yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
|
|
@ -1451,12 +1481,8 @@
|
|||
|
||||
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"eciesjs/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
|
||||
|
||||
"eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
|
@ -1469,8 +1495,12 @@
|
|||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"msw/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
|
|
@ -1479,11 +1509,11 @@
|
|||
|
||||
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
"yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
|
|
@ -1503,18 +1533,30 @@
|
|||
|
||||
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
"msw/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"msw/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"msw/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
"yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
"msw/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"msw/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"msw/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { useTTP } from "@tensamin/ttp/context";
|
|||
import { useCrypto } from "@tensamin/crypto/context";
|
||||
import { log, toast } from "@tensamin/shared/log";
|
||||
import Message from "./message";
|
||||
import { cn, useIsMobile } from "@tensamin/ui/utils";
|
||||
|
||||
export default function InputComponent({
|
||||
value,
|
||||
|
|
@ -27,7 +28,7 @@ export default function InputComponent({
|
|||
const { load } = useStorage();
|
||||
|
||||
React.useEffect(() => {
|
||||
void load("chat_invert_enter_behaviour").then((shouldInvert) => {
|
||||
void load("settings.reverse_enter_behavior").then((shouldInvert) => {
|
||||
setInvertEnterBehavior(shouldInvert);
|
||||
});
|
||||
}, [load]);
|
||||
|
|
@ -100,8 +101,17 @@ export default function InputComponent({
|
|||
setValue("");
|
||||
}
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Card className="rounded-none rounded-t-xl border-b-0 pt-0 pb-[env(safe-area-inset-bottom)]">
|
||||
<Card
|
||||
className={cn(
|
||||
"rounded-none border-b-0 pt-0 pb-[env(safe-area-inset-bottom)]",
|
||||
isMobile
|
||||
? "border-0 border-t-1 fixed w-full bottom-0 left-0 px-2"
|
||||
: "rounded-t-xl",
|
||||
)}
|
||||
>
|
||||
<CardHeader className="relative p-0 flex flex-col">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
|
|
|
|||
|
|
@ -258,7 +258,13 @@ export default function Screen() {
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div ref={inputBoxRef}>
|
||||
<div
|
||||
ref={inputBoxRef}
|
||||
className="fixed -bottom-15 pb-15"
|
||||
style={{
|
||||
width: "calc(100vw - 277px)",
|
||||
}}
|
||||
>
|
||||
<InputComponent setValue={setValue} value={value} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"exports": {
|
||||
"./data": "./src/data.ts",
|
||||
"./log": "./src/log.tsx",
|
||||
"./settings": "./src/settings.ts",
|
||||
"./features/legal/schema": "./src/features/legal/schema.ts",
|
||||
"./features/conversation/schema": "./src/features/conversation/schema.ts"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { legalDocsSchema } from "./features/legal/schema";
|
||||
import { settingsStorageDefaults, type SettingsStorageDefaults } from "./settings";
|
||||
|
||||
const fileFromMessage = z.object({
|
||||
name: z.string(),
|
||||
|
|
@ -168,7 +169,7 @@ export const ttp = {
|
|||
export type TTP = typeof ttp;
|
||||
|
||||
// Storage
|
||||
export interface Storage {
|
||||
export interface Storage extends SettingsStorageDefaults {
|
||||
session_id: number;
|
||||
user_id: number;
|
||||
private_key: string;
|
||||
|
|
@ -179,7 +180,6 @@ export interface Storage {
|
|||
analytics_usage_data: boolean;
|
||||
analytics_done: boolean;
|
||||
legal_docs: z.infer<typeof legalDocsSchema>;
|
||||
chat_invert_enter_behaviour: boolean;
|
||||
}
|
||||
|
||||
export const storageDefaults: Storage = {
|
||||
|
|
@ -192,6 +192,7 @@ export const storageDefaults: Storage = {
|
|||
analytics_crash_reports: true,
|
||||
analytics_usage_data: true,
|
||||
analytics_done: false,
|
||||
...settingsStorageDefaults,
|
||||
legal_docs: {
|
||||
eula: {
|
||||
version: "0.0",
|
||||
|
|
@ -209,5 +210,4 @@ export const storageDefaults: Storage = {
|
|||
unix: 0,
|
||||
},
|
||||
},
|
||||
chat_invert_enter_behaviour: false,
|
||||
};
|
||||
|
|
|
|||
76
packages/shared/src/settings.ts
Normal file
76
packages/shared/src/settings.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
type StringKeyOf<T> = Extract<keyof T, string>;
|
||||
|
||||
type SettingDefinition = {
|
||||
display: string;
|
||||
type: string;
|
||||
default?: unknown;
|
||||
};
|
||||
|
||||
export type SettingsSchema = Record<
|
||||
string,
|
||||
Record<string, Record<string, SettingDefinition>>
|
||||
>;
|
||||
|
||||
const settings = {
|
||||
account: {
|
||||
security: {
|
||||
login_qr_code: {
|
||||
display: "Login QR Code",
|
||||
type: "custom",
|
||||
},
|
||||
},
|
||||
},
|
||||
accessability: {
|
||||
chat: {
|
||||
reverse_enter_behavior: {
|
||||
display: "Reverse Enter Behavior",
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const satisfies SettingsSchema;
|
||||
|
||||
export default settings;
|
||||
|
||||
type BooleanSettingNames<T extends SettingsSchema> = {
|
||||
[C in StringKeyOf<T>]: {
|
||||
[P in StringKeyOf<T[C]>]: {
|
||||
[S in StringKeyOf<T[C][P]>]: T[C][P][S] extends {
|
||||
type: "boolean";
|
||||
default: boolean;
|
||||
}
|
||||
? S
|
||||
: never;
|
||||
}[StringKeyOf<T[C][P]>];
|
||||
}[StringKeyOf<T[C]>];
|
||||
}[StringKeyOf<T>];
|
||||
|
||||
export type SettingsStorageKey<T extends SettingsSchema = typeof settings> =
|
||||
`settings.${BooleanSettingNames<T>}`;
|
||||
|
||||
export type SettingsStorageDefaults<T extends SettingsSchema = typeof settings> =
|
||||
{
|
||||
[K in SettingsStorageKey<T>]: boolean;
|
||||
};
|
||||
|
||||
function buildSettingsStorageDefaults<T extends SettingsSchema>(
|
||||
schema: T,
|
||||
): SettingsStorageDefaults<T> {
|
||||
const defaults: Partial<SettingsStorageDefaults<T>> = {};
|
||||
|
||||
for (const category of Object.values(schema)) {
|
||||
for (const page of Object.values(category)) {
|
||||
for (const [settingName, setting] of Object.entries(page)) {
|
||||
if (setting.type === "boolean" && typeof setting.default === "boolean") {
|
||||
const key = `settings.${settingName}` as SettingsStorageKey<T>;
|
||||
defaults[key] = setting.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaults as SettingsStorageDefaults<T>;
|
||||
}
|
||||
|
||||
export const settingsStorageDefaults = buildSettingsStorageDefaults(settings);
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
"./utils": "./src/lib/utils.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"patch": "find src -type f \\( -name \"*.ts\" -o -name \"*.tsx\" \\) -print0 | xargs -0 sed -i 's|@/lib/utils|../lib/utils|g; s|@/cmp/|./|g' && bunx prettier --write .",
|
||||
"format": "bunx prettier --write .",
|
||||
"lint": "eslint src",
|
||||
"build": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tensamin/tauri": "workspace:*",
|
||||
"@base-ui/react": "^1.3.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
|
|
|||
30
packages/ui/src/cmp/switch.tsx
Normal file
30
packages/ui/src/cmp/switch.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
|
||||
|
||||
import { cn } from "../lib/utils"
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
size = "default",
|
||||
...props
|
||||
}: SwitchPrimitive.Root.Props & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Switch }
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { cn } from "../lib/utils";
|
||||
import Controls from "@tensamin/tauri/controls";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
|
||||
export default function CreateScreen({
|
||||
children,
|
||||
|
|
@ -41,7 +42,13 @@ import { useIsMobile } from "../lib/utils";
|
|||
export function StorageDestoryer() {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<div className={cn("fixed p-5 bottom-0", isMobile ? "w-full" : "right-0")}>
|
||||
<div
|
||||
className={cn(
|
||||
"fixed p-5 bottom-0",
|
||||
isMobile ? "w-full" : "right-0",
|
||||
isTauri() ? "py-10" : "",
|
||||
)}
|
||||
>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger
|
||||
render={
|
||||
|
|
|
|||
Loading…
Reference in a new issue