import taskServer from "/tasks/@server/tasks.js" class Tasks extends Shadow { tasks = [] render() { VStack(() => { // Task list — full-bleed on mobile, no inset borders VStack(() => { if (this.tasks.length === 0) { p("No tasks yet") .margin(0) .padding(2, em) .textAlign("center") .color("var(--headertext)") .opacity(0.4) .fontSize(0.95, em) } else { this.tasks.forEach((task, i) => { HStack(() => { // Larger circular checkbox — 1.6em for thumb-friendly target VStack(() => {}) .width(1.6, em).height(1.6, em) .minWidth(1.6, em) .borderRadius(50, pct) .border(task.done ? "none" : "2px solid var(--divider)") .background(task.done ? "var(--accent)" : "transparent") .cursor("pointer") .flexShrink(0) .transition("all 0.15s ease") .onClick(async (end) => { if (end) { const updated = await taskServer.updateTaskDone(task.id, !task.done) if (updated && updated.id) { const idx = this.tasks.findIndex(t => t.id === task.id) if (idx >= 0) this.tasks[idx] = updated this.rerender() } } }) // Title input — takes remaining width input("", "100%") .attr({ value: task.title, placeholder: "Task title" }) .background("transparent") .border("none") .outline("none") .fontSize(1.05, em) .color(task.done ? "var(--headertext)" : "var(--text)") .textDecoration(task.done ? "line-through" : "none") .opacity(task.done ? 0.5 : 1) .padding(0) .paddingVertical(0.25, em) .onBlur(async function () { const newVal = this.value.trim() if (newVal && newVal !== task.title) { await taskServer.editTaskTitle(task.id, newVal) task.title = newVal } }) .onKeyDown(function (e) { if (e.key === "Enter") this.blur() }) // Delete button — bigger hit area on mobile VStack(() => { p("×") .margin(0) .fontSize(1.4, em) .color("var(--headertext)") .opacity(0.35) .lineHeight(1) }) .width(2.2, em).height(2.2, em) .minWidth(2.2, em) .alignItems("center") .justifyContent("center") .cursor("pointer") .flexShrink(0) .onClick(async () => { await taskServer.deleteTask(task.id) this.tasks = this.tasks.filter(t => t.id !== task.id) this.rerender() }) }) .gap(0.85, em) .alignItems("center") .paddingVertical(0.85, em) .paddingHorizontal(5, vw) .borderBottom(i < this.tasks.length - 1 ? "1px solid var(--divider)" : "none") }) } }) .width(100, pct) .borderTop("1px solid var(--divider)") .borderBottom("1px solid var(--divider)") }) .onAppear(async () => { const tasks = await taskServer.getTasks(global.currentNetwork.id) if (tasks && !tasks.error && tasks.length !== this.tasks.length) { this.tasks = tasks this.rerender() } }) .gap(0) .paddingBottom(6, em) // leave room for sticky input below .width(100, vw) .height(100, pct) .overflow("auto") // Sticky bottom add-task bar — anchored above the keyboard area, easy thumb reach HStack(() => { input("", "100%") .attr({ placeholder: "New task..." }) .padding(0.85, em) .paddingHorizontal(1, em) .background("var(--darkaccent)") .border("1px solid var(--divider)") .borderRadius(999, px) .outline("none") .fontSize(1, em) .boxSizing("border-box") .color("var(--text)") .onKeyDown(async (e) => { if (e.key === "Enter") { const val = e.target.value.trim() if (!val) return const task = await taskServer.addTask(global.currentNetwork.id, val) if (task && task.id) { this.tasks.push(task) e.target.value = "" this.rerender() } else if (task && task.error) { console.error("Error making task:", task.error) } } }) }) .position("fixed") .bottom(0, px) .left(0, px) .right(0, px) .padding(0.75, em) .paddingHorizontal(5, vw) .paddingBottom("calc(0.75em + env(safe-area-inset-bottom))") .background("var(--background)") .borderTop("1px solid var(--divider)") } } register(Tasks)