From b98c31b18487b69635d05c8c9d38cd627ef97b56 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 2 Jun 2025 08:54:16 +0700 Subject: [PATCH] enhancement: open folder log and change data folder dialog confirm (#5159) * enhancement: ux change data folder with confirmation and reveal in finder logs * chore: update button open logs local api server * Update web-app/src/components/ui/button.tsx Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * chore: handle error when change location data folder failed --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src-tauri/Cargo.toml | 1 + src-tauri/capabilities/default.json | 11 +- src-tauri/src/lib.rs | 1 + web-app/package.json | 1 + web-app/src/components/ui/button.tsx | 2 +- .../dialogs/ChangeDataFolderLocation.tsx | 81 +++++++ web-app/src/routes/settings/general.tsx | 209 ++++++++++++++---- .../src/routes/settings/local-api-server.tsx | 4 +- 8 files changed, 258 insertions(+), 52 deletions(-) create mode 100644 web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f03faf629..ef8932d4e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -25,6 +25,7 @@ tauri = { version = "2.4.0", features = [ "protocol-asset", "macos-private-api", tauri-plugin-log = "2.0.0-rc" tauri-plugin-shell = "2.2.0" tauri-plugin-os = "2.2.1" +tauri-plugin-opener = "2.2.7" flate2 = "1.0" tar = "0.4" rand = "0.8" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 36a40f6f3..566118a7d 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,13 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "enables the default permissions", - "windows": [ - "main" - ], + "windows": ["main"], "remote": { - "urls": [ - "http://*" - ] + "urls": ["http://*"] }, "permissions": [ "core:default", @@ -19,6 +15,7 @@ "core:app:allow-set-app-theme", "core:window:allow-set-focus", "os:default", + "opener:default", "log:default", "updater:default", "dialog:default", @@ -77,4 +74,4 @@ }, "store:default" ] -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 744fb383a..6e5b9e1c5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,6 +17,7 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_updater::Builder::new().build()) diff --git a/web-app/package.json b/web-app/package.json index 8b8e736ad..d0941dd2b 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -31,6 +31,7 @@ "@tanstack/react-router-devtools": "^1.116.0", "@tauri-apps/api": "^2.5.0", "@tauri-apps/plugin-dialog": "^2.2.1", + "@tauri-apps/plugin-opener": "^2.2.7", "@tauri-apps/plugin-os": "^2.2.1", "@tauri-apps/plugin-updater": "^2.7.1", "@types/react-syntax-highlighter": "^15.5.13", diff --git a/web-app/src/components/ui/button.tsx b/web-app/src/components/ui/button.tsx index ea53b16cd..bface0830 100644 --- a/web-app/src/components/ui/button.tsx +++ b/web-app/src/components/ui/button.tsx @@ -16,7 +16,7 @@ const buttonVariants = cva( }, size: { default: 'h-7 px-3 py-2 has-[>svg]:px-3 rounded-sm', - sm: 'h-6 rounded gap-1.5 px-2 has-[>svg]:px-2.5', + sm: 'h-6 gap-1.5 px-2 has-[>svg]:px-2.5 rounded-sm', lg: 'h-9 rounded-md px-4 has-[>svg]:px-4', icon: 'size-8', }, diff --git a/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx b/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx new file mode 100644 index 000000000..501c1ce36 --- /dev/null +++ b/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx @@ -0,0 +1,81 @@ +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { IconFolder } from '@tabler/icons-react' + +interface ChangeDataFolderLocationProps { + children: React.ReactNode + currentPath: string + newPath: string + onConfirm: () => void + open: boolean + onOpenChange: (open: boolean) => void +} + +export default function ChangeDataFolderLocation({ + children, + currentPath, + newPath, + onConfirm, + open, + onOpenChange, +}: ChangeDataFolderLocationProps) { + return ( + + {children} + + + + + Change Data Folder Location + + + Are you sure you want to change the data folder location? This will + move all your data to the new location and restart the application. + + + +
+
+

+ Current Location: +

+
+ + {currentPath} + +
+
+ +
+

+ New Location: +

+
+ {newPath} +
+
+
+ + + + + + + + + +
+
+ ) +} diff --git a/web-app/src/routes/settings/general.tsx b/web-app/src/routes/settings/general.tsx index 533e8673d..4fc441e21 100644 --- a/web-app/src/routes/settings/general.tsx +++ b/web-app/src/routes/settings/general.tsx @@ -10,6 +10,8 @@ import { useTranslation } from 'react-i18next' import { useGeneralSetting } from '@/hooks/useGeneralSetting' import { useEffect, useState } from 'react' import { open } from '@tauri-apps/plugin-dialog' +import { revealItemInDir } from '@tauri-apps/plugin-opener' +import ChangeDataFolderLocation from '@/containers/dialogs/ChangeDataFolderLocation' import { Dialog, @@ -32,19 +34,35 @@ import { IconExternalLink, IconFolder, IconLogs, + IconCopy, + IconCopyCheck, } from '@tabler/icons-react' import { WebviewWindow } from '@tauri-apps/api/webviewWindow' import { windowKey } from '@/constants/windows' +import { toast } from 'sonner' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.general as any)({ component: General, }) +const openFileTitle = (): string => { + if (IS_MACOS) { + return 'Show in Finder' + } else if (IS_WINDOWS) { + return 'Show in File Explorer' + } else { + return 'Open Containing Folder' + } +} + function General() { const { t } = useTranslation() const { spellCheckChatInput, setSpellCheckChatInput } = useGeneralSetting() const [janDataFolder, setJanDataFolder] = useState() + const [isCopied, setIsCopied] = useState(false) + const [selectedNewPath, setSelectedNewPath] = useState(null) + const [isDialogOpen, setIsDialogOpen] = useState(false) useEffect(() => { const fetchDataFolder = async () => { @@ -97,6 +115,52 @@ function General() { } } + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text) + setIsCopied(true) + setTimeout(() => setIsCopied(false), 2000) // Reset after 2 seconds + } catch (error) { + console.error('Failed to copy to clipboard:', error) + } + } + + const handleDataFolderChange = async () => { + const selectedPath = await open({ + multiple: false, + directory: true, + defaultPath: janDataFolder, + }) + + if (selectedPath === janDataFolder) return + if (selectedPath !== null) { + setSelectedNewPath(selectedPath) + setIsDialogOpen(true) + } + } + + const confirmDataFolderChange = async () => { + if (selectedNewPath) { + try { + setJanDataFolder(selectedNewPath) + await relocateJanDataFolder(selectedNewPath) + // Only relaunch if relocation was successful + window.core?.api?.relaunch() + setSelectedNewPath(null) + setIsDialogOpen(false) + } catch (error) { + console.error('Failed to relocate data folder:', error) + // Revert the data folder path on error + const originalPath = await getJanDataFolder() + setJanDataFolder(originalPath) + + toast.error( + 'Failed to relocate data folder. Please try again or choose a different location.' + ) + } + } + } + return (
@@ -133,42 +197,71 @@ function General() { {t('settings.dataFolder.appDataDesc', { ns: 'settings', })} +   - - {janDataFolder} - +
+ + {janDataFolder} + + +
} actions={ - + <> + + {selectedNewPath && ( + { + setIsDialogOpen(open) + if (!open) { + setSelectedNewPath(null) + } + }} + > +
+ + )} + } /> - {/* Open Logs */} -
- -
- +
+ + +
} /> diff --git a/web-app/src/routes/settings/local-api-server.tsx b/web-app/src/routes/settings/local-api-server.tsx index 7ad6227c0..d6a13ac36 100644 --- a/web-app/src/routes/settings/local-api-server.tsx +++ b/web-app/src/routes/settings/local-api-server.tsx @@ -140,11 +140,13 @@ function LocalAPIServer() { }