From d2341cdc52fdb1e5eff37ae85220f5220c411852 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Tue, 16 Jan 2024 16:15:10 -0600 Subject: [PATCH 1/2] I'm pretending to be liveview --- flake.lock | 8 +++---- flake.nix | 2 +- islands/TodoList.tsx | 47 ++++++++++++++++++++++++++++++++++------- models.ts | 13 ++++++++---- routes/api/todo/done.ts | 21 +++++++++++++----- routes/index.tsx | 26 +++++++++++++++++++++-- 6 files changed, 93 insertions(+), 24 deletions(-) diff --git a/flake.lock b/flake.lock index 1d18f97..115cd7d 100644 --- a/flake.lock +++ b/flake.lock @@ -2,17 +2,17 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1701718080, - "narHash": "sha256-6ovz0pG76dE0P170pmmZex1wWcQoeiomUZGggfH9XPs=", + "lastModified": 1705316053, + "narHash": "sha256-J2Ey5mPFT8gdfL2XC0JTZvKaBw/b2pnyudEXFvl+dQM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", + "rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", - "rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", "type": "github" } }, diff --git a/flake.nix b/flake.nix index a1a2c15..e0e76b7 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - inputs.nixpkgs.url = "github:NixOS/nixpkgs?rev=2c7f3c0fb7c08a0814627611d9d7d45ab6d75335"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; outputs = { self, nixpkgs, diff --git a/islands/TodoList.tsx b/islands/TodoList.tsx index 875e92f..3612a69 100644 --- a/islands/TodoList.tsx +++ b/islands/TodoList.tsx @@ -6,12 +6,15 @@ import { Avatar } from '@homeman/components/Avatar.tsx' export interface Props { user: UserWithTodos onNewButtonClicked: JSX.MouseEventHandler + onTodoDone: (id: string) => Promise } export function TodoList( - { onNewButtonClicked, user: { avatarUrl, assignedTodos, name, color } }: + { onTodoDone, onNewButtonClicked, user: { avatarUrl, name, color, ...user } }: Props, ) { + const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null) + const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null) const todoItem = ( { id, doneAt, className, description, hideDone }: & Pick @@ -22,26 +25,40 @@ export function TodoList( ) => (
  • - {JSON.stringify(doneAt)} {description} {(hideDone || doneAt != null) ? '' : ( )} + {(!hideDone && doneAt != null) + ? ( + + ) + : ''}
  • ) return ( @@ -61,14 +78,28 @@ export function TodoList( + New
      - {assignedTodos.length < 1 + {inProgressTodos.length < 1 ? todoItem({ + id: '', + doneAt: null, description: 'All clear! 🎉', className: 'text-center', hideDone: true, }) - : assignedTodos.map(todoItem)} + : inProgressTodos.map(todoItem)}
    + {doneTodos.length > 0 + ? ( + + +{doneTodos.length} completed todos +
    +
      + {doneTodos.map(todoItem)} +
    +
    +
    + ) + : ''} ) } diff --git a/models.ts b/models.ts index 0204955..b095e11 100644 --- a/models.ts +++ b/models.ts @@ -1,8 +1,11 @@ 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' -const kv = await Deno.openKv('homeman.db') +export const kv = await Deno.openKv('homeman.db') // const todos = kv.list({ prefix: ['todos'] }) // const deleteme = [] @@ -41,7 +44,7 @@ const Todo = z.object({ export const TodoModel = Todo export type Todo = z.infer -export const db = createPentagon(kv, { +export const schema: Record = { users: { schema: User, relations: { @@ -54,7 +57,9 @@ export const db = createPentagon(kv, { assignee: ['users', User, 'assigneeUserId', 'id'], }, }, -}) +} + +export const db = createPentagon(kv, schema) // const daddy: User = { // id: ulid(), diff --git a/routes/api/todo/done.ts b/routes/api/todo/done.ts index f0c200e..45df6c7 100644 --- a/routes/api/todo/done.ts +++ b/routes/api/todo/done.ts @@ -3,12 +3,23 @@ import { db, TodoModel } from '@homeman/models.ts' const Model = TodoModel.pick({ id: true }) +async function markDone(id: string) { + const todo = await db.todos.findFirst({ where: { id } }) + todo.doneAt = new Date() + return await db.todos.update({ where: { id }, data: todo }) +} + +async function markNotDone(id: string) { + return await db.todos.update({ where: { id }, data: { doneAt: null } }) +} + export const handler: Handlers = { - async POST(req, _ctx) { + async PUT(req, _ctx) { const { id } = Model.parse(await req.json()) - const todo = await db.todos.findFirst({ where: { id } }) - todo.doneAt = new Date() - const newTodo = await db.todos.update({ where: { id }, data: todo }) - return new Response(JSON.stringify(newTodo)) + 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))) }, } diff --git a/routes/index.tsx b/routes/index.tsx index c69798b..ade88c6 100644 --- a/routes/index.tsx +++ b/routes/index.tsx @@ -1,12 +1,23 @@ 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 { useSignal } from '@preact/signals' +import { useEffect } from 'preact/hooks' interface Data { users: Record 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 = { async GET(_req, ctx) { const users = Object.fromEntries( @@ -22,5 +33,16 @@ export const handler: Handlers = { } export default function Home({ data }: PageProps) { - return + 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 } From bdb444328b00beb85e8ab722478b6ab3a719df7f Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Tue, 16 Jan 2024 16:45:51 -0600 Subject: [PATCH 2/2] Crudded up, WIP menu for routine --- components/Nav.tsx | 19 ------------- deno.json | 26 ++++------------- fresh.gen.ts | 2 ++ islands/Admin.tsx | 35 ++++++++++++++++++++--- islands/Nav.tsx | 37 ++++++++++++++++++++++++ islands/TodoList.tsx | 67 +++++++++++++++++++++++++++++--------------- routes/_app.tsx | 2 +- routes/admin.tsx | 4 ++- routes/api/todo.ts | 1 + 9 files changed, 124 insertions(+), 69 deletions(-) delete mode 100644 components/Nav.tsx create mode 100644 islands/Nav.tsx diff --git a/components/Nav.tsx b/components/Nav.tsx deleted file mode 100644 index 06b0ca8..0000000 --- a/components/Nav.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// import { JSX } from 'preact' -// import { IS_BROWSER } from '$fresh/runtime.ts' - -import { Bars3Outline } from 'preact-heroicons' -import { Clock } from '@homeman/islands/Clock.tsx' - -export function Nav(/* props: {} */) { - return ( - - ) -} diff --git a/deno.json b/deno.json index 4fe499c..8e0d808 100644 --- a/deno.json +++ b/deno.json @@ -9,19 +9,10 @@ "preview": "deno run -A main.ts", "update": "deno run -A -r https://fresh.deno.dev/update ." }, - "lint": { - "rules": { - "tags": [ - "fresh", - "recommended" - ] - } - }, - "exclude": [ - "**/_fresh/*" - ], + "lint": { "rules": { "tags": ["fresh", "recommended"] } }, + "exclude": ["**/_fresh/*"], "imports": { - "$fresh/": "https://deno.land/x/fresh@1.6.1/", + "$fresh/": "https://deno.land/x/fresh@1.6.3/", "preact": "https://esm.sh/preact@10.19.2", "preact/": "https://esm.sh/preact@10.19.2/", "@preact/signals": "https://esm.sh/*@preact/signals@1.2.1", @@ -33,15 +24,8 @@ "$std/": "https://deno.land/std@0.208.0/", "@homeman/": "./" }, - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "preact" - }, - "fmt": { - "useTabs": true, - "semiColons": false, - "singleQuote": true - }, + "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" }, + "fmt": { "useTabs": true, "semiColons": false, "singleQuote": true }, "unstable": ["kv"], "nodeModulesDir": true } diff --git a/fresh.gen.ts b/fresh.gen.ts index 02ed8ab..c6d4592 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -15,6 +15,7 @@ import * as $Admin from './islands/Admin.tsx' import * as $Clock from './islands/Clock.tsx' import * as $Counter from './islands/Counter.tsx' import * as $Dashboard from './islands/Dashboard.tsx' +import * as $Nav from './islands/Nav.tsx' import * as $TodoList from './islands/TodoList.tsx' import { type Manifest } from '$fresh/server.ts' @@ -35,6 +36,7 @@ const manifest = { './islands/Clock.tsx': $Clock, './islands/Counter.tsx': $Counter, './islands/Dashboard.tsx': $Dashboard, + './islands/Nav.tsx': $Nav, './islands/TodoList.tsx': $TodoList, }, baseUrl: import.meta.url, diff --git a/islands/Admin.tsx b/islands/Admin.tsx index aaa5800..5838a6b 100644 --- a/islands/Admin.tsx +++ b/islands/Admin.tsx @@ -21,12 +21,23 @@ export interface Props { } async function promptDeleteUser(id: string, name: string) { - if (confirm(`Are you sure you want to delete ${name} (${id})?`)) { + if (confirm(`Are you sure you want to delete '${name}' (${id})?`)) { await fetch(`/api/user?id=${id}`, { method: 'DELETE' }) location.reload() } } +async function promptDeleteTodo(id: string, description: string) { + if (confirm(`Are you sure you want to delete '${description}' (${id})?`)) { + await fetch(`/api/todo`, { + method: 'DELETE', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ id }), + }) + location.reload() + } +} + interface UserFormProps extends JSX.HTMLAttributes { onCancelButtonClicked: JSX.MouseEventHandler userData: User | null @@ -123,7 +134,7 @@ export function Admin({ users, todos }: Props) { + ))} diff --git a/islands/Nav.tsx b/islands/Nav.tsx new file mode 100644 index 0000000..5cf0199 --- /dev/null +++ b/islands/Nav.tsx @@ -0,0 +1,37 @@ +// import { JSX } from 'preact' +// import { IS_BROWSER } from '$fresh/runtime.ts' + +import { Bars3Outline } from 'preact-heroicons' +import { Clock } from '@homeman/islands/Clock.tsx' +import { Dialog } from '@homeman/components/Dialog.tsx' +import { useSignal } from '@preact/signals' + +export function Nav(/* props: {} */) { + const showMenu = useSignal(false) + return ( + <> + { + showMenu.value = false + }} + > + + + + ) +} diff --git a/islands/TodoList.tsx b/islands/TodoList.tsx index 3612a69..c1f21fb 100644 --- a/islands/TodoList.tsx +++ b/islands/TodoList.tsx @@ -2,6 +2,7 @@ import { Todo, UserWithTodos } from '@homeman/models.ts' import { JSX } from 'preact' import { Button } from '@homeman/components/Button.tsx' import { Avatar } from '@homeman/components/Avatar.tsx' +import { TrashSolid } from 'preact-heroicons' export interface Props { user: UserWithTodos @@ -16,49 +17,69 @@ export function TodoList( const doneTodos = user.assignedTodos.filter((t) => t.doneAt != null) const inProgressTodos = user.assignedTodos.filter((t) => t.doneAt == null) const todoItem = ( - { id, doneAt, className, description, hideDone }: + { id, doneAt, className, description, virtual }: & Pick & { className?: string - hideDone?: boolean + virtual?: boolean }, ) => (
  • {description} - {(hideDone || doneAt != null) ? '' : ( - - )} - {(!hideDone && doneAt != null) - ? ( +
    + {(virtual || doneAt != null) ? '' : ( - ) - : ''} + )} + {(!virtual && doneAt != null) + ? ( + + ) + : ''} + {virtual ? '' : ( + + )} +
  • ) return ( @@ -84,7 +105,7 @@ export function TodoList( doneAt: null, description: 'All clear! 🎉', className: 'text-center', - hideDone: true, + virtual: true, }) : inProgressTodos.map(todoItem)} diff --git a/routes/_app.tsx b/routes/_app.tsx index b41a3ec..bedd5e4 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -1,5 +1,5 @@ import { type PageProps } from '$fresh/server.ts' -import { Nav } from '@homeman/components/Nav.tsx' +import { Nav } from '@homeman/islands/Nav.tsx' export default function App({ Component }: PageProps) { return ( diff --git a/routes/admin.tsx b/routes/admin.tsx index 30ea3a3..c2387ff 100644 --- a/routes/admin.tsx +++ b/routes/admin.tsx @@ -4,7 +4,9 @@ import { Admin, Props } from '@homeman/islands/Admin.tsx' export const handler: Handlers = { async GET(_req, ctx) { - const users = await db.users.findMany({}) + const users = Object.fromEntries((await db.users.findMany({})).map( + (u) => [u.id, u], + )) const todos = await db.todos.findMany({}) return ctx.render({ users, todos }) }, diff --git a/routes/api/todo.ts b/routes/api/todo.ts index c8e52ef..55a9710 100644 --- a/routes/api/todo.ts +++ b/routes/api/todo.ts @@ -60,6 +60,7 @@ export const handler: Handlers = { } else { data = { id: new URL(req.url).searchParams.get('id') } } + console.log('delete todo data:', data) const todoData = TodoModel.pick({ id: true }).parse(data) const result = await db.todos.delete({ where: todoData }) return new Response(JSON.stringify(result))