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