diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..19bf2e1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "npm.packageManager": "bun" +} diff --git a/components/db.ts b/components/db.ts index d92632f..da7157d 100644 --- a/components/db.ts +++ b/components/db.ts @@ -17,7 +17,7 @@ export interface Note { export interface StarRole { id: string - icon?: Icon + icon: Icon title: string } diff --git a/components/demo/Journey/index.tsx b/components/demo/Journey/index.tsx index d099a14..19302e1 100644 --- a/components/demo/Journey/index.tsx +++ b/components/demo/Journey/index.tsx @@ -1,6 +1,6 @@ import { createRef, useEffect, useLayoutEffect, useRef, useState } from 'react' import Starship from '../../common/Starship' -import Todos from '../../todos' +import { Todos } from '../../todos' import { Todo } from '../../todos/interfaces' import { useWindowSize } from '../../common/useWindowResize' import { TodoPosition } from '../../todos/TodoContext' diff --git a/components/landingPage/AppDemo.tsx b/components/landingPage/AppDemo.tsx index 83329cc..79ebf41 100644 --- a/components/landingPage/AppDemo.tsx +++ b/components/landingPage/AppDemo.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react' import Log from '../../pages/docs/log' -import Todos from '../todos' +import { Todos } from '../todos' import Events from '../todos/Events' export default function AppDemo() { diff --git a/components/pages/Home.tsx b/components/pages/Home.tsx index a6f030c..33449db 100644 --- a/components/pages/Home.tsx +++ b/components/pages/Home.tsx @@ -2,9 +2,6 @@ import { menuController } from '@ionic/core/components' import { IonButton, IonButtons, - IonCard, - IonCardHeader, - IonCardTitle, IonCheckbox, IonCol, IonContent, @@ -34,10 +31,6 @@ import { IonToast, IonToggle, IonToolbar, - useIonViewDidEnter, - useIonViewDidLeave, - useIonViewWillEnter, - useIonViewWillLeave, } from '@ionic/react' import { useLiveQuery } from 'dexie-react-hooks' import { @@ -52,24 +45,24 @@ import { RefObject, useCallback, useEffect, - useLayoutEffect, useMemo, useRef, useState, } from 'react' import { Header } from '../common/Header' +import Starship from '../common/Starship' +import order, { calculateReorderIndices, starMudder } from '../common/order' import { db, Todo } from '../db' +import { useStarshipYPosition } from '../demo/Journey' +import Tracjectory from '../landingPage/Journey/Trajectory' import NoteProviders from '../notes/providers' import useSettings from '../settings/useSettings' import { getIonIcon } from '../starRoles/icons' -import useTodoContext, { TodoContextProvider } from '../todos/TodoContext' import { useTodoActionSheet } from '../todos/TodoActionSheet' +import useTodoContext, { TodoContextProvider } from '../todos/TodoContext' import { useCreateTodoModal } from '../todos/create/useCreateTodoModal' import useView, { ViewProvider } from '../view' -import order, { calculateReorderIndices, starMudder } from '../common/order' -import Tracjectory from '../landingPage/Journey/Trajectory' -import Starship from '../common/Starship' -import { useStarshipYPosition } from '../demo/Journey' +import { TodoCard, TodoListItem } from '../todos' const Home = () => { useGlobalKeyboardShortcuts() @@ -211,13 +204,17 @@ export const TodoLists = ({}: {}) => { .limit(iceboxLimit) .toArray() - return Promise.all([ + const todos = await Promise.all([ logTodosPromise, wayfinderTodosPromise, iceboxTodosPromise, ]) + return { + log: todos[0], + wayfinder: todos[1], + icebox: todos[2], + } }, [inActiveStarRoles, iceboxLimit, logLimit, query]) - console.log(logLimit) const loading = todos === undefined @@ -232,7 +229,10 @@ export const TodoLists = ({}: {}) => { } }, []) const todosCount = useMemo( - () => todos?.reduce((acc, todos) => acc + todos.length, 0), + () => + todos === undefined + ? 0 + : Object.values(todos).reduce((acc, todos) => acc + todos.length, 0), [todos], ) @@ -279,7 +279,7 @@ export const TodoLists = ({}: {}) => { size="auto" sizeLg="2" > - + { className="mr-1 ion-no-padding" id="log-and-wayfinder" > - {todos[0].sort(byDate).map(todo => ( - ( + { + onSelect={_event => { present(todo) }} - > - { - // Prevents the IonItem onClick from firing when completing a todo - event.stopPropagation() - }} - onIonChange={event => { - db.transaction( - 'rw', - db.wayfinderOrder, - db.todos, - async () => { - const wayfinderOrder = await db.wayfinderOrder - .orderBy('order') - .limit(1) - .keys() - await Promise.all([ - db.wayfinderOrder.add({ - todoId: todo.id, - order: order( - undefined, - wayfinderOrder[0]?.toString(), - ), - }), - db.todos.update(todo.id, { - completedAt: event.detail.checked - ? new Date() - : undefined, - }), - ]) - }, - ) - setLogLimit(limit => limit - 1) - }} - checked={!!todo.completedAt} - /> - {todo?.title} - + onCompletionChange={event => { + db.transaction( + 'rw', + db.wayfinderOrder, + db.todos, + async () => { + const wayfinderOrder = await db.wayfinderOrder + .orderBy('order') + .limit(1) + .keys() + await Promise.all([ + db.wayfinderOrder.add({ + todoId: todo.id, + order: order( + undefined, + wayfinderOrder[0]?.toString(), + ), + }), + db.todos.update(todo.id, { + completedAt: event.detail.checked + ? new Date() + : undefined, + }), + ]) + }, + ) + setLogLimit(limit => limit - 1) + }} + starRole={starRoles?.find( + starRole => todo.starRole === starRole.id, + )} + todo={todo} + /> ))} { }) }} > - {todos[1].map((todo, index) => ( -
( + - { - // Prevent the action sheet from opening when reordering - if (event.target['localName'] === 'ion-item') return - - present(todo, { - buttons: [ - { - text: 'Move to icebox', - data: { - action: 'icebox', - }, - handler: async () => { - db.transaction( - 'rw', - db.wayfinderOrder, - async () => { - await db.wayfinderOrder.delete(todo.id) - }, - ) - }, + onCompletionChange={async event => { + db.transaction( + 'rw', + db.wayfinderOrder, + db.todos, + async () => { + await Promise.all([ + db.wayfinderOrder.delete(todo.id), + db.todos.update(todo.id, { + completedAt: event.detail.checked + ? new Date() + : undefined, + }), + ]) + }, + ) + setLogLimit(limit => limit + 1) + }} + onSelect={event => { + // Prevent the action sheet from opening when reordering + if (event.target['localName'] === 'ion-item') return + + present(todo, { + buttons: [ + { + text: 'Move to icebox', + data: { + action: 'icebox', }, - ], - }) - }} - > - { - // Prevents the IonItem onClick from firing when completing a todo - event.stopPropagation() - }} - onIonChange={async event => { - db.transaction( - 'rw', - db.wayfinderOrder, - db.todos, - async () => { - await Promise.all([ - db.wayfinderOrder.delete(todo.id), - db.todos.update(todo.id, { - completedAt: event.detail.checked - ? new Date() - : undefined, - }), - ]) + handler: async () => { + db.transaction( + 'rw', + db.wayfinderOrder, + async () => { + await db.wayfinderOrder.delete(todo.id) + }, + ) }, - ) - setLogLimit(limit => limit + 1) - }} - /> - {todo?.title} - {debug && ( - - {todo.id} - - {todo.order} - - - )} - {todo.starRole && ( - starRole.id === todo.starRole, - )?.icon?.name, - )} - slot="end" - /> - )} - {todo.note && ( - - - - )} - - -
+ }, + ], + }) + }} + ref={index === 0 ? (nextTodoRef as any) : undefined} + starRole={starRoles?.find( + starRole => todo.starRole === starRole.id, + )} + todo={{ ...todo, order: index }} + > + + ))}
- + { {todos.map(todo => ( - { onClick(todo) }} - > - - {todo.title} - - + todo={todo} + /> ))} diff --git a/components/todos/index.tsx b/components/todos/index.tsx index f6ef482..f10ab02 100644 --- a/components/todos/index.tsx +++ b/components/todos/index.tsx @@ -1,8 +1,29 @@ +import { + CheckboxChangeEventDetail, + IonCard, + IonCardHeader, + IonCardTitle, + IonCheckbox, + IonIcon, + IonItem, + IonLabel, +} from '@ionic/react' import Todo from './Todo' +import type { StarRole, Todo as TodoType } from '../db' import { Todo as TodoInterface } from './interfaces' -import { RefObject, useMemo } from 'react' +import { + ComponentProps, + forwardRef, + MouseEventHandler, + PropsWithChildren, + RefObject, + useMemo, +} from 'react' +import { useDebug } from '../useDebug' +import { getIonIcon } from '../starRoles/icons' +import { documentText, rocketSharp } from 'ionicons/icons' -export default function Todos({ +export function Todos({ currentTodoRef, listRef, todos, @@ -49,3 +70,82 @@ export default function Todos({ ) } + +export const TodoListItem = forwardRef< + HTMLDivElement, + PropsWithChildren< + { + starRole?: StarRole + todo: TodoType & { order?: number } + onCompletionChange: ComponentProps['onIonChange'] + onSelect: MouseEventHandler + } & JSX.IntrinsicElements['div'] + > +>(function TodoListItem( + { children, starRole, todo, onCompletionChange, onSelect, ...props }, + ref, +) { + const [debug] = useDebug() + + return ( +
+ + { + // Prevents the IonItem onClick from firing when completing a todo + event.stopPropagation() + }} + onIonChange={onCompletionChange} + /> + {todo?.title} + {debug && ( + + {todo.id} + {todo.order} + + )} + + {todo.note && ( + + + + )} + {children} + +
+ ) +}) + +export function TodoCard({ + todo, + ...props +}: { todo: TodoType } & ComponentProps) { + return ( + + + {todo.title} + + + ) +} diff --git a/components/useDebug.ts b/components/useDebug.ts new file mode 100644 index 0000000..a8ec77c --- /dev/null +++ b/components/useDebug.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from 'react' + +export function useDebug() { + const [debug, setDebug] = useState('') + useEffect(() => { + const params = new URLSearchParams(window.location.search) + const searchQuery = params.get('debug') + if (searchQuery) { + setDebug(searchQuery) + } + }, []) + return [debug, setDebug] +}