152 lines
6.8 KiB
JavaScript
152 lines
6.8 KiB
JavaScript
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) |