I'm pretending to be liveview

This commit is contained in:
Daniel Flanagan 2024-01-16 16:15:10 -06:00
parent 78bd64f100
commit d2341cdc52
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
6 changed files with 93 additions and 24 deletions

View file

@ -2,17 +2,17 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1701718080, "lastModified": 1705316053,
"narHash": "sha256-6ovz0pG76dE0P170pmmZex1wWcQoeiomUZGggfH9XPs=", "narHash": "sha256-J2Ey5mPFT8gdfL2XC0JTZvKaBw/b2pnyudEXFvl+dQM=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", "rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335",
"type": "github" "type": "github"
} }
}, },

View file

@ -1,5 +1,5 @@
{ {
inputs.nixpkgs.url = "github:NixOS/nixpkgs?rev=2c7f3c0fb7c08a0814627611d9d7d45ab6d75335"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { outputs = {
self, self,
nixpkgs, nixpkgs,

View file

@ -6,12 +6,15 @@ import { Avatar } from '@homeman/components/Avatar.tsx'
export interface Props { export interface Props {
user: UserWithTodos user: UserWithTodos
onNewButtonClicked: JSX.MouseEventHandler<HTMLButtonElement> onNewButtonClicked: JSX.MouseEventHandler<HTMLButtonElement>
onTodoDone: (id: string) => Promise<void>
} }
export function TodoList( export function TodoList(
{ onNewButtonClicked, user: { avatarUrl, assignedTodos, name, color } }: { onTodoDone, onNewButtonClicked, user: { avatarUrl, name, color, ...user } }:
Props, Props,
) { ) {
const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null)
const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null)
const todoItem = ( const todoItem = (
{ id, doneAt, className, description, hideDone }: { id, doneAt, className, description, hideDone }:
& Pick<Todo, 'description' | 'id' | 'doneAt'> & Pick<Todo, 'description' | 'id' | 'doneAt'>
@ -22,26 +25,40 @@ export function TodoList(
) => ( ) => (
<li <li
style={`border-color: #${color}`} style={`border-color: #${color}`}
title={doneAt != null ? `Completed at ${doneAt}` : ''}
class={`${className || ''} ${ class={`${className || ''} ${
hideDone ? '' : 'border-l-4' hideDone ? '' : 'border-l-4'
} p-4 rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col gap-2`} } p-4 text-black cursor-auto dark:text-white rounded drop-shadow-lg bg-white dark:bg-stone-900 flex flex-col gap-2`}
> >
{JSON.stringify(doneAt)}
<span class='text-xl'>{description}</span> <span class='text-xl'>{description}</span>
{(hideDone || doneAt != null) ? '' : ( {(hideDone || doneAt != null) ? '' : (
<Button <Button
onClick={async () => { onClick={async () => {
await fetch('/api/todo/done', { await fetch('/api/todo/done', {
method: 'POST', method: 'PUT',
body: JSON.stringify({ id }), body: JSON.stringify({ id }),
}) })
// TODO: remove the todoitem? await onTodoDone(id)
// TODO: confetti
}} }}
> >
Done Done
</Button> </Button>
)} )}
{(!hideDone && doneAt != null)
? (
<Button
onClick={async () => {
await fetch('/api/todo/done', {
method: 'DELETE',
body: JSON.stringify({ id }),
})
await onTodoDone(id)
}}
>
Not Done
</Button>
)
: ''}
</li> </li>
) )
return ( return (
@ -61,14 +78,28 @@ export function TodoList(
+ New + New
</button> </button>
<ul class='flex flex-col gap-y-4'> <ul class='flex flex-col gap-y-4'>
{assignedTodos.length < 1 {inProgressTodos.length < 1
? todoItem({ ? todoItem({
id: '',
doneAt: null,
description: 'All clear! 🎉', description: 'All clear! 🎉',
className: 'text-center', className: 'text-center',
hideDone: true, hideDone: true,
}) })
: assignedTodos.map(todoItem)} : inProgressTodos.map(todoItem)}
</ul> </ul>
{doneTodos.length > 0
? (
<summary class='text-gray-500 mt-4'>
+{doneTodos.length} completed todos
<details class='cursor-pointer'>
<ul class='flex flex-col gap-y-4 mt-4'>
{doneTodos.map(todoItem)}
</ul>
</details>
</summary>
)
: ''}
</div> </div>
) )
} }

View file

@ -1,8 +1,11 @@
import { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts' import { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts'
import { createPentagon } from 'https://deno.land/x/pentagon@v0.1.5/mod.ts' import {
createPentagon,
TableDefinition,
} from 'https://deno.land/x/pentagon@v0.1.5/mod.ts'
// import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' // import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts'
const kv = await Deno.openKv('homeman.db') export const kv = await Deno.openKv('homeman.db')
// const todos = kv.list({ prefix: ['todos'] }) // const todos = kv.list({ prefix: ['todos'] })
// const deleteme = [] // const deleteme = []
@ -41,7 +44,7 @@ const Todo = z.object({
export const TodoModel = Todo export const TodoModel = Todo
export type Todo = z.infer<typeof Todo> export type Todo = z.infer<typeof Todo>
export const db = createPentagon(kv, { export const schema: Record<string, TableDefinition> = {
users: { users: {
schema: User, schema: User,
relations: { relations: {
@ -54,7 +57,9 @@ export const db = createPentagon(kv, {
assignee: ['users', User, 'assigneeUserId', 'id'], assignee: ['users', User, 'assigneeUserId', 'id'],
}, },
}, },
}) }
export const db = createPentagon(kv, schema)
// const daddy: User = { // const daddy: User = {
// id: ulid(), // id: ulid(),

View file

@ -3,12 +3,23 @@ import { db, TodoModel } from '@homeman/models.ts'
const Model = TodoModel.pick({ id: true }) const Model = TodoModel.pick({ id: true })
export const handler: Handlers = { async function markDone(id: string) {
async POST(req, _ctx) {
const { id } = Model.parse(await req.json())
const todo = await db.todos.findFirst({ where: { id } }) const todo = await db.todos.findFirst({ where: { id } })
todo.doneAt = new Date() todo.doneAt = new Date()
const newTodo = await db.todos.update({ where: { id }, data: todo }) return await db.todos.update({ where: { id }, data: todo })
return new Response(JSON.stringify(newTodo)) }
async function markNotDone(id: string) {
return await db.todos.update({ where: { id }, data: { doneAt: null } })
}
export const handler: Handlers = {
async PUT(req, _ctx) {
const { id } = Model.parse(await req.json())
return new Response(JSON.stringify(await markDone(id)))
},
async DELETE(req, _ctx) {
const { id } = Model.parse(await req.json())
return new Response(JSON.stringify(await markNotDone(id)))
}, },
} }

View file

@ -1,12 +1,23 @@
import { Handlers, PageProps } from '$fresh/server.ts' import { Handlers, PageProps } from '$fresh/server.ts'
import { db, Todo, UserWithTodos } from '@homeman/models.ts' import { db, kv, schema, Todo, UserWithTodos } from '@homeman/models.ts'
import Dashboard from '@homeman/islands/Dashboard.tsx' import Dashboard from '@homeman/islands/Dashboard.tsx'
import { useSignal } from '@preact/signals'
import { useEffect } from 'preact/hooks'
interface Data { interface Data {
users: Record<string, UserWithTodos> users: Record<string, UserWithTodos>
unassignedTodos: Todo[] unassignedTodos: Todo[]
} }
const allTableNames = Object.keys(schema).map((s) => [s])
async function watcher() {
console.log('watching:', allTableNames)
for await (const entry of kv.watch(allTableNames)) {
console.log('entry:', entry)
}
}
watcher()
export const handler: Handlers = { export const handler: Handlers = {
async GET(_req, ctx) { async GET(_req, ctx) {
const users = Object.fromEntries( const users = Object.fromEntries(
@ -22,5 +33,16 @@ export const handler: Handlers = {
} }
export default function Home({ data }: PageProps<Data>) { export default function Home({ data }: PageProps<Data>) {
return <Dashboard {...data} /> const rdata = useSignal(data)
useEffect(() => {
async function watcher() {
console.log('watcher watching...')
for await (const entry of kv.watch(allTableNames)) {
console.log('entry:', entry)
}
}
watcher().catch(console.error)
}, [rdata])
console.log('Home rendered')
return <Dashboard {...rdata.value} />
} }