No persistence, but we have routine stuff working

This commit is contained in:
Daniel Flanagan 2024-01-21 11:31:15 -06:00
parent 074d058825
commit 2ce48df6f6
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
14 changed files with 297 additions and 115 deletions

59
common.ts Normal file
View file

@ -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,
})
}
}

27
db.ts Normal file
View file

@ -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<string, TableDefinition> = {
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)

View file

@ -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 $api_user from './routes/api/user.ts'
import * as $greet_name_ from './routes/greet/[name].tsx' import * as $greet_name_ from './routes/greet/[name].tsx'
import * as $index from './routes/index.tsx' import * as $index from './routes/index.tsx'
import * as $routine from './routes/routine.tsx'
import * as $Admin from './islands/Admin.tsx' import * as $Admin from './islands/Admin.tsx'
import * as $Clock from './islands/Clock.tsx' import * as $Clock from './islands/Clock.tsx'
import * as $Counter from './islands/Counter.tsx' import * as $Counter from './islands/Counter.tsx'
import * as $Dashboard from './islands/Dashboard.tsx' import * as $Dashboard from './islands/Dashboard.tsx'
import * as $Nav from './islands/Nav.tsx' import * as $Nav from './islands/Nav.tsx'
import * as $Routine from './islands/Routine.tsx'
import * as $TodoList from './islands/TodoList.tsx' import * as $TodoList from './islands/TodoList.tsx'
import { type Manifest } from '$fresh/server.ts' import { type Manifest } from '$fresh/server.ts'
@ -30,6 +32,7 @@ const manifest = {
'./routes/api/user.ts': $api_user, './routes/api/user.ts': $api_user,
'./routes/greet/[name].tsx': $greet_name_, './routes/greet/[name].tsx': $greet_name_,
'./routes/index.tsx': $index, './routes/index.tsx': $index,
'./routes/routine.tsx': $routine,
}, },
islands: { islands: {
'./islands/Admin.tsx': $Admin, './islands/Admin.tsx': $Admin,
@ -37,6 +40,7 @@ const manifest = {
'./islands/Counter.tsx': $Counter, './islands/Counter.tsx': $Counter,
'./islands/Dashboard.tsx': $Dashboard, './islands/Dashboard.tsx': $Dashboard,
'./islands/Nav.tsx': $Nav, './islands/Nav.tsx': $Nav,
'./islands/Routine.tsx': $Routine,
'./islands/TodoList.tsx': $TodoList, './islands/TodoList.tsx': $TodoList,
}, },
baseUrl: import.meta.url, baseUrl: import.meta.url,

View file

@ -7,9 +7,8 @@ import { Input } from '@homeman/components/Input.tsx'
import { Avatar } from '@homeman/components/Avatar.tsx' import { Avatar } from '@homeman/components/Avatar.tsx'
import { Button } from '@homeman/components/Button.tsx' import { Button } from '@homeman/components/Button.tsx'
import { JSX } from 'preact' import { JSX } from 'preact'
import { confetti } from 'https://esm.sh/@tsparticles/confetti@3.0.3'
import { useEffect } from 'preact/hooks' import { useEffect } from 'preact/hooks'
import { IS_BROWSER } from '$fresh/runtime.ts' import { excitement } from '@homeman/common.ts'
interface Props { interface Props {
users: Record<string, UserWithTodos> users: Record<string, UserWithTodos>
@ -31,63 +30,6 @@ interface UserSelectButtonProps extends JSX.HTMLAttributes<HTMLInputElement> {
user: User 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( function UserSelectButton(
{ user: { id, name, avatarUrl, color }, tabindex, ...props }: { user: { id, name, avatarUrl, color }, tabindex, ...props }:
UserSelectButtonProps, UserSelectButtonProps,

81
islands/Routine.tsx Normal file
View file

@ -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<DailyPhase, string> = {
Morning: '🌄',
Midday: '🌞',
Evening: '🌆',
Bedtime: '🛌',
Night: '🌙',
}
type TaskGroups = Record<DailyPhase, TaskWithIndex[]>
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 (
<>
<nav class='flex overflow-x-scroll'>
{DailyPhaseModel.options.map((phase) => (
<button
onClick={() => {
currentPhase.value = phase
console.log(currentPhase, phase)
}}
class={`p-4 shrink-0 grow border-b-2 text-lg ${
currentPhase.value == phase
? 'border-purple-500'
: 'border-gray-500/20 hover:border-gray-500'
}`}
>
{phaseEmoji[phase]} {phase}
</button>
))}
</nav>
<main class='flex flex-col overflow-x-scroll'>
<ul>
{taskGroups[currentPhase.value].map((
{ emoji, text, doneAt, index }: TaskWithIndex,
) => (
<li
role='button'
class={`text-lg cursor-pointer p-4 hover:bg-gray-500/20 ${
doneAt ? 'line-through opacity-30 hover:bg-gray-500' : ''
}`}
onClick={() => {
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()}
</li>
))}
</ul>
</main>
</>
)
}

View file

@ -1,21 +1,4 @@
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,
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({ const User = z.object({
id: z.string().ulid().describe('primary'), id: z.string().ulid().describe('primary'),
@ -44,37 +27,40 @@ 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 schema: Record<string, TableDefinition> = { const DailyPhase = z.enum([
users: { 'Morning',
schema: User, 'Midday',
relations: { 'Evening',
assignedTodos: ['todos', [Todo], 'id', 'assigneeUserId'], 'Bedtime',
}, 'Night',
}, ])
todos: { export const DailyPhaseModel = DailyPhase
schema: Todo, export type DailyPhase = z.infer<typeof DailyPhase>
relations: {
assignee: ['users', User, 'assigneeUserId', 'id'], export function toPhase(dt?: Date | null): z.infer<typeof DailyPhase> {
}, 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 Task = z.object({
emoji: z.string().nullable(),
// const daddy: User = { text: z.string(),
// id: ulid(), doneAt: z.date().nullable(),
// createdAt: new Date(), phase: DailyPhase,
// name: 'Daddy', })
// avatarUrl: null, export const TaskModel = Task
// } export type Task = z.infer<typeof Task>
// 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 })

View file

@ -6,7 +6,7 @@ export default function Error404() {
<Head> <Head>
<title>404 - Page not found</title> <title>404 - Page not found</title>
</Head> </Head>
<div class='px-4 py-8 mx-auto bg-[#86efac]'> <div class='px-4 py-8 mx-auto bg-rose-500/20'>
<div class='max-w-screen-md mx-auto flex flex-col items-center justify-center'> <div class='max-w-screen-md mx-auto flex flex-col items-center justify-center'>
<img <img
class='my-6' class='my-6'

View file

@ -10,7 +10,7 @@ export default function App({ Component }: PageProps) {
<title>homeman-deno</title> <title>homeman-deno</title>
<link rel='stylesheet' href='/styles.css' /> <link rel='stylesheet' href='/styles.css' />
</head> </head>
<body class='bg-stone-100 text-stone-950 dark:bg-stone-950 dark:text-stone-100 min-h-lvh'> <body class='bg-stone-100 text-stone-950 dark:bg-stone-950 dark:text-stone-100 min-h-lvh relative'>
<Nav /> <Nav />
<Component /> <Component />
</body> </body>

View file

@ -1,5 +1,5 @@
import { Handlers, PageProps } from '$fresh/server.ts' import { Handlers, PageProps } from '$fresh/server.ts'
import { db } from '@homeman/models.ts' import { db } from '@homeman/db.ts'
import { Admin, Props } from '@homeman/islands/Admin.tsx' import { Admin, Props } from '@homeman/islands/Admin.tsx'
export const handler: Handlers = { export const handler: Handlers = {

View file

@ -1,5 +1,6 @@
import { Handlers } from '$fresh/server.ts' import { Handlers } from '$fresh/server.ts'
import { db, kv, Todo, TodoModel } from '@homeman/models.ts' import { Todo, TodoModel } from '@homeman/models.ts'
import { db, kv } from '@homeman/db.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'
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'

View file

@ -1,5 +1,6 @@
import { Handlers } from '$fresh/server.ts' import { Handlers } from '$fresh/server.ts'
import { db, kv, TodoModel } from '@homeman/models.ts' import { TodoModel } from '@homeman/models.ts'
import { db, kv } from '@homeman/db.ts'
const Model = TodoModel.pick({ id: true }) const Model = TodoModel.pick({ id: true })

View file

@ -1,5 +1,6 @@
import { Handlers } from '$fresh/server.ts' import { Handlers } from '$fresh/server.ts'
import { db, kv, User, UserModel } from '@homeman/models.ts' import { User, UserModel } from '@homeman/models.ts'
import { db, kv } from '@homeman/db.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'
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'

View file

@ -1,5 +1,6 @@
import { Handlers, PageProps } from '$fresh/server.ts' import { Handlers, PageProps } from '$fresh/server.ts'
import { db, kv, Todo, UserWithTodos } from '@homeman/models.ts' import { Todo, UserWithTodos } from '@homeman/models.ts'
import { db, kv } from '@homeman/db.ts'
import Dashboard from '@homeman/islands/Dashboard.tsx' import Dashboard from '@homeman/islands/Dashboard.tsx'
interface Data { interface Data {

79
routes/routine.tsx Normal file
View file

@ -0,0 +1,79 @@
import { Handlers, PageProps } from '$fresh/server.ts'
import { Task } from '@homeman/models.ts'
import { Routine } from '@homeman/islands/Routine.tsx'
// import { db, kv, Todo, UserWithTodos } from '@homeman/models.ts'
interface Data {
tasks: Task[]
}
export const handler: Handlers = {
GET(_req, ctx) {
const tasks: Task[] = Array.from([])
console.log(tasks)
Array.prototype.forEach.apply([
['🥣', 'Breakfast'],
['🪥', 'Brush teeth'],
['👕', 'Get dressed'],
['🙏', 'Ask and thank Jesus'],
['✝️', 'Bible story or devotional'],
['📚', 'School: with Mrs. Emily or Mama'],
['🎨', 'Create time: 🧶craft ✏️ draw 🧑🏼‍🎨 paint'],
['🏗️', ' Build time: 🧱legos 🚂train tracks 🏎magna tiles'],
['👯', 'Friend time: playdate or neighbor time'],
['🚗', 'Outing: 📚library 🌳park 🥑groceries ☕ coffee shop'],
], [([emoji, text]) => {
tasks.push({
emoji,
text,
doneAt: null,
phase: 'Morning',
})
}])
return ctx.render({ tasks })
},
}
export default function Page(props: PageProps<Data>) {
console.log(props)
return <Routine tasks={props.data.tasks} />
}
/*
🌄 MORNING
🥣 Breakfast
🪥Brush teeth
👕Get dressed
🙏Ask and thank Jesus
Bible story or devotional
📚School: with Mrs. Emily or Mama
🎨Create time: 🧶craft draw 🧑🏼🎨 paint
🏗 Build time: 🧱legos 🚂train tracks 🏎magna tiles
👯Friend time: playdate or neighbor time
🚗 Outing: 📚library 🌳park 🥑groceries coffee shop
🌤 AFTERNOON
🥓Lunch
🧹Tidy time
🤫Quiet time
🏃 BIG energy time: 🚲bike 🥁 drums 🤸 silly play
👯Friend time: playdate or neighbor time
🚗Outing: 📚library 🌳park 🥑groceries
🌇 EVENING
🍽Dinner
🚗Outing
👪Family time: 🃏games 🕺dance party 🤸silly play 🏋workout
🛏 BEDTIME
🪥Brush teeth
🛁Take bath
👕Put on pajamas
🙏Ask and thank Jesus
Bible story or devotional
📕Read a story
😭 😡 😂 😟 😣 😀 😰 😁Emotions check in
*/