Files
apps/tasks/tasks.js
metacryst 0d6c7683ff init
2026-04-28 20:05:00 -05:00

152 lines
6.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)