diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 6a43333..db230ae 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -160,6 +160,26 @@ fn remove_workspace( .map_err(|e| e.to_string()) } +#[tauri::command] +fn rename_workspace( + old_name: String, + new_name: String, + state: State<'_, Mutex>, +) -> Result<(), String> { + let mut s = lock_state(&state)?; + let ws = s.config.get_workspace(&old_name) + .ok_or_else(|| format!("Workspace '{}' not found", old_name))?; + let old_path = ws.path.clone(); + let new_path = old_path.parent() + .ok_or("Workspace path has no parent directory")? + .join(&new_name); + std::fs::rename(&old_path, &new_path).map_err(|e| e.to_string())?; + s.config.rename_workspace(&old_name, new_name.clone()).map_err(|e| e.to_string())?; + s.config.workspaces.get_mut(&new_name).unwrap().path = new_path; + s.repo = None; + s.config.save_to_file(&s.config_path.clone()).map_err(|e| e.to_string()) +} + // ── Workspace init ─────────────────────────────────────────────────── #[tauri::command] @@ -648,6 +668,7 @@ pub fn run() { add_workspace, set_current_workspace, remove_workspace, + rename_workspace, init_workspace, get_lists, create_list, diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index 0257973..1d0bf38 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core"; import { app } from "../stores/app.svelte"; - let { onclose, workspaceName }: { onclose?: () => void; workspaceName: string } = $props(); + let { onclose, workspaceName, onrename, ondelete }: { onclose?: () => void; workspaceName: string; onrename?: (newName: string) => void; ondelete?: (name: string) => void } = $props(); let ws = $derived(app.config?.workspaces[workspaceName]); let isWebdav = $derived(ws?.mode === "webdav"); @@ -12,6 +12,10 @@ let webdavPass = $state(""); let testStatus = $state<"idle" | "testing" | "ok" | "fail">("idle"); + let renaming = $state(false); + let renameValue = $state(""); + let showKebab = $state(false); + $effect(() => { if (!ws?.webdav_url) return; webdavUrl = ws.webdav_url; @@ -54,12 +58,32 @@ } await app.loadConfig(); } + + function startRename() { + showKebab = false; + renaming = true; + renameValue = workspaceName; + } + + async function handleRename() { + if (!renaming) return; + renaming = false; + var trimmed = renameValue.trim(); + if (!trimmed || trimmed === workspaceName) return; + await app.renameWorkspace(workspaceName, trimmed); + onrename?.(trimmed); + } + function handleWindowClick(e: MouseEvent) { + if (showKebab && !(e.target as HTMLElement).closest("[data-settings-kebab]")) showKebab = false; + } + +
-

{workspaceName} Settings

+

Workspace Settings

+ +
+
+ {#if renaming} + { if (e.key === "Enter") handleRename(); if (e.key === "Escape") { renaming = false; } }} + onblur={handleRename} + autofocus + /> + {:else} +

{workspaceName}

+ {/if} +
+
+ + {#if showKebab} +
+ + +
+ {/if} +
+
+
{#if isWebdav} @@ -157,27 +231,22 @@
-

- Appearance -

-
- - -
+ +

Tauri v2 + Svelte

diff --git a/apps/tauri/src/lib/screens/TasksScreen.svelte b/apps/tauri/src/lib/screens/TasksScreen.svelte index a21a39a..e56a44b 100644 --- a/apps/tauri/src/lib/screens/TasksScreen.svelte +++ b/apps/tauri/src/lib/screens/TasksScreen.svelte @@ -45,14 +45,11 @@ showWorkspacePicker = false; if (showListMenu && listMenuEl && !listMenuEl.contains(e.target as Node)) showListMenu = false; - const target = e.target as HTMLElement; - if (wsMenuName && !target.closest("[data-ws-menu]")) wsMenuName = null; } let newListName = $state(""); let showCompleted = $state(false); let completedVisible = $state(false); - let wsMenuName = $state(null); let renamingListId = $state(null); let renameValue = $state(""); let showListMenu = $state(false); @@ -137,7 +134,6 @@ if (taskStack.length > 0) { closeDetail(); return; } if (showListMenu) { showListMenu = false; return; } if (showDrawer) { closeDrawer(); return; } - if (wsMenuName) { wsMenuName = null; return; } if (showWorkspacePicker) { showWorkspacePicker = false; return; } } @@ -257,38 +253,14 @@

{ws?.mode === "webdav" ? ws.webdav_url ?? "WebDAV" : ws?.path ?? ""}

-
- - {#if wsMenuName === name} -
- - -
- {/if} -
+ {/each}
@@ -625,7 +597,7 @@ class="relative flex h-full w-full flex-col overflow-hidden rounded-2xl bg-surface-light transition-transform duration-200 dark:bg-surface-dark {showSettings ? 'scale-100' : 'scale-95'}" style="border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 25px 60px rgba(0,0,0,0.7), 0 10px 20px rgba(0,0,0,0.5)" > - + settingsWorkspace = newName} ondelete={(name) => { closeSettings(); confirmRemoveWorkspace = name; }} />
diff --git a/apps/tauri/src/lib/stores/app.svelte.ts b/apps/tauri/src/lib/stores/app.svelte.ts index a76aeb5..f54bbe0 100644 --- a/apps/tauri/src/lib/stores/app.svelte.ts +++ b/apps/tauri/src/lib/stores/app.svelte.ts @@ -109,6 +109,16 @@ async function switchWorkspace(name: string) { } } +async function renameWorkspace(oldName: string, newName: string) { + try { + await invoke("rename_workspace", { oldName, newName }); + config = await invoke("get_config"); + error = null; + } catch (e) { + error = String(e); + } +} + async function removeWorkspace(name: string) { try { await invoke("remove_workspace", { name }); @@ -393,6 +403,7 @@ export const app = { loadConfig, addWorkspace, switchWorkspace, + renameWorkspace, removeWorkspace, loadLists, loadTasks, diff --git a/crates/onyx-core/src/config.rs b/crates/onyx-core/src/config.rs index 8d22016..cf46d65 100644 --- a/crates/onyx-core/src/config.rs +++ b/crates/onyx-core/src/config.rs @@ -60,6 +60,21 @@ impl AppConfig { self.workspaces.remove(name) } + pub fn rename_workspace(&mut self, old_name: &str, new_name: String) -> Result<()> { + if !self.workspaces.contains_key(old_name) { + return Err(Error::InvalidData(format!("Workspace '{}' not found", old_name))); + } + if self.workspaces.contains_key(&new_name) { + return Err(Error::InvalidData(format!("Workspace '{}' already exists", new_name))); + } + let ws = self.workspaces.remove(old_name).unwrap(); + if self.current_workspace.as_deref() == Some(old_name) { + self.current_workspace = Some(new_name.clone()); + } + self.workspaces.insert(new_name, ws); + Ok(()) + } + pub fn get_workspace(&self, name: &str) -> Option<&WorkspaceConfig> { self.workspaces.get(name) }