From 2ce48df6f6f3ea1b6acd530bbb335ee3d577ca0b Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Sun, 21 Jan 2024 11:31:15 -0600 Subject: [PATCH] No persistence, but we have routine stuff working --- common.ts | 59 +++++++++++++++++++++++++++++ db.ts | 27 +++++++++++++ fresh.gen.ts | 4 ++ islands/Dashboard.tsx | 60 +---------------------------- islands/Routine.tsx | 81 +++++++++++++++++++++++++++++++++++++++ models.ts | 84 +++++++++++++++++------------------------ routes/_404.tsx | 2 +- routes/_app.tsx | 2 +- routes/admin.tsx | 2 +- routes/api/todo.ts | 3 +- routes/api/todo/done.ts | 3 +- routes/api/user.ts | 3 +- routes/index.tsx | 3 +- routes/routine.tsx | 79 ++++++++++++++++++++++++++++++++++++++ 14 files changed, 297 insertions(+), 115 deletions(-) create mode 100644 common.ts create mode 100644 db.ts create mode 100644 islands/Routine.tsx create mode 100644 routes/routine.tsx diff --git a/common.ts b/common.ts new file mode 100644 index 0000000..a7ef6a3 --- /dev/null +++ b/common.ts @@ -0,0 +1,59 @@ +import { confetti } from 'https://esm.sh/@tsparticles/confetti@3.0.3' +import { IS_BROWSER } from '$fresh/runtime.ts' + +let fireworks: HTMLAudioElement | null +if (IS_BROWSER) { + fireworks = new Audio('/fireworks.mp3') +} + +export function excitement() { + if (IS_BROWSER) { + const count = 200, + defaults = { + origin: { y: 0.7 }, + } + + if (fireworks) fireworks.play() + + // deno-lint-ignore no-inner-declarations + function fire(particleRatio: number, opts: { + spread?: number + startVelocity?: number + decay?: number + scalar?: number + }) { + confetti( + Object.assign({}, defaults, opts, { + particleCount: Math.floor(count * particleRatio), + }), + ) + } + + fire(0.25, { + spread: 26, + startVelocity: 55, + }) + + fire(0.2, { + spread: 60, + }) + + fire(0.35, { + spread: 100, + decay: 0.91, + scalar: 0.8, + }) + + fire(0.1, { + spread: 120, + startVelocity: 25, + decay: 0.92, + scalar: 1.2, + }) + + fire(0.1, { + spread: 120, + startVelocity: 45, + }) + } +} diff --git a/db.ts b/db.ts new file mode 100644 index 0000000..58b2f3b --- /dev/null +++ b/db.ts @@ -0,0 +1,27 @@ +import { + createPentagon, + TableDefinition, +} from 'https://deno.land/x/pentagon@v0.1.5/mod.ts' +import { TaskModel, TodoModel, UserModel } from '@homeman/models.ts' + +export const kv = await Deno.openKv('homeman.db') + +export const schema: Record = { + users: { + schema: UserModel, + relations: { + assignedTodos: ['todos', [TodoModel], 'id', 'assigneeUserId'], + }, + }, + todos: { + schema: TodoModel, + relations: { + assignee: ['users', UserModel, 'assigneeUserId', 'id'], + }, + }, + tasks: { + schema: TaskModel, + }, +} + +export const db = createPentagon(kv, schema) diff --git a/fresh.gen.ts b/fresh.gen.ts index c6d4592..2105b02 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -11,11 +11,13 @@ import * as $api_todo_done from './routes/api/todo/done.ts' import * as $api_user from './routes/api/user.ts' import * as $greet_name_ from './routes/greet/[name].tsx' import * as $index from './routes/index.tsx' +import * as $routine from './routes/routine.tsx' 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 $Routine from './islands/Routine.tsx' import * as $TodoList from './islands/TodoList.tsx' import { type Manifest } from '$fresh/server.ts' @@ -30,6 +32,7 @@ const manifest = { './routes/api/user.ts': $api_user, './routes/greet/[name].tsx': $greet_name_, './routes/index.tsx': $index, + './routes/routine.tsx': $routine, }, islands: { './islands/Admin.tsx': $Admin, @@ -37,6 +40,7 @@ const manifest = { './islands/Counter.tsx': $Counter, './islands/Dashboard.tsx': $Dashboard, './islands/Nav.tsx': $Nav, + './islands/Routine.tsx': $Routine, './islands/TodoList.tsx': $TodoList, }, baseUrl: import.meta.url, diff --git a/islands/Dashboard.tsx b/islands/Dashboard.tsx index bc3bd7c..c27c1eb 100644 --- a/islands/Dashboard.tsx +++ b/islands/Dashboard.tsx @@ -7,9 +7,8 @@ import { Input } from '@homeman/components/Input.tsx' import { Avatar } from '@homeman/components/Avatar.tsx' import { Button } from '@homeman/components/Button.tsx' import { JSX } from 'preact' -import { confetti } from 'https://esm.sh/@tsparticles/confetti@3.0.3' import { useEffect } from 'preact/hooks' -import { IS_BROWSER } from '$fresh/runtime.ts' +import { excitement } from '@homeman/common.ts' interface Props { users: Record @@ -31,63 +30,6 @@ interface UserSelectButtonProps extends JSX.HTMLAttributes { user: User } -let fireworks: HTMLAudioElement | null -if (IS_BROWSER) { - fireworks = new Audio('/fireworks.mp3') -} - -function excitement() { - if (IS_BROWSER) { - const count = 200, - defaults = { - origin: { y: 0.7 }, - } - - if (fireworks) fireworks.play() - - // deno-lint-ignore no-inner-declarations - function fire(particleRatio: number, opts: { - spread?: number - startVelocity?: number - decay?: number - scalar?: number - }) { - confetti( - Object.assign({}, defaults, opts, { - particleCount: Math.floor(count * particleRatio), - }), - ) - } - - fire(0.25, { - spread: 26, - startVelocity: 55, - }) - - fire(0.2, { - spread: 60, - }) - - fire(0.35, { - spread: 100, - decay: 0.91, - scalar: 0.8, - }) - - fire(0.1, { - spread: 120, - startVelocity: 25, - decay: 0.92, - scalar: 1.2, - }) - - fire(0.1, { - spread: 120, - startVelocity: 45, - }) - } -} - function UserSelectButton( { user: { id, name, avatarUrl, color }, tabindex, ...props }: UserSelectButtonProps, diff --git a/islands/Routine.tsx b/islands/Routine.tsx new file mode 100644 index 0000000..f98c8d5 --- /dev/null +++ b/islands/Routine.tsx @@ -0,0 +1,81 @@ +import { DailyPhase, DailyPhaseModel, Task, toPhase } from '@homeman/models.ts' +import { useSignal } from '@preact/signals' +import { excitement } from '@homeman/common.ts' + +export interface Props { + tasks: Task[] +} + +interface TaskWithIndex extends Task { + index: number +} + +const phaseEmoji: Record = { + Morning: '🌄', + Midday: '🌞', + Evening: '🌆', + Bedtime: '🛌', + Night: '🌙', +} + +type TaskGroups = Record + +export function Routine( + props: Props, +) { + const tasks = useSignal(props.tasks) + const currentPhase = useSignal(toPhase()) + const taskGroups: TaskGroups = { + Morning: [], + Midday: [], + Evening: [], + Bedtime: [], + Night: [], + } + tasks.value.forEach((t, i) => taskGroups[t.phase].push({ ...t, index: i })) + return ( + <> + +
+
    + {taskGroups[currentPhase.value].map(( + { emoji, text, doneAt, index }: TaskWithIndex, + ) => ( +
  • { + tasks.value[index].doneAt = tasks.value[index].doneAt + ? null + : new Date() + tasks.value = [...tasks.value] + if (tasks.value[index].doneAt) excitement() + }} + > + {emoji ? `${emoji.trim()} ` : ''} + {text.trim()} +
  • + ))} +
+
+ + ) +} diff --git a/models.ts b/models.ts index b095e11..30d95d9 100644 --- a/models.ts +++ b/models.ts @@ -1,21 +1,4 @@ import { z } from 'https://deno.land/x/zod@v3.21.4/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' - -export const kv = await Deno.openKv('homeman.db') - -// const todos = kv.list({ prefix: ['todos'] }) -// const deleteme = [] -// for await (const u of todos) { -// deleteme.push(u.key) -// console.log(u) -// } -// for (const d of deleteme) { -// await kv.delete(d) -// } const User = z.object({ id: z.string().ulid().describe('primary'), @@ -44,37 +27,40 @@ const Todo = z.object({ export const TodoModel = Todo export type Todo = z.infer -export const schema: Record = { - users: { - schema: User, - relations: { - assignedTodos: ['todos', [Todo], 'id', 'assigneeUserId'], - }, - }, - todos: { - schema: Todo, - relations: { - assignee: ['users', User, 'assigneeUserId', 'id'], - }, - }, +const DailyPhase = z.enum([ + 'Morning', + 'Midday', + 'Evening', + 'Bedtime', + 'Night', +]) +export const DailyPhaseModel = DailyPhase +export type DailyPhase = z.infer + +export function toPhase(dt?: Date | null): z.infer { + const d = dt || new Date() + const h = d.getHours() + + if (h >= 6 && h < 12) { + return 'Morning' + } else if (h >= 12 && h < 17) { + return 'Midday' + } else if (h >= 17 && h < 19) { + return 'Evening' + } else if (h >= 19 && h < 23) { + return 'Bedtime' + } + + // if (h >= 21 || h < 6) { + return 'Night' + // } else } -export const db = createPentagon(kv, schema) - -// const daddy: User = { -// id: ulid(), -// createdAt: new Date(), -// name: 'Daddy', -// avatarUrl: null, -// } -// const todo: Todo = { -// emoji: null, -// id: ulid(), -// createdAt: new Date(), -// description: 'Test Todo', -// doneAt: null, -// assigneeUserId: daddy.id, -// } - -// db.todos.create({ data: todo }) -// db.users.create({ data: daddy }) +const Task = z.object({ + emoji: z.string().nullable(), + text: z.string(), + doneAt: z.date().nullable(), + phase: DailyPhase, +}) +export const TaskModel = Task +export type Task = z.infer diff --git a/routes/_404.tsx b/routes/_404.tsx index 61a430c..2d4df34 100644 --- a/routes/_404.tsx +++ b/routes/_404.tsx @@ -6,7 +6,7 @@ export default function Error404() { 404 - Page not found -
+
homeman-deno - +