diff --git a/opal-web/src/app.css b/opal-web/src/app.css index 1eefb54..bfc2afa 100644 --- a/opal-web/src/app.css +++ b/opal-web/src/app.css @@ -68,6 +68,8 @@ --color-overdue-text: #f85149; --color-tag-bg: rgba(139, 148, 158, 0.1); --color-tag-text: #8b949e; + --color-active-bg: rgba(57, 208, 186, 0.15); + --color-active-text: #39d0ba; color-scheme: dark; } @@ -118,6 +120,8 @@ --color-overdue-text: #be123c; --color-tag-bg: #f5f5f4; --color-tag-text: #78716c; + --color-active-bg: rgba(99, 102, 241, 0.12); + --color-active-text: #4f46e5; color-scheme: light; } @@ -168,6 +172,8 @@ --color-overdue-text: #ef4444; --color-tag-bg: rgba(148, 163, 184, 0.1); --color-tag-text: #94a3b8; + --color-active-bg: rgba(139, 92, 246, 0.15); + --color-active-text: #8b5cf6; color-scheme: dark; } diff --git a/opal-web/src/lib/components/BottomSheet.svelte b/opal-web/src/lib/components/BottomSheet.svelte new file mode 100644 index 0000000..7731871 --- /dev/null +++ b/opal-web/src/lib/components/BottomSheet.svelte @@ -0,0 +1,248 @@ + + + +
+ + +
+ + diff --git a/opal-web/src/lib/components/ConfirmDialog.svelte b/opal-web/src/lib/components/ConfirmDialog.svelte new file mode 100644 index 0000000..b498c53 --- /dev/null +++ b/opal-web/src/lib/components/ConfirmDialog.svelte @@ -0,0 +1,178 @@ + + + + +
+

{title}

+ +

"{message}"

+ + {#if detail} +

{detail}

+ {/if} + +
+ + +
+
+
+ + diff --git a/opal-web/src/lib/components/Toast.svelte b/opal-web/src/lib/components/Toast.svelte new file mode 100644 index 0000000..d19dd93 --- /dev/null +++ b/opal-web/src/lib/components/Toast.svelte @@ -0,0 +1,105 @@ + + +
+ {message} + {#if action} + + {/if} +
+ + diff --git a/opal-web/src/lib/stores/tasks.js b/opal-web/src/lib/stores/tasks.js index 894ae3c..e26684d 100644 --- a/opal-web/src/lib/stores/tasks.js +++ b/opal-web/src/lib/stores/tasks.js @@ -140,6 +140,55 @@ function createTasksStore() { } }, + /** + * Start task timer (optimistic) + * @param {string} uuid + */ + async startTask(uuid) { + const now = Math.floor(Date.now() / 1000); + update(tasks => tasks.map(t => + t.uuid === uuid ? { ...t, start: now } : t + )); + try { + const updated = await tasksAPI.start(uuid); + update(tasks => tasks.map(t => + t.uuid === uuid ? updated : t + )); + } catch (error) { + update(tasks => tasks.map(t => + t.uuid === uuid ? { ...t, start: null } : t + )); + throw error; + } + }, + + /** + * Stop task timer (optimistic) + * @param {string} uuid + */ + async stopTask(uuid) { + /** @type {number|null} */ + let prevStart = null; + update(tasks => tasks.map(t => { + if (t.uuid === uuid) { + prevStart = t.start; + return { ...t, start: null }; + } + return t; + })); + try { + const updated = await tasksAPI.stop(uuid); + update(tasks => tasks.map(t => + t.uuid === uuid ? updated : t + )); + } catch (error) { + update(tasks => tasks.map(t => + t.uuid === uuid ? { ...t, start: prevStart } : t + )); + throw error; + } + }, + /** * Complete task * @param {string} uuid