init
This commit is contained in:
144
tasks/desktop/tasks.js
Normal file
144
tasks/desktop/tasks.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import "../../components/AppTitle.js"
|
||||
import taskServer from "/tasks/@server/tasks.js"
|
||||
|
||||
class Tasks extends Shadow {
|
||||
tasks = []
|
||||
newTitle = ""
|
||||
|
||||
render() {
|
||||
VStack(() => {
|
||||
AppTitle("Tasks")
|
||||
.marginTop(1, em)
|
||||
.marginBottom(2, em)
|
||||
.marginLeft(7, vw)
|
||||
|
||||
VStack(() => {
|
||||
this.tasks.forEach((task) => {
|
||||
HStack(() => {
|
||||
// Circular checkbox
|
||||
VStack(() => {})
|
||||
.width(1.1, em).height(1.1, em)
|
||||
.minWidth(1.1, em)
|
||||
.borderRadius(50, pct)
|
||||
.border(task.done ? "none" : "1.5px solid var(--divider)")
|
||||
.background(task.done ? "var(--accent)" : "transparent")
|
||||
.cursor("pointer")
|
||||
.flexShrink(0)
|
||||
.onClick(async (end) => {
|
||||
if(end) {
|
||||
const updated = await taskServer.updateTaskDone(task.id, !task.done)
|
||||
console.log(updated)
|
||||
if (updated && updated.id) {
|
||||
const idx = this.tasks.findIndex(t => t.id === task.id)
|
||||
if (idx >= 0) this.tasks[idx] = updated
|
||||
this.rerender()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
p(`#${task.id}`)
|
||||
.margin(0)
|
||||
.fontSize(0.75, em)
|
||||
.color("var(--headertext)")
|
||||
.opacity(0.4)
|
||||
.flexShrink(0)
|
||||
|
||||
input("", "100%")
|
||||
.attr({ value: task.title, placeholder: "Task title" })
|
||||
.background("transparent")
|
||||
.border("none")
|
||||
.outline("none")
|
||||
.fontSize(0.9, em)
|
||||
.color(task.done ? "var(--headertext)" : "var(--text)")
|
||||
.textDecoration(task.done ? "line-through" : "none")
|
||||
.opacity(task.done ? 0.5 : 1)
|
||||
.padding(0)
|
||||
.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
|
||||
p("×")
|
||||
.margin(0)
|
||||
.fontSize(1.1, em)
|
||||
.color("var(--headertext)")
|
||||
.opacity(0.3)
|
||||
.cursor("pointer")
|
||||
.flexShrink(0)
|
||||
.onHover(function (hovering) {
|
||||
this.style.opacity = hovering ? "1" : "0.3"
|
||||
this.style.color = hovering ? "var(--quillred)" : "var(--headertext)"
|
||||
})
|
||||
.onClick(async () => {
|
||||
await taskServer.deleteTask(task.id)
|
||||
this.tasks = this.tasks.filter(t => t.id !== task.id)
|
||||
this.rerender()
|
||||
})
|
||||
})
|
||||
.gap(0.65, em)
|
||||
.alignItems("center")
|
||||
.paddingVertical(0.4, em)
|
||||
.paddingHorizontal(0.5, em)
|
||||
.marginRight(0)
|
||||
})
|
||||
})
|
||||
.width(70, pct)
|
||||
.marginLeft(7, vw)
|
||||
.border("1px solid var(--divider)")
|
||||
.borderRadius(8, px)
|
||||
.overflow("hidden")
|
||||
|
||||
// Add task input
|
||||
input("", "70%")
|
||||
.attr({ placeholder: "New task..." })
|
||||
.marginLeft(7, vw)
|
||||
.marginTop(0.5, em)
|
||||
.padding(0.5, em)
|
||||
.paddingHorizontal(0.75, em)
|
||||
.background("var(--darkaccent)")
|
||||
.border("1px solid var(--divider)")
|
||||
.borderRadius(8, px)
|
||||
.outline("none")
|
||||
.fontSize(0.9, em)
|
||||
.boxSizing("border-box")
|
||||
.color("var(--text)")
|
||||
.onKeyDown(async (e) => {
|
||||
if (e.key === "Enter") {
|
||||
console.log("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.error) console.error("Error making task: ", task.error)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.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)
|
||||
.paddingTop(2, pct)
|
||||
.paddingBottom(4, pct)
|
||||
.width(80, vw)
|
||||
.height(100, pct)
|
||||
.overflow("auto")
|
||||
}
|
||||
}
|
||||
|
||||
register(Tasks)
|
||||
4
tasks/icons/tasks.svg
Normal file
4
tasks/icons/tasks.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="125" height="131" viewBox="0 0 125 131" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M123.931 0.6264C122.635 -0.348 120.853 -0.1856 119.881 1.1136L60.4266 76.6296L30.7806 49.6712C29.3226 48.372 27.2166 48.2096 25.5966 49.3464C23.6526 50.6456 23.1666 53.244 24.4626 55.1928L53.6226 99.0408C54.4326 100.178 55.5666 101.314 56.8626 101.964C61.5606 104.725 67.3926 103.101 70.1466 98.3912L70.4706 97.904L124.579 4.3616C125.227 3.0624 125.065 1.4384 123.931 0.6264Z" fill="black"/>
|
||||
<path d="M107.532 109.271C107.532 114.955 100.926 121.736 95.256 121.736H20.736C15.066 121.736 7.53223 114.793 7.53223 109.109V34.7296C7.53223 29.0456 15.066 21.7363 20.736 21.7363H87.5322L96.39 13.9424C96.066 13.9424 95.58 13.9424 95.256 13.9424H20.736C9.396 13.9424 0 23.1992 0 34.7296V109.271C0 120.639 9.234 130.058 20.736 130.058H95.094C106.434 130.058 115.83 120.802 115.83 109.271V40.2512L107.532 58.7363V109.271Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 945 B |
4
tasks/icons/taskslight.svg
Normal file
4
tasks/icons/taskslight.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="125" height="131" viewBox="0 0 125 131" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M123.931 0.6264C122.635 -0.348 120.853 -0.1856 119.881 1.1136L60.4266 76.6296L30.7806 49.6712C29.3226 48.372 27.2166 48.2096 25.5966 49.3464C23.6526 50.6456 23.1666 53.244 24.4626 55.1928L53.6226 99.0408C54.4326 100.178 55.5666 101.314 56.8626 101.964C61.5606 104.725 67.3926 103.101 70.1466 98.3912L70.4706 97.904L124.579 4.3616C125.227 3.0624 125.065 1.4384 123.931 0.6264Z" fill="#FFE9C8"/>
|
||||
<path d="M107.532 109.271C107.532 114.955 100.926 121.736 95.256 121.736H20.736C15.066 121.736 7.53223 114.793 7.53223 109.109V34.7296C7.53223 29.0456 15.066 21.7363 20.736 21.7363H87.5322L96.39 13.9424C96.066 13.9424 95.58 13.9424 95.256 13.9424H20.736C9.396 13.9424 0 23.1992 0 34.7296V109.271C0 120.639 9.234 130.058 20.736 130.058H95.094C106.434 130.058 115.83 120.802 115.83 109.271V40.2512L107.532 58.7363V109.271Z" fill="#FFE9C8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 949 B |
47
tasks/server/functions.js
Normal file
47
tasks/server/functions.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export async function getTasks(networkId) {
|
||||
const tasks = await this.sql`
|
||||
SELECT * FROM tasks.tasks
|
||||
WHERE network_id = ${networkId}
|
||||
AND is_active = true
|
||||
ORDER BY created ASC
|
||||
`;
|
||||
return tasks
|
||||
}
|
||||
|
||||
export async function addTask(networkId, title) {
|
||||
const [task] = await this.sql`
|
||||
INSERT INTO tasks.tasks (title, network_id)
|
||||
VALUES (${title}, ${networkId})
|
||||
RETURNING *
|
||||
`;
|
||||
return task
|
||||
}
|
||||
|
||||
export async function updateTaskDone(taskId, done) {
|
||||
const [task] = await this.sql`
|
||||
UPDATE tasks.tasks
|
||||
SET done = ${done}
|
||||
WHERE id = ${taskId}
|
||||
RETURNING *
|
||||
`;
|
||||
return task
|
||||
}
|
||||
|
||||
export async function editTaskTitle(taskId, title) {
|
||||
const [task] = await this.sql`
|
||||
UPDATE tasks.tasks
|
||||
SET title = ${title}
|
||||
WHERE id = ${taskId}
|
||||
RETURNING *
|
||||
`;
|
||||
return task
|
||||
}
|
||||
|
||||
export async function deleteTask(taskId) {
|
||||
await this.sql`
|
||||
UPDATE tasks.tasks
|
||||
SET is_active = false
|
||||
WHERE id = ${taskId}
|
||||
`;
|
||||
return { ok: true }
|
||||
}
|
||||
152
tasks/tasks.js
Normal file
152
tasks/tasks.js
Normal file
@@ -0,0 +1,152 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user