diff --git a/bun.lockb b/bun.lockb index da6873c..fbadf68 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/common/Header.tsx b/components/common/Header.tsx index 345d7c7..de79ccc 100644 --- a/components/common/Header.tsx +++ b/components/common/Header.tsx @@ -33,7 +33,7 @@ export const Header = ({ title }: { title: string }) => { {title} ( + function Starship({ className }, ref) { + return ( + A starship from a birds-eye view + ) + }, +) + +export default Starship diff --git a/components/common/cn.ts b/components/common/cn.ts new file mode 100644 index 0000000..568ec19 --- /dev/null +++ b/components/common/cn.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...args: ClassValue[]) { + return twMerge(clsx(args)) +} diff --git a/components/demo/Journey/index.tsx b/components/demo/Journey/index.tsx index c94291c..75e35a0 100644 --- a/components/demo/Journey/index.tsx +++ b/components/demo/Journey/index.tsx @@ -1,5 +1,5 @@ -import { createRef, useLayoutEffect, useRef, useState } from 'react' -import Starship from '../../landingPage/Journey/Starship' +import { createRef, useEffect, useLayoutEffect, useRef, useState } from 'react' +import Starship from '../../common/Starship' import Todos from '../../todos' import { Todo } from '../../todos/interfaces' import { useWindowSize } from '../../common/useWindowResize' @@ -9,14 +9,14 @@ export default function Journey({ todos }: { todos: Todo[] }) { const todosRef = createRef() useMaintainScrollOffset(currentTodoRef, todosRef) - const [starshipY, _] = useStarshipYPosition(currentTodoRef) + // const [starshipY, _] = useStarshipYPosition(currentTodoRef) return (
@@ -50,18 +50,41 @@ function useMaintainScrollOffset(currentTodoRef, todosRef) { }, [currentTodoRef, todosRef]) } -function useStarshipYPosition(currentTodoRef) { +export function useStarshipYPosition( + starship: HTMLElement | null, + nextTodoPosition: DOMRect | null, + commonAncestor: HTMLElement | null, +) { + console.debug('Starship position render') const size = useWindowSize() const [starshipY, setStarshipY] = useState(0) // TODO: This causes it to render a second time when a todo completion changes the scroll height. But maybe that doesn't matter and maybe we can move the starship into its own component hierarchy so it doesn't re-render other things unnecessarily. - useLayoutEffect(() => { - const elementPadding = 80 - const starshipImagePadding = 10 - setStarshipY( - currentTodoRef.current.offsetTop - elementPadding - starshipImagePadding, + useEffect(() => { + console.debug('Starship position effect') + if ( + starship === null || + nextTodoPosition === null || + nextTodoPosition.height === null || + commonAncestor === null ) - }, [currentTodoRef, size, setStarshipY]) + return setStarshipY(0) + + const commonAncestorRect = commonAncestor.getBoundingClientRect() + const todoDistanceFromCommonAncestor = + nextTodoPosition.y - commonAncestorRect.y + const starshipHeightAdjustment = + (nextTodoPosition.height - starship?.offsetHeight) / 2 + + const y = todoDistanceFromCommonAncestor + starshipHeightAdjustment + console.debug(`Setting startship Y to ${y}`, { + commonAncestorRect, + nextTodoPosition, + todoDistanceFromCommonAncestor, + starshipHeightAdjustment, + }) + setStarshipY(y) + }, [commonAncestor, nextTodoPosition, size, starship, setStarshipY]) return [starshipY, setStarshipY] } diff --git a/components/landingPage/Journey/Starship.tsx b/components/landingPage/Journey/Starship.tsx deleted file mode 100644 index 5fce502..0000000 --- a/components/landingPage/Journey/Starship.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import Image from 'next/image' - -export default function Starship() { - return ( - A starship from a birds-eye view - ) -} diff --git a/components/landingPage/Journey/Trajectory.tsx b/components/landingPage/Journey/Trajectory.tsx index 893e9a3..3cf0663 100644 --- a/components/landingPage/Journey/Trajectory.tsx +++ b/components/landingPage/Journey/Trajectory.tsx @@ -1,3 +1,14 @@ -export default function Tracjectory() { - return
+import { cn } from '../../common/cn' + +export default function Tracjectory(props: JSX.IntrinsicElements['div']) { + return ( +
+ ) } diff --git a/components/landingPage/Journey/index.tsx b/components/landingPage/Journey/index.tsx index ee834e2..4e55eff 100644 --- a/components/landingPage/Journey/index.tsx +++ b/components/landingPage/Journey/index.tsx @@ -1,4 +1,4 @@ -import Starship from './Starship' +import Starship from '../../common/Starship' import Tracjectory from './Trajectory' export default function Journey() { diff --git a/components/landingPage/Layout.tsx b/components/landingPage/Layout.tsx index 6815e85..0d61d35 100644 --- a/components/landingPage/Layout.tsx +++ b/components/landingPage/Layout.tsx @@ -4,7 +4,7 @@ import Footer from '../common/Footer' import Planets from '../common/Planets' import { ScrollDirection } from '../common/scroll' import Contact from './Contact' -import Starship from './Journey/Starship' +import Starship from '../common/Starship' import Tracjectory from './Journey/Trajectory' const Layout = ({ diff --git a/components/pages/Home.tsx b/components/pages/Home.tsx index 63d6bf0..955f4c5 100644 --- a/components/pages/Home.tsx +++ b/components/pages/Home.tsx @@ -6,6 +6,7 @@ import { IonCardHeader, IonCardTitle, IonCheckbox, + IonCol, IonContent, IonFab, IonFabButton, @@ -33,11 +34,14 @@ import { IonToast, IonToggle, IonToolbar, + useIonViewDidEnter, + useIonViewDidLeave, + useIonViewWillEnter, + useIonViewWillLeave, } from '@ionic/react' import { useLiveQuery } from 'dexie-react-hooks' import { add, - checkmarkDoneCircleSharp, documentText, filterSharp, locateOutline, @@ -45,10 +49,10 @@ import { } from 'ionicons/icons' import _ from 'lodash' import { - forwardRef, RefObject, useCallback, useEffect, + useLayoutEffect, useMemo, useRef, useState, @@ -58,11 +62,14 @@ import { db, Todo } from '../db' import NoteProviders from '../notes/providers' import useSettings from '../settings/useSettings' import { getIonIcon } from '../starRoles/icons' -import { SelectedTodoProvider } from '../todos/SelectedTodo' +import useTodoContext, { TodoContextProvider } from '../todos/TodoContext' import { useTodoActionSheet } from '../todos/TodoActionSheet' 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' const Home = () => { useGlobalKeyboardShortcuts() @@ -70,7 +77,7 @@ const Home = () => { return ( <> - + @@ -101,7 +108,7 @@ const Home = () => { - + ) @@ -113,31 +120,16 @@ export const TodoLists = ({}: {}) => { // Initial loading & scrolling stuff const contentRef = useRef(null) const [enablePagination, setEnablePagination] = useState(false) - useEffect(() => { - setTimeout(() => { - // TODO: See if ionViewDidEnter works better than setTimeout - console.debug('Scrolling to bottom', contentRef.current) - contentRef.current?.scrollToBottom(500) - setTimeout(() => { - setEnablePagination(true) - }, 500) - }, 200) - }, []) - - // Loading spinner stuff - const [ready, setReady] = useState<{ - log: boolean - wayfinder: boolean - icebox: boolean - }>({ - log: false, - wayfinder: false, - icebox: false, - }) - const isLoading = useMemo( - () => Object.values(ready).some(ready => ready === false), - [ready], - ) + // useEffect(() => { + // setTimeout(() => { + // // TODO: See if ionViewDidEnter works better than setTimeout + // console.debug('Scrolling to bottom', contentRef.current) + // contentRef.current?.scrollToBottom(500) + // setTimeout(() => { + // setEnablePagination(true) + // }, 500) + // }, 200) + // }, []) // Query stuff const [logLimit, setLogLimit] = useState(7) @@ -178,66 +170,373 @@ export const TodoLists = ({}: {}) => { } }, [contentRef, fab, openCreateTodoModal]) + const { inActiveStarRoles, query } = useView() + + const todos = useLiveQuery(async () => { + const logTodosPromise = db.todos + .orderBy('completedAt') + .reverse() + .filter( + todo => + !!todo.completedAt && + matchesQuery(query, todo) && + inActiveStarRoles(todo), + ) + .limit(logLimit) + .toArray() + + const todoOrderItems = await db.wayfinderOrder.orderBy('order').toArray() + const todoIds = todoOrderItems.map(({ todoId }) => todoId) + const wayfinderTodosPromise = db.todos.bulkGet(todoIds).then(todoIds => { + return todoIds + .map((todo, index) => ({ + ...todo!, + order: todoOrderItems[index].order, + })) + .filter( + todo => matchesQuery(query, todo) && inActiveStarRoles(todo), + ) as (Todo & { order: string })[] + }) + + const iceboxTodosPromise = db.todos + .where('id') + .noneOf(todoOrderItems.map(({ todoId }) => todoId)) + .and( + todo => + todo.completedAt === undefined && + matchesQuery(query, todo) && + inActiveStarRoles(todo), + ) + .reverse() + .limit(iceboxLimit) + .toArray() + + return Promise.all([ + logTodosPromise, + wayfinderTodosPromise, + iceboxTodosPromise, + ]) + }, [inActiveStarRoles, iceboxLimit, logLimit, query]) + + const loading = todos === undefined + + const starRoles = useLiveQuery(() => db.starRoles.toArray()) + + const [debug, setDebug] = useState('') + useEffect(() => { + const params = new URLSearchParams(window.location.search) + const searchQuery = params.get('debug') + if (searchQuery) { + setDebug(searchQuery) + } + }, []) + const todosCount = useMemo( + () => todos?.reduce((acc, todos) => acc + todos.length, 0), + [todos], + ) + + // Its possible for ref not to change when todo is completed because one other than 'next' is completed in which case starship doesn't move + // Consider using a callback ref instead: https://stackoverflow.com/questions/60881446/receive-dimensions-of-element-via-getboundingclientrect-in-react + const nextTodoRef = useRef(null) + const { + nextTodo: { + position: [_, setNextTodoPosition], + }, + } = useTodoContext() + // TODO: When dev tools aren't open the todo has zero height + // useLayoutEffect doesn't work + // setTimeout 0 doesn't work + // callbackRef doesn't work + // This person thinks its an Ionic bug but I'm not sure: https://stackoverflow.com/questions/60881446/receive-dimensions-of-element-via-getboundingclientrect-in-react + useEffect(() => { + setTimeout(() => { + if (nextTodoRef.current) { + console.debug( + 'Setting next todo with ID', + nextTodoRef.current.dataset.id, + ) + setNextTodoPosition(nextTodoRef.current.getBoundingClientRect()) // Send this rather than the current ref as if unchanged then is will be memoised and nothing will happen. + } else { + console.debug('No next todo ref') + setNextTodoPosition(null) // Send this rather than the current ref as if unchanged then is will be memoised and nothing will happen. + } + }, 1) + }, [nextTodoRef, setNextTodoPosition, todos]) // The todos dep is used as an imperfect proxy for one the position of the next todo changes + + const [present] = useTodoActionSheet() + return ( - {isLoading && ( -
- -
- )} - <> - { - setLogLimit(limit => limit + 10) - setTimeout(() => event.target.complete(), 500) - }} - > - - - setReady(state => ({ ...state, log: true }))} - /> - setReady(state => ({ ...state, wayfinder: true }))} - /> - setReady(state => ({ ...state, icebox: true }))} - /> - { - setIceboxLimit(limit => limit + 10) - setTimeout(() => event.target.complete(), 500) - }} - > - - - - - - - - + + + + + + + + + + + + {/* TODO: Use suspense instead */} + {loading ? ( +
+ +
+ ) : todosCount === 0 ? ( +
+ +

Create some todos to get started

+
+ ) : ( + <> + { + setLogLimit(limit => limit + 10) + setTimeout(() => event.target.complete(), 500) + }} + > + + + + {todos[0].sort(byDate).map(todo => ( + { + 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, + }), + ]) + }, + ) + }} + checked={!!todo.completedAt} + /> + {todo?.title} + + ))} + { + // We don't use this to reorder for us because it results in a flash of 'unordered' content. + // Instead we re-order right away, calculate the new order ourselves, and update the DB. + event.detail.complete() + + const wayfinderTodos = await db.wayfinderOrder + .orderBy('order') + .toArray() + /* If the todo moves down then all the todos after its target location must be nudged up + * If the todo moves up then all the todos + */ + // TODO: Could make this easier with IDs in the DOM + const fromTodo = todos[1][event.detail.from] + const toTodo = todos[1][event.detail.to] + const unfilteredFromIndex = wayfinderTodos.findIndex( + ({ todoId }) => todoId === fromTodo.id, + ) + const unfilteredToIndex = wayfinderTodos.findIndex( + ({ todoId }) => todoId === toTodo.id, + ) + + const [startIndex, endIndex] = calculateReorderIndices( + unfilteredFromIndex, + unfilteredToIndex, + ) + const start = wayfinderTodos[startIndex]?.order + const end = wayfinderTodos[endIndex]?.order + const newOrder = order(start, end) + + console.debug('Re-ordering', { + unfilteredFromIndex, + unfilteredToIndex, + start, + end, + newOrder, + }) + + await db.wayfinderOrder.update(fromTodo.id, { + order: newOrder, + }) + }} + > + {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) + }, + ) + }, + }, + ], + }) + }} + > + { + // 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, + }), + ]) + }, + ) + }} + /> + {todo?.title} + {debug && ( + + {todo.id} + + {todo.order} + + + )} + {todo.starRole && ( + starRole.id === todo.starRole, + )?.icon?.name, + )} + slot="end" + /> + )} + {todo.note && ( + + + + )} + + +
+ ))} +
+
+ + { + setIceboxLimit(limit => limit + 10) + setTimeout(() => event.target.complete(), 500) + }} + > + + + + + )} +
+ +
+
+
+
) } @@ -509,310 +808,41 @@ export const ViewMenu = () => { ) } -export const Log = ({ - limit, - onLoad, +export const Journey = ({ + commonAncestor, }: { - limit: number - onLoad: () => void + commonAncestor: RefObject }) => { - const { inActiveStarRoles, query } = useView() - - const todos = useLiveQuery(async () => { - console.debug('Re-running log query') - return db.todos - .orderBy('completedAt') - .reverse() - .filter( - todo => - !!todo.completedAt && - matchesQuery(query, todo) && - inActiveStarRoles(todo), - ) - .limit(limit) - .toArray() - }, [inActiveStarRoles, limit, query]) - - useEffect(() => { - if (todos !== undefined) { - onLoad() - } - }, [todos]) - - const [present] = useTodoActionSheet() - - if (todos === undefined) return null - - return ( -
-

Log

- {todos?.length ? ( - - {todos.sort(byDate).map(todo => ( - { - present(todo) - }} - > - { - // Prevents the IonItem onClick from firing when completing a todo - event.stopPropagation() - }} - onIonChange={async 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, - }), - ]) - }, - ) - }} - checked={!!todo.completedAt} - /> - {todo?.title} - - ))} - - ) : ( -
- -

Your completed todos will appear here

-
- )} -
+ const { + nextTodo: { + position: [nextTodoPosition], + }, + } = useTodoContext() + const starship = useRef(null) + const [starshipY] = useStarshipYPosition( + starship?.current, + nextTodoPosition, + commonAncestor?.current, ) -} - -export const Wayfinder = ({ onLoad }: { onLoad: () => void }) => { - const { inActiveStarRoles, query } = useView() - - const todos = useLiveQuery(async () => { - const todoOrderItems = await db.wayfinderOrder.orderBy('order').toArray() - const todoIds = todoOrderItems.map(({ todoId }) => todoId) - return (await db.todos.bulkGet(todoIds)) - .map((todo, index) => ({ ...todo!, order: todoOrderItems[index].order })) - .filter( - todo => matchesQuery(query, todo) && inActiveStarRoles(todo), - ) as (Todo & { order: string })[] - }, [inActiveStarRoles, query]) - - const starRoles = useLiveQuery(() => db.starRoles.toArray()) - - useEffect(() => { - if (todos !== undefined) { - setTimeout(onLoad, 2000) - } - }, [todos]) - - const [debug, setDebug] = useState('') - useEffect(() => { - const params = new URLSearchParams(window.location.search) - const searchQuery = params.get('debug') - if (searchQuery) { - setDebug(searchQuery) - } - }, []) - - const [present] = useTodoActionSheet() - - if (todos === undefined) return null return ( -
-

Wayfinder

- {todos?.length ? ( - - { - // We don't use this to reorder for us because it results in a flash of 'unordered' content. - // Instead we re-order right away, calculate the new order ourselves, and update the DB. - event.detail.complete() - - const wayfinderTodos = await db.wayfinderOrder - .orderBy('order') - .toArray() - /* If the todo moves down then all the todos after its target location must be nudged up - * If the todo moves up then all the todos - */ - // TODO: Could make this easier with IDs in the DOM - const fromTodo = todos[event.detail.from] - const toTodo = todos[event.detail.to] - const unfilteredFromIndex = wayfinderTodos.findIndex( - ({ todoId }) => todoId === fromTodo.id, - ) - const unfilteredToIndex = wayfinderTodos.findIndex( - ({ todoId }) => todoId === toTodo.id, - ) - - const [startIndex, endIndex] = calculateReorderIndices( - unfilteredFromIndex, - unfilteredToIndex, - ) - const start = wayfinderTodos[startIndex]?.order - const end = wayfinderTodos[endIndex]?.order - const newOrder = order(start, end) - - console.debug('Re-ordering', { - unfilteredFromIndex, - unfilteredToIndex, - start, - end, - newOrder, - }) - - await db.wayfinderOrder.update(fromTodo.id, { - order: newOrder, - }) - }} - > - {todos.map(todo => ( - { - // 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) - }) - }, - }, - ], - }) - }} - key={todo.id} - > - { - // 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, - }), - ]) - }, - ) - }} - /> - {todo?.title} - {debug && ( - - {todo.id} - {todo.order} - - )} - {todo.starRole && ( - starRole.id === todo.starRole) - ?.icon?.name, - )} - slot="end" - /> - )} - {todo.note && ( - - - - )} - - - ))} - - - ) : ( -
- -

Create some todos to get started

-
- )} -
+
+ +
+ +
+
) } -export const Icebox = ({ - limit, - onLoad, -}: { - limit: number - onLoad: () => void -}) => { - const { inActiveStarRoles, query } = useView() - - const todos = useLiveQuery(async () => { - console.debug('Re-running icebox query') - const todoOrderItems = await db.wayfinderOrder.orderBy('order').toArray() - return db.todos - .where('id') - .noneOf(todoOrderItems.map(({ todoId }) => todoId)) - .and( - todo => - todo.completedAt === undefined && - matchesQuery(query, todo) && - inActiveStarRoles(todo), - ) - .reverse() - .limit(limit) - .toArray() - }, [limit, inActiveStarRoles, query]) - - useEffect(() => { - if (todos !== undefined) onLoad() - }, [todos]) - +export const Icebox = ({ todos }: { todos: Todo[] }) => { const [present] = useTodoActionSheet() const onClick = useCallback( todo => { @@ -869,51 +899,31 @@ export const Icebox = ({ if (todos === undefined) return null return ( -
- -

Icebox

- - {todos === undefined ? ( - - ) : ( - todos.map(todo => ( - - )) - )} +
+ + + {todos.map(todo => ( + { + onClick(todo) + }} + > + + {todo.title} + + + ))}
) } -export const IceboxItem = ({ - onClick, - todo, -}: { - onClick: (todo: Todo) => void - todo: Todo -}) => { - return ( - { - onClick(todo) - }} - > - - {todo.title} - - - ) -} - export const Searchbar = () => { const searchbarRef = useRef(null) @@ -954,12 +964,6 @@ export const Searchbar = () => { ) } -const removeItemFromArray = (array: any[], index: number): any[] => { - const newArray = [...array] - newArray.splice(index, 1) - return newArray -} - const byDate = (a: any, b: any) => { const dateA = new Date(a.completedAt) const dateB = new Date(b.completedAt) diff --git a/components/todos/SelectedTodo.tsx b/components/todos/SelectedTodo.tsx deleted file mode 100644 index 1549997..0000000 --- a/components/todos/SelectedTodo.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - createContext, - Dispatch, - SetStateAction, - useContext, - useState, -} from 'react' -import { Todo } from '../db' - -export const SelecteTodoContext = createContext< - [todo: Todo | null, setTodo: Dispatch>] ->([null, () => null]) - -export function SelectedTodoProvider({ - children, -}: { - children: React.ReactNode -}) { - const [todo, setTodo] = useState(null) - - return ( - - {children} - - ) -} - -export default function useSelectedTodo() { - return useContext(SelecteTodoContext) -} diff --git a/components/todos/TodoContext.tsx b/components/todos/TodoContext.tsx new file mode 100644 index 0000000..a13a76b --- /dev/null +++ b/components/todos/TodoContext.tsx @@ -0,0 +1,50 @@ +import { + createContext, + Dispatch, + SetStateAction, + useContext, + useState, +} from 'react' +import { Todo } from '../db' + +export const TodoContext = createContext<{ + nextTodo: { + position: [ + get: DOMRect | null, + set: Dispatch>, + ] + } + selectedTodo: [ + todo: Todo | null, + setTodo: Dispatch>, + ] +}>({ + nextTodo: { position: [null, () => null] }, + selectedTodo: [null, () => null], +}) + +export function TodoContextProvider({ + children, +}: { + children: React.ReactNode +}) { + const [nextTodoPosition, setNextTodoPosition] = useState(null) + const [selectedTodo, setSelectedTodo] = useState(null) + + return ( + + {children} + + ) +} + +export default function useTodoContext() { + return useContext(TodoContext) +} diff --git a/components/todos/create/useCreateTodoModal.ts b/components/todos/create/useCreateTodoModal.ts index 9a7ab06..2c3439d 100644 --- a/components/todos/create/useCreateTodoModal.ts +++ b/components/todos/create/useCreateTodoModal.ts @@ -5,7 +5,7 @@ import { db, ListType, Todo } from '../../db' import useNoteProvider from '../../notes/useNoteProvider' import { CreateTodoModal } from './modal' import order from '../../common/order' -import useSelectedTodo from '../SelectedTodo' +import useTodoContext from '../TodoContext' export function useCreateTodoModal(): [ ({ @@ -17,7 +17,9 @@ export function useCreateTodoModal(): [ }) => void, (data?: any, role?: string) => void, ] { - const [todo, setTodo] = useSelectedTodo() + const { + selectedTodo: [todo, setTodo], + } = useTodoContext() const titleInput = useRef(null) const [present, dismiss] = useIonModal(CreateTodoModal, { dismiss: (data: string, role: string) => dismiss(data, role), diff --git a/components/todos/edit/useEditTodoModal.ts b/components/todos/edit/useEditTodoModal.ts index 3e716a1..0afc6cb 100644 --- a/components/todos/edit/useEditTodoModal.ts +++ b/components/todos/edit/useEditTodoModal.ts @@ -2,14 +2,16 @@ import { useIonModal } from '@ionic/react' import { useCallback, useRef } from 'react' import { Todo, db } from '../../db' import useNoteProvider from '../../notes/useNoteProvider' -import useSelectedTodo from '../SelectedTodo' +import useTodoContext from '../TodoContext' import { EditTodoModal } from './modal' export function useEditTodoModal(): [ (todo: Todo) => void, (data?: any, role?: string) => void, ] { - const [todo, setTodo] = useSelectedTodo() + const { + selectedTodo: [todo, setTodo], + } = useTodoContext() const titleInput = useRef(null) const [present, dismiss] = useIonModal(EditTodoModal, { dismiss: (data: string, role: string) => dismiss(data, role), diff --git a/components/todos/index.tsx b/components/todos/index.tsx index f21afc2..f6ef482 100644 --- a/components/todos/index.tsx +++ b/components/todos/index.tsx @@ -15,7 +15,9 @@ export default function Todos({ () => todos.sort((a, b) => { if (a.completedAt || b.completedAt) { - return a.completedAt!.getTime() || 0 - b.completedAt!.getTime() || 0 + return ( + (a.completedAt?.getTime() || 0) - (b.completedAt?.getTime() || 0) + ) } return Number(b.rank) - Number(a.rank) }), diff --git a/components/view/index.tsx b/components/view/index.tsx index e1ad815..0fccf72 100644 --- a/components/view/index.tsx +++ b/components/view/index.tsx @@ -66,7 +66,7 @@ export function ViewProvider({ children }: { children: React.ReactNode }) { const inActiveStarRoles = useCallback( (todo: Todo) => { if (todo.starRole === 'str0PBouoSd4NWkh6Em771Nj0Ojcom') { - console.log('in active star role', { + console.debug('in active star role', { todo, allStarRolesActive, activeStarRoles, diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts index 4341fa4..9242ea0 100644 --- a/cypress/e2e/spec.cy.ts +++ b/cypress/e2e/spec.cy.ts @@ -5,10 +5,6 @@ before(() => { it('works', () => { cy.visit('/home') - cy.get('#log').contains('Log').should('be.visible') - cy.get('#wayfinder').contains('Wayfinder').should('be.visible') - cy.get('#icebox').contains('Icebox').should('be.visible') - cy.get('ion-fab-button').click() cy.get('ion-modal').within(() => { cy.contains('label', 'Title') @@ -18,7 +14,7 @@ it('works', () => { cy.contains('Confirm').click() }) - assertLists([], [], ['take the bins out']) + assertLists([], ['take the bins out']) cy.get('#view-menu-button').click() cy.contains('ion-button', 'Edit roles') @@ -76,41 +72,39 @@ it('works', () => { cy.get('ion-modal').contains('Confirm').click() assertLists( - [], [], ['plan birthday day out', 'be silly together', 'take the bins out'], ) cy.get('#icebox').contains('take the bins out').click() cy.get('#todo-action-sheet').contains('Move to wayfinder').click() - cy.get('#wayfinder').contains('take the bins out') + cy.get('#log-and-wayfinder').contains('take the bins out') cy.get('#todo-action-sheet').should('not.exist') cy.get('#icebox').contains('be silly together').click() cy.get('#todo-action-sheet').contains('Move to wayfinder').click() - cy.get('#wayfinder').contains('be silly together') + cy.get('#log-and-wayfinder').contains('be silly together') cy.get('#todo-action-sheet').should('not.exist') cy.get('#icebox').contains('plan birthday day out').click() cy.get('#todo-action-sheet').contains('Move to wayfinder').click() - cy.get('#wayfinder').contains('plan birthday day out') + cy.get('#log-and-wayfinder').contains('plan birthday day out') cy.get('#todo-action-sheet').should('not.exist') assertLists( - [], ['plan birthday day out', 'be silly together', 'take the bins out'], [], ) // reorderImportantTodo(0, 2) - // cy.get('#wayfinder .todo') + // cy.get('#log-and-wayfinder .todo') // .first() // .find('ion-reorder') // .shadow() // .find('.reorder-icon') // .shadow() // .find('svg') - // .drag('#wayfinder .todo:nth-child(3)') + // .drag('#log-and-wayfinder .todo:nth-child(3)') // assertLists( // [], // ['be silly together', 'plan birthday day out', 'take the bins out'], @@ -129,25 +123,19 @@ it('works', () => { // [], // ) - cy.get('#wayfinder') + cy.get('#log-and-wayfinder') .contains('.todo', 'take the bins out') .find('ion-checkbox') .click() assertLists( - ['take the bins out'], - ['plan birthday day out', 'be silly together'], + ['take the bins out', 'plan birthday day out', 'be silly together'], [], ) }) -function assertLists(log: string[], wayfinder: string[], icebox: string[]) { - cy.get('#log .todo') - .should('have.length', log.length) - .invoke('toArray') - .invoke('map', item => item.textContent) - .should('deep.equal', log) - cy.get('#wayfinder .todo') +function assertLists(wayfinder: string[], icebox: string[]) { + cy.get('#log-and-wayfinder .todo') .should('have.length', wayfinder.length) .invoke('toArray') .invoke('map', item => item.textContent) @@ -160,7 +148,7 @@ function assertLists(log: string[], wayfinder: string[], icebox: string[]) { } function reorderWayfinderTodo(todoIndex: number, places: number) { - cy.get(`#wayfinder .todo`) + cy.get(`#log-and-wayfinder .todo`) .eq(todoIndex) .find('ion-reorder') .shadow() diff --git a/package.json b/package.json index 0c58f26..1dec8c0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@ionic/react-router": "7.7.4", "@types/lodash": "^4.17.6", "classnames": "2.5.1", + "clsx": "^2.1.1", "convex": "^1.11.3", "debounce": "^1.2.1", "dexie": "^4.0.9", @@ -56,7 +57,8 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-router": "5.3.4", - "react-router-dom": "5.3.4" + "react-router-dom": "5.3.4", + "tailwind-merge": "^2.5.4" }, "devDependencies": { "@4tw/cypress-drag-drop": "^2.2.5",