Merge pull request #7 from SteelDynamite/gui-window-and-task-input

Gui window and task input
This commit is contained in:
SteelDynamite 2026-03-30 10:11:28 -07:00 committed by GitHub
commit 56e3021281
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 105 additions and 54 deletions

View file

@ -20,7 +20,7 @@
"minHeight": 500, "minHeight": 500,
"resizable": true, "resizable": true,
"decorations": false, "decorations": false,
"transparent": true "transparent": false
} }
], ],
"security": { "security": {

View file

@ -11,10 +11,10 @@
</script> </script>
<div class={app.darkMode ? "dark" : ""}> <div class={app.darkMode ? "dark" : ""}>
<div class="h-screen w-screen p-2"> <div class="h-screen w-screen">
<div <div
class="relative h-full w-full overflow-hidden rounded-xl border border-black/15 bg-surface-light text-text-light dark:border-white/15 dark:bg-surface-dark dark:text-text-dark" class="relative h-full w-full overflow-hidden bg-surface-light text-text-light dark:bg-surface-dark dark:text-text-dark"
style="container-type: inline-size; box-shadow: 0 2px 8px rgba(0,0,0,0.25), 0 0 2px rgba(0,0,0,0.1)" style="container-type: inline-size"
> >
{#if app.error} {#if app.error}
<div <div

View file

@ -24,16 +24,11 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
html {
background: transparent;
}
body { body {
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
background: transparent;
} }
/* Scrollbar styling */ /* Scrollbar styling */

View file

@ -5,16 +5,24 @@
<script lang="ts"> <script lang="ts">
import { app } from "../stores/app.svelte"; import { app } from "../stores/app.svelte";
import DateTimePicker from "./DateTimePicker.svelte";
let title = $state(""); let title = $state("");
let description = $state(""); let description = $state("");
let dueDate = $state<string | null>(null);
let inputEl = $state<HTMLInputElement | null>(null); let inputEl = $state<HTMLInputElement | null>(null);
let showDatePicker = $state(false);
async function handleSubmit() { async function handleSubmit() {
if (!title.trim()) return; if (!title.trim()) return;
await app.createTask(title.trim(), description.trim() || undefined); await app.createTask(title.trim(), description.trim() || undefined);
if (dueDate && app.tasks.length > 0) {
const created = app.tasks[app.tasks.length - 1];
await app.updateTask({ ...created, due_date: dueDate, updated_at: new Date().toISOString() });
}
title = ""; title = "";
description = ""; description = "";
dueDate = null;
newTaskState.open = false; newTaskState.open = false;
} }
@ -22,6 +30,24 @@
newTaskState.open = false; newTaskState.open = false;
title = ""; title = "";
description = ""; description = "";
dueDate = null;
showDatePicker = false;
}
function handleDateChange(iso: string | null) {
dueDate = iso;
}
function formatDateChip(iso: string): string {
const d = new Date(iso);
const today = new Date();
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const day = dayNames[d.getDay()];
const pad = (n: number) => String(n).padStart(2, "0");
const hasTime = d.getHours() !== 0 || d.getMinutes() !== 0;
const timePart = hasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
} }
$effect(() => { $effect(() => {
@ -31,51 +57,90 @@
}); });
</script> </script>
<!-- Backdrop --> <!-- Backdrop + sheet wrapper -->
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div <div
class="absolute inset-0 z-40 transition-opacity duration-250 ease-out {newTaskState.open ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}" class="fixed inset-0 z-50 flex flex-col justify-end transition-opacity duration-250 ease-out {newTaskState.open ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}"
style="background: rgba(0,0,0,0.4)" style="background: rgba(0,0,0,0.4)"
onclick={handleClose} onclick={handleClose}
onkeydown={(e) => { if (e.key === "Escape") handleClose(); }} onkeydown={(e) => { if (e.key === "Escape") handleClose(); }}
></div>
<!-- Toast input sheet -->
<div
class="pointer-events-auto absolute bottom-0 left-0 right-0 z-50 rounded-t-2xl bg-surface-light shadow-xl transition-all duration-250 ease-out dark:bg-card-dark {newTaskState.open ? 'translate-y-0 opacity-100' : 'translate-y-full opacity-0 pointer-events-none'}"
> >
<div class="px-4 pb-4 pt-3"> <!-- svelte-ignore a11y_no_static_element_interactions -->
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}> <div
class="rounded-t-2xl bg-surface-light shadow-xl transition-transform duration-250 ease-out dark:bg-card-dark {newTaskState.open ? 'translate-y-0' : 'translate-y-full'}"
onclick={(e) => e.stopPropagation()}
>
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="px-4 pt-4">
<!-- Title -->
<input <input
bind:this={inputEl} bind:this={inputEl}
type="text" type="text"
bind:value={title} bind:value={title}
placeholder="New task" placeholder="Task title"
class="w-full border-none bg-transparent text-base font-medium outline-none placeholder:opacity-40" class="w-full bg-transparent text-xl font-bold outline-none placeholder:opacity-30"
onkeydown={(e) => { if (e.key === "Escape") handleClose(); }} onkeydown={(e) => { if (e.key === "Escape") handleClose(); }}
/> />
<input
type="text" <!-- Description -->
<div class="mt-4 flex items-start gap-3">
<svg class="mt-0.5 h-5 w-5 shrink-0 opacity-40" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 4a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zm0 4a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zm0 4a1 1 0 011-1h7a1 1 0 110 2H5a1 1 0 01-1-1z" />
</svg>
<textarea
bind:value={description} bind:value={description}
placeholder="Add details" placeholder="Add details"
class="mt-2 w-full border-none bg-transparent text-sm outline-none placeholder:opacity-40" rows="3"
class="w-full flex-1 resize-none bg-transparent text-sm outline-none placeholder:opacity-40"
onkeydown={(e) => { if (e.key === "Escape") handleClose(); }} onkeydown={(e) => { if (e.key === "Escape") handleClose(); }}
/> ></textarea>
</form> </div>
<div class="mt-3 flex items-center justify-between"> <!-- Date/time -->
<button class="opacity-40 hover:opacity-70" title="Set due date"> <div class="mt-4 flex items-center gap-3">
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-5 w-5 shrink-0 opacity-40" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />
</svg> </svg>
{#if dueDate}
<div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10">
<button type="button" onclick={() => (showDatePicker = true)} class="hover:opacity-70">
{formatDateChip(dueDate)}
</button> </button>
<button type="button" onclick={() => (dueDate = null)} class="opacity-40 hover:opacity-80">
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
</svg>
</button>
</div>
{:else}
<button
type="button"
onclick={() => (showDatePicker = true)}
class="text-sm opacity-40 hover:opacity-70"
>
Add date/time
</button>
{/if}
</div>
</form>
<!-- Save button -->
<div class="border-t border-border-light px-4 py-3 mt-4 dark:border-border-dark">
<button <button
onclick={handleSubmit} onclick={handleSubmit}
disabled={!title.trim()} disabled={!title.trim()}
class="text-sm font-medium text-primary disabled:opacity-30" class="w-full text-center text-sm font-medium text-primary hover:opacity-70 disabled:opacity-30"
> >
Save Save
</button> </button>
</div> </div>
</div>
<!-- Date picker overlay -->
{#if showDatePicker}
<DateTimePicker
value={dueDate}
onchange={handleDateChange}
onclose={() => (showDatePicker = false)}
/>
{/if}
</div>
</div> </div>

View file

@ -334,7 +334,6 @@
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<header <header
onmousedown={handleHeaderMouseDown} onmousedown={handleHeaderMouseDown}
ondblclick={() => { if (isDesktop) appWindow.toggleMaximize(); }}
class="relative flex items-center border-b border-border-light px-4 py-3 dark:border-border-dark" class="relative flex items-center border-b border-border-light px-4 py-3 dark:border-border-dark"
> >
<!-- Drawer toggle (left) --> <!-- Drawer toggle (left) -->
@ -367,14 +366,6 @@
<path d="M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z" /> <path d="M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z" />
</svg> </svg>
</button> </button>
<button
onclick={() => appWindow.toggleMaximize()}
class="rounded p-1.5 opacity-50 hover:bg-black/10 hover:opacity-80 dark:hover:bg-white/10"
>
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="14" height="14" rx="1" />
</svg>
</button>
{/if} {/if}
<button <button
onclick={() => appWindow.close()} onclick={() => appWindow.close()}