diff --git a/PLAN.md b/PLAN.md index 9815686..4f324fb 100644 --- a/PLAN.md +++ b/PLAN.md @@ -706,6 +706,8 @@ WorkspaceConfig { - [x] Settings popup overlay (WebDAV config, dark mode toggle) - [x] Dark mode (GNOME-style neutral theme, cyan-blue accent) - [x] Animated completed section show/hide +- [ ] Move task between lists (needs `move_task(from_list, to_list, task_id)` added to bevy-tasks-core + Tauri command, then wire into task detail kebab menu) +- [ ] Optional time on due dates (backend `due_date` is `DateTime` — needs a separate `due_time` field or a nullable time component so date-only tasks don't default to midnight; currently the GUI uses `hours == 0 && minutes == 0` as a heuristic for "no time set" which breaks for actual midnight times) - [ ] Due date picker/editor (backend supports it, needs date input in new task toast + inline editing) - [ ] WebDAV setup flow with credentials (settings panel has fields, triggerSync needs to pull creds from config) - [ ] List/workspace rename (needs `rename_list` added to bevy-tasks-core first) diff --git a/apps/tauri/package-lock.json b/apps/tauri/package-lock.json index 794946d..27a6f67 100644 --- a/apps/tauri/package-lock.json +++ b/apps/tauri/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0", "dependencies": { "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-dialog": "^2.0.0" + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-os": "^2.3.2" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", @@ -1421,6 +1422,15 @@ "@tauri-apps/api": "^2.8.0" } }, + "node_modules/@tauri-apps/plugin-os": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.3.2.tgz", + "integrity": "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", diff --git a/apps/tauri/package.json b/apps/tauri/package.json index c3290d5..7229dab 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-dialog": "^2.0.0" + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-os": "^2.3.2" } } diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index bc8a859..bee0a11 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -13,6 +13,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-dialog = "2" +tauri-plugin-os = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" bevy-tasks-core = { path = "../../../crates/bevy-tasks-core" } diff --git a/apps/tauri/src-tauri/capabilities/default.json b/apps/tauri/src-tauri/capabilities/default.json new file mode 100644 index 0000000..e08700a --- /dev/null +++ b/apps/tauri/src-tauri/capabilities/default.json @@ -0,0 +1,15 @@ +{ + "identifier": "default", + "description": "Default capabilities for Bevy Tasks", + "windows": ["main"], + "permissions": [ + "core:default", + "dialog:default", + "os:default", + "core:window:allow-close", + "core:window:allow-minimize", + "core:window:allow-toggle-maximize", + "core:window:allow-start-dragging", + "core:window:allow-is-maximized" + ] +} diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 7859b6c..b4ef687 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -353,6 +353,7 @@ pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_os::init()) .manage(Mutex::new(AppState { config, repo: None })) .invoke_handler(tauri::generate_handler![ get_config, diff --git a/apps/tauri/src-tauri/tauri.conf.json b/apps/tauri/src-tauri/tauri.conf.json index e60211c..ad02de7 100644 --- a/apps/tauri/src-tauri/tauri.conf.json +++ b/apps/tauri/src-tauri/tauri.conf.json @@ -18,7 +18,9 @@ "height": 700, "minWidth": 320, "minHeight": 500, - "resizable": true + "resizable": true, + "decorations": false, + "transparent": true } ], "security": { diff --git a/apps/tauri/src/App.svelte b/apps/tauri/src/App.svelte index c14a839..ecc3e47 100644 --- a/apps/tauri/src/App.svelte +++ b/apps/tauri/src/App.svelte @@ -11,22 +11,25 @@
-
- {#if app.error} -
- {app.error} - -
- {/if} +
+
+ {#if app.error} +
+ {app.error} + +
+ {/if} - {#if app.screen === "setup"} - - {:else} - - {/if} + {#if app.screen === "setup"} + + {:else} + + {/if} +
diff --git a/apps/tauri/src/app.css b/apps/tauri/src/app.css index 1804ee4..7636361 100644 --- a/apps/tauri/src/app.css +++ b/apps/tauri/src/app.css @@ -24,11 +24,16 @@ -moz-osx-font-smoothing: grayscale; } +html { + background: transparent; +} + body { margin: 0; overflow: hidden; user-select: none; -webkit-user-select: none; + background: transparent; } /* Scrollbar styling */ @@ -42,3 +47,9 @@ body { .dark ::-webkit-scrollbar-thumb { background: #4b5563; } + +/* Select dropdown theming */ +.dark select option { + background-color: #242424; + color: #e5e7eb; +} diff --git a/apps/tauri/src/lib/components/DateTimePicker.svelte b/apps/tauri/src/lib/components/DateTimePicker.svelte new file mode 100644 index 0000000..40cafce --- /dev/null +++ b/apps/tauri/src/lib/components/DateTimePicker.svelte @@ -0,0 +1,186 @@ + + + + +
{ if (e.key === "Escape") dismiss(); }} +> + +
e.stopPropagation()} + > + +
+ Date & Time + +
+ + +
+ {monthLabel} +
+ + +
+
+ + +
+ {#each DAY_NAMES as name} +
{name}
+ {/each} +
+ + +
+ {#each calendarCells as day} + {#if day === null} +
+ {:else} + + {/if} + {/each} +
+ + +
+ {#if includeTime} + Time +
+ + : + +
+ + {:else} + + {/if} +
+ + + {#if value} +
+ +
+ {/if} +
+
diff --git a/apps/tauri/src/lib/components/TaskDetailView.svelte b/apps/tauri/src/lib/components/TaskDetailView.svelte new file mode 100644 index 0000000..20085e7 --- /dev/null +++ b/apps/tauri/src/lib/components/TaskDetailView.svelte @@ -0,0 +1,195 @@ + + + + +
+ + + +
+ + {#if showMenu} +
+ +
+ {/if} +
+
+ + +
+ + + + +
+ + + + +
+ + +
+ + + + {#if task.due_date} +
+ + +
+ {:else} + + {/if} +
+
+ + +
+ +
+ + +{#if showDatePicker} + (showDatePicker = false)} + /> +{/if} diff --git a/apps/tauri/src/lib/components/TaskItem.svelte b/apps/tauri/src/lib/components/TaskItem.svelte index 9e40f90..c2e23e7 100644 --- a/apps/tauri/src/lib/components/TaskItem.svelte +++ b/apps/tauri/src/lib/components/TaskItem.svelte @@ -1,5 +1,4 @@ @@ -7,26 +6,18 @@ import type { Task } from "../types"; import { app } from "../stores/app.svelte"; - let { task }: { task: Task } = $props(); + let { task, onopen }: { task: Task; onopen?: (task: Task) => void } = $props(); - let editTitle = $state(task.title); - let editDesc = $state(task.description); - let editing = $derived(editingTaskId === task.id); let touchStartX = $state(0); let swipeX = $state(0); let swiping = $state(false); - let containerEl = $state(null); - let titleInputEl = $state(null); - let showMenu = $state(false); - let menuEl = $state(null); let transitioning = $state(false); let animatingIn = $state(false); let isCompleted = $derived(task.status === "completed"); $effect(() => { - // Check on status change whether this task should animate in - const _ = task.status; // track reactively + const _ = task.status; if (animateInIds.has(task.id)) { animateInIds.delete(task.id); animatingIn = true; @@ -38,50 +29,14 @@ } }); - async function handleToggle() { + async function handleToggle(e: MouseEvent) { + e.stopPropagation(); transitioning = true; animateInIds.add(task.id); await new Promise((r) => setTimeout(r, 200)); await app.toggleTask(task.id); } - function handleMenuClickOutside(e: MouseEvent) { - if (showMenu && menuEl && !menuEl.contains(e.target as Node)) { - showMenu = false; - } - } - - $effect(() => { - if (showMenu) { - window.addEventListener("mousedown", handleMenuClickOutside); - return () => window.removeEventListener("mousedown", handleMenuClickOutside); - } - }); - - function startEditing() { - if (editing) return; - editingTaskId = task.id; - editTitle = task.title; - editDesc = task.description; - setTimeout(() => titleInputEl?.focus(), 220); - } - - async function save() { - if (editingTaskId !== task.id) return; - editingTaskId = null; - const trimmed = editTitle.trim(); - if (!trimmed) { editTitle = task.title; return; } - if (trimmed === task.title && editDesc === task.description) return; - await app.updateTask({ ...task, title: trimmed, description: editDesc }); - } - - function handleFocusOut(e: FocusEvent) { - if (containerEl?.contains(e.relatedTarget as Node)) return; - requestAnimationFrame(() => { - if (editingTaskId === task.id) save(); - }); - } - function handleTouchStart(e: TouchEvent) { touchStartX = e.touches[0].clientX; swiping = true; @@ -98,7 +53,9 @@ if (Math.abs(swipeX) > 100) { swipeX = 0; swiping = false; - handleToggle(); + transitioning = true; + animateInIds.add(task.id); + setTimeout(() => app.toggleTask(task.id), 200); return; } swipeX = 0; @@ -120,9 +77,9 @@ class="grid transition-[grid-template-rows,opacity] duration-300 ease-out {animatingIn || transitioning ? 'grid-rows-[0fr] opacity-0' : 'grid-rows-[1fr] opacity-100'}" >
+
- -
onopen?.(task)} > - - - -
- {#if editing} - { if (e.key === "Enter") (e.target as HTMLElement).blur(); if (e.key === "Escape") { editTitle = task.title; editDesc = task.description; editingTaskId = null; } }} - /> - {:else} -

- {task.title} -

- {#if task.description} -

{task.description}

- {/if} - {#if task.due_date} - - {formatDate(task.due_date)} - - {/if} - {/if} - - -
-
- -
-
- -
- - {#if showMenu} -
- -
- {/if} -
-
+ +
+

+ {task.title} +

+ {#if task.description} +

{task.description}

+ {/if} + {#if task.due_date} + + {formatDate(task.due_date)} + + {/if} +
+ + + + + +
diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index afca842..311ddc2 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -41,20 +41,19 @@
+

Settings

-

Settings

diff --git a/apps/tauri/src/lib/screens/TasksScreen.svelte b/apps/tauri/src/lib/screens/TasksScreen.svelte index c95d603..41bfb3e 100644 --- a/apps/tauri/src/lib/screens/TasksScreen.svelte +++ b/apps/tauri/src/lib/screens/TasksScreen.svelte @@ -1,8 +1,28 @@ -
+
-
+
{#each app.lists as list (list.id)} @@ -287,7 +313,7 @@
-
+
{ if (e.key === "Escape") closeDrawer(); }} >
- -
+
- - - - -
-

- {app.config?.current_workspace ?? ""} -

-

{app.activeList?.title ?? "Tasks"}

-
- - - {#if app.syncing} -
- {/if} -
- - -
- {#if app.lists.length === 0} -
-

No lists yet

-

Tap the list name above to create one

-
- {:else if !app.activeListId} -
- Select a list -
- {:else} - {#each app.pendingTasks as task (task.id)} - -
handleDragStart(e, task.id)} - ondragover={(e) => handleDragOver(e, task.id)} - ondragend={handleDragEnd} - ondrop={(e) => handleDrop(e, task.id)} - class="{dragId === task.id ? 'opacity-30' : ''} {dragOverId === task.id && dragId !== task.id ? 'border-t-2 border-t-primary' : ''}" - > - -
- {/each} - - {#if app.pendingTasks.length === 0} -
No tasks. Add one below.
- {/if} - - {#if app.completedTasks.length > 0} -
+ +
+ + +
{ if (isDesktop) appWindow.toggleMaximize(); }} + class="relative flex items-center border-b border-border-light px-4 py-3 dark:border-border-dark" + > + - {#if completedVisible} -
- {#each app.completedTasks as task (task.id)} - - {/each} + + +
+

+ {app.config?.current_workspace ?? ""} +

+

{app.activeList?.title ?? "Tasks"}

+
+ + + {#if isDesktop} +
+ {#if isWindows} + + + {/if} +
{/if} - {/if} - {/if} -
+ - -
- + +
+ {#if app.lists.length === 0} +
+

No lists yet

+

Tap the list name above to create one

+
+ {:else if !app.activeListId} +
+ Select a list +
+ {:else} + {#each app.pendingTasks as task (task.id)} + +
handleDragStart(e, task.id)} + ondragover={(e) => handleDragOver(e, task.id)} + ondragend={handleDragEnd} + ondrop={(e) => handleDrop(e, task.id)} + class="{dragId === task.id ? 'opacity-30' : ''} {dragOverId === task.id && dragId !== task.id ? 'border-t-2 border-t-primary' : ''}" + > + +
+ {/each} + + {#if app.pendingTasks.length === 0} +
No tasks. Add one below.
+ {/if} + + {#if app.completedTasks.length > 0} +
+ + {#if completedVisible} +
+ {#each app.completedTasks as task (task.id)} + + {/each} +
+ {/if} + {/if} + {/if} +
+ + +
+ +
+
+ + +
+ {#if selectedTask} + + {/if} +
+ + + {#if app.syncing} +
+ {/if}
@@ -412,7 +490,7 @@
@@ -431,6 +509,6 @@
-
+