fix(gui): wire sync credentials, fix memory leak, race condition, add delete confirmations
- Add load_credentials Tauri command and use it in triggerSync() instead of passing empty username/password strings - Replace raw __TAURI_INTERNALS__.invoke() with proper invoke import in SettingsScreen - Wrap window event listeners in $effect() with cleanup to prevent memory leak on component remount - Return created Task from createTask() and use it directly in NewTaskInput instead of guessing from array index - Add confirm() dialogs before deleting tasks, lists, and workspaces
This commit is contained in:
parent
a54e427cd9
commit
c138a8bcf6
|
|
@ -310,6 +310,11 @@ fn store_credentials(
|
|||
webdav::store_credentials(&domain, &username, &password).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn load_credentials(domain: String) -> Result<(String, String), String> {
|
||||
webdav::load_credentials(&domain).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn test_webdav_connection(
|
||||
url: String,
|
||||
|
|
@ -373,6 +378,7 @@ pub fn run() {
|
|||
reorder_task,
|
||||
set_webdav_config,
|
||||
store_credentials,
|
||||
load_credentials,
|
||||
test_webdav_connection,
|
||||
sync_workspace,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@
|
|||
|
||||
async function handleSubmit() {
|
||||
if (!title.trim()) return;
|
||||
await app.createTask(title.trim(), description.trim() || undefined);
|
||||
if (dueDate && app.tasks.length > 0) {
|
||||
const created = app.tasks[app.tasks.length - 1];
|
||||
const created = await app.createTask(title.trim(), description.trim() || undefined);
|
||||
if (dueDate && created) {
|
||||
await app.updateTask({ ...created, due_date: dueDate, updated_at: new Date().toISOString() });
|
||||
}
|
||||
title = "";
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
|
||||
async function handleDelete() {
|
||||
showMenu = false;
|
||||
if (!confirm(`Delete task "${task.title}"?`)) return;
|
||||
await app.deleteTask(task.id);
|
||||
onback();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { app } from "../stores/app.svelte";
|
||||
|
||||
let { onclose }: { onclose?: () => void } = $props();
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
async function testConnection() {
|
||||
testStatus = "testing";
|
||||
try {
|
||||
await (globalThis as any).__TAURI_INTERNALS__.invoke("test_webdav_connection", {
|
||||
await invoke("test_webdav_connection", {
|
||||
url: webdavUrl,
|
||||
username: webdavUser,
|
||||
password: webdavPass,
|
||||
|
|
@ -24,18 +25,19 @@
|
|||
|
||||
async function saveWebdav() {
|
||||
if (!app.config?.current_workspace || !webdavUrl.trim()) return;
|
||||
await (globalThis as any).__TAURI_INTERNALS__.invoke("set_webdav_config", {
|
||||
await invoke("set_webdav_config", {
|
||||
workspaceName: app.config.current_workspace,
|
||||
webdavUrl: webdavUrl.trim(),
|
||||
});
|
||||
if (webdavUser && webdavPass) {
|
||||
const domain = new URL(webdavUrl).hostname;
|
||||
await (globalThis as any).__TAURI_INTERNALS__.invoke("store_credentials", {
|
||||
await invoke("store_credentials", {
|
||||
domain,
|
||||
username: webdavUser,
|
||||
password: webdavPass,
|
||||
});
|
||||
}
|
||||
await app.loadConfig();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@
|
|||
if (wsMenuName && !target.closest("[data-ws-menu]")) wsMenuName = null;
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("mousedown", handleWindowClick);
|
||||
}
|
||||
let newListName = $state("");
|
||||
let showCompleted = $state(false);
|
||||
let completedVisible = $state(false);
|
||||
|
|
@ -52,13 +49,19 @@
|
|||
let resizing = $state(false);
|
||||
let resizeTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("resize", () => {
|
||||
$effect(() => {
|
||||
window.addEventListener("mousedown", handleWindowClick);
|
||||
const handleResize = () => {
|
||||
resizing = true;
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => (resizing = false), 150);
|
||||
});
|
||||
}
|
||||
};
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", handleWindowClick);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
});
|
||||
|
||||
async function handleNewList() {
|
||||
if (!newListName.trim()) return;
|
||||
|
|
@ -69,6 +72,8 @@
|
|||
|
||||
async function handleDeleteList(id: string) {
|
||||
listMenuId = null;
|
||||
const list = app.lists.find(l => l.id === id);
|
||||
if (!confirm(`Delete list "${list?.title ?? id}" and all its tasks?`)) return;
|
||||
await app.deleteList(id);
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +275,7 @@
|
|||
{#if wsMenuName === name}
|
||||
<div class="absolute right-0 top-full z-40 mt-1 min-w-[140px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
|
||||
<button
|
||||
onclick={() => { wsMenuName = null; app.removeWorkspace(name); }}
|
||||
onclick={() => { wsMenuName = null; if (confirm(`Remove workspace "${name}"? (Files remain on disk)`)) app.removeWorkspace(name); }}
|
||||
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
|
||||
>
|
||||
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ async function deleteList(id: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function createTask(title: string, description?: string) {
|
||||
if (!activeListId) return;
|
||||
async function createTask(title: string, description?: string): Promise<Task | null> {
|
||||
if (!activeListId) return null;
|
||||
try {
|
||||
const task = await invoke<Task>("create_task", {
|
||||
listId: activeListId,
|
||||
|
|
@ -150,8 +150,10 @@ async function createTask(title: string, description?: string) {
|
|||
});
|
||||
tasks = [...tasks, task];
|
||||
error = null;
|
||||
return task;
|
||||
} catch (e) {
|
||||
error = String(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -214,11 +216,13 @@ async function triggerSync() {
|
|||
syncing = true;
|
||||
error = null;
|
||||
try {
|
||||
const domain = new URL(ws.webdav_url).hostname;
|
||||
const [username, password] = await invoke<[string, string]>("load_credentials", { domain });
|
||||
const result = await invoke<SyncResult>("sync_workspace", {
|
||||
workspacePath: ws.path,
|
||||
webdavUrl: ws.webdav_url,
|
||||
username: "",
|
||||
password: "",
|
||||
username,
|
||||
password,
|
||||
});
|
||||
if (result.errors.length > 0) {
|
||||
error = result.errors.join("; ");
|
||||
|
|
|
|||
Loading…
Reference in a new issue