From ffe2265e7af4b469122371fd4e0db09487a2622a Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sat, 9 Mar 2024 21:26:32 +0100 Subject: [PATCH 1/8] Move castTemporal to temporal dir --- lib/hooks/useCastTemporal.ts | 2 +- lib/{ => temporal}/castTemporal.ts | 0 pages/note/[id].tsx | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename lib/{ => temporal}/castTemporal.ts (100%) diff --git a/lib/hooks/useCastTemporal.ts b/lib/hooks/useCastTemporal.ts index 26c0793..cc0c8a3 100644 --- a/lib/hooks/useCastTemporal.ts +++ b/lib/hooks/useCastTemporal.ts @@ -5,7 +5,7 @@ import { SqliteDateTime, SqliteTime, castTemporal, -} from "../castTemporal"; +} from "../temporal/castTemporal"; import { NowContext } from "../contexts/NowContext"; export const useCastTemporal = () => { diff --git a/lib/castTemporal.ts b/lib/temporal/castTemporal.ts similarity index 100% rename from lib/castTemporal.ts rename to lib/temporal/castTemporal.ts diff --git a/pages/note/[id].tsx b/pages/note/[id].tsx index 655b85f..8c08d31 100644 --- a/pages/note/[id].tsx +++ b/pages/note/[id].tsx @@ -18,7 +18,7 @@ import { import { appSpacing } from "../../lib/Themes"; import { baseline, colors, consts, spacing } from "../../lib/Tokens.stylex"; import { RNfW } from "../../lib/Types"; -import { SqliteDateTime } from "../../lib/castTemporal"; +import { SqliteDateTime } from "../../lib/temporal/castTemporal"; import { useCastTemporal } from "../../lib/hooks/useCastTemporal"; import { useGetDayUrl } from "../../lib/hooks/useGetDayUrl"; import { useOnUserLeave } from "../../lib/hooks/useOnUserLeave"; From dfe1ef531e55d3e2f0dd03148aae072dba530394 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sat, 9 Mar 2024 21:26:43 +0100 Subject: [PATCH 2/8] Add dev comment --- lib/Lexical.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Lexical.ts b/lib/Lexical.ts index cb4ad7a..fbf1d0a 100644 --- a/lib/Lexical.ts +++ b/lib/Lexical.ts @@ -77,6 +77,7 @@ export const Root = Element.pipe( export type Root = S.Schema.To; export const rootsAreEqual = (root1: Root, root2: Root): boolean => + // TODO: Remove when Effect supports deep equality (soon) dequal(root1, root2); /** From 932eb50229090ee66fa2bda2cf3cac703c78545f Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sat, 9 Mar 2024 21:27:00 +0100 Subject: [PATCH 3/8] Add end prop to NoteId --- lib/Db.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Db.ts b/lib/Db.ts index 1311640..4021410 100644 --- a/lib/Db.ts +++ b/lib/Db.ts @@ -1,7 +1,7 @@ import * as S from "@effect/schema/Schema"; import * as Evolu from "@evolu/react"; import { createEvolu, database, id, table } from "@evolu/react"; -import { SqliteDateTime } from "./castTemporal"; +import { SqliteDateTime } from "./temporal/castTemporal"; import { Content, ContentMax10k } from "./Lexical"; export const NoteId = id("Note"); @@ -11,8 +11,7 @@ export const NoteTable = table({ id: NoteId, content: ContentMax10k, start: SqliteDateTime, - // TODO: - // end: S.nullable(SqliteDateTime), + end: S.nullable(SqliteDateTime), }); export type NoteTable = S.Schema.To; From 0ebbfb6e8c5fddc713c21fa1097d9226ae1d8fe8 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sat, 9 Mar 2024 21:28:24 +0100 Subject: [PATCH 4/8] NoteDialog Note end --- components/DayNotes.tsx | 8 +-- components/NoteDialog.tsx | 142 +++++++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/components/DayNotes.tsx b/components/DayNotes.tsx index 0597907..14602f1 100644 --- a/components/DayNotes.tsx +++ b/components/DayNotes.tsx @@ -5,7 +5,7 @@ import { Temporal } from "temporal-polyfill"; import { evolu } from "../lib/Db"; import { getNoteUrl } from "../lib/Routing"; import { spacing } from "../lib/Tokens.stylex"; -import { SqliteDateTime } from "../lib/castTemporal"; +import { SqliteDateTime } from "../lib/temporal/castTemporal"; import { useCastTemporal } from "../lib/hooks/useCastTemporal"; import { Button } from "./Button"; import { EditorOneLine } from "./EditorOneLine"; @@ -23,14 +23,14 @@ const notesByDay = (startOfDay: SqliteDateTime, endOfDay: SqliteDateTime) => evolu.createQuery((db) => db .selectFrom("note") - .select(["id", "content", "start"]) + .select(["id", "content", "start", "end"]) .where("isDeleted", "is not", cast(true)) // Filter null value and ensure non-null type. .where("content", "is not", null) .where("start", "is not", null) + .$narrowType<{ content: NotNull; start: NotNull }>() .where((eb) => eb.between("start", startOfDay, endOfDay)) - .orderBy(["start"]) - .$narrowType<{ content: NotNull; start: NotNull }>(), + .orderBy(["start"]), ); export type NotesByDayRow = ExtractRow>; diff --git a/components/NoteDialog.tsx b/components/NoteDialog.tsx index b9ffb46..6ed453f 100644 --- a/components/NoteDialog.tsx +++ b/components/NoteDialog.tsx @@ -1,4 +1,5 @@ import { create, props } from "@stylexjs/stylex"; +import { Function, Match } from "effect"; import { FC, useCallback, useState } from "react"; import { Temporal } from "temporal-polyfill"; import { useEvolu } from "../lib/Db"; @@ -15,84 +16,115 @@ export const NoteDialog: FC<{ row: NotesByDayRow; onRequestClose: () => void; }> = ({ row, onRequestClose }) => { - const { update } = useEvolu(); - const castTemporal = useCastTemporal(); - const start = castTemporal(row.start); - // const end = castTemporal( row.start); + const { update } = useEvolu(); - const [startTime, setStartTime] = useState(start.toPlainTime()); - const [startDate, setStartDate] = useState(start.toPlainDate()); - // const [endTime, setEndTime] = useState(end.toPlainTime()); - // const [endDate /*, setEndDate*/] = useState(end.toPlainDate()); + const initialStart = castTemporal(row.start); + const initialEnd = row.end ? castTemporal(row.end) : castTemporal(row.start); - const handleDeleteButtonPress = () => { - update("note", { id: row.id, isDeleted: true }, onRequestClose); - }; + const [start, setStart] = useState(initialStart); + const [end, setEnd] = useState(initialEnd); - const handleDialogCancel = useCallback(() => { - onRequestClose(); - }, [onRequestClose]); + const startEndComparison = Temporal.PlainDateTime.compare(start, end); const handleDialogDone = useCallback(() => { - const start = castTemporal( - new Temporal.PlainDateTime( - startDate.year, - startDate.month, - startDate.day, - startTime.hour, - startTime.minute, - ), - ); - update("note", { id: row.id, start }, onRequestClose); + if (startEndComparison === 1) { + alert("The start date must be before the end date."); + return; + } + + const values = { + start: castTemporal(start), + // Don't duplicate the start if it's not necessary. + end: startEndComparison === 0 ? null : castTemporal(end), + }; + + if (values.start === row.start && values.end === row.end) { + onRequestClose(); + } else { + update("note", { id: row.id, ...values }, onRequestClose); + } }, [ castTemporal, + end, onRequestClose, + row.end, row.id, - startDate.day, - startDate.month, - startDate.year, - startTime.hour, - startTime.minute, + row.start, + start, + startEndComparison, update, ]); + const endTitleStyle = Match.value(startEndComparison).pipe( + Match.when(0, () => styles.secondaryColor), + Match.when(1, () => styles.lineThroughTextDecoration), + Match.orElse(Function.constNull), + ); + return ( { + onRequestClose(); + }} onDone={handleDialogDone} containerStyle={styles.dialogContainer} >
-
-
-
+
@@ -101,25 +133,25 @@ export const NoteDialog: FC<{ const styles = create({ dialogContainer: { - maxWidth: "22rem", + maxWidth: "20rem", }, editorWrapperForEllipsis: { display: "flex", flexDirection: "row", alignItems: "baseline", }, - timeGrid: { - display: "grid", - gridTemplateColumns: "1fr auto auto", + buttons: { + display: "flex", + justifyContent: "space-between", }, - label: { - justifySelf: "start", + centeredButton: { + // No flex: 1 because we don't want full width. + margin: "auto", }, - actions: { - display: "flex", - justifyContent: "center", + secondaryColor: { + color: colors.secondary, }, - deleteTitle: { - color: colors.inactive, + lineThroughTextDecoration: { + textDecoration: "line-through", }, }); From 2a6b456291bc9060c2ddde0ac506c2c98726abf8 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 10 Mar 2024 00:05:01 +0100 Subject: [PATCH 5/8] Make color transition slightly slower --- lib/Tokens.stylex.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Tokens.stylex.ts b/lib/Tokens.stylex.ts index 804f263..ac19754 100644 --- a/lib/Tokens.stylex.ts +++ b/lib/Tokens.stylex.ts @@ -84,7 +84,7 @@ export const baseline = defineVars({ export const transitions = defineVars({ color: - "color 0.1s ease, background-color 0.1s ease, outline-color 0.1s ease, border-color 0.1s ease", + "color 0.15s ease, background-color 0.15s ease, outline-color 0.15s ease, border-color 0.15s ease", }); // https://github.com/facebook/stylex/blob/main/packages/open-props/src/shadows.stylex.js From 366dd4bf412f9370cf9225ed9818a36a0072c7bb Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 10 Mar 2024 00:05:24 +0100 Subject: [PATCH 6/8] Update NodeDialog styles --- components/NoteDialog.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/NoteDialog.tsx b/components/NoteDialog.tsx index 6ed453f..80300a1 100644 --- a/components/NoteDialog.tsx +++ b/components/NoteDialog.tsx @@ -3,7 +3,7 @@ import { Function, Match } from "effect"; import { FC, useCallback, useState } from "react"; import { Temporal } from "temporal-polyfill"; import { useEvolu } from "../lib/Db"; -import { colors } from "../lib/Tokens.stylex"; +import { colors, transitions } from "../lib/Tokens.stylex"; import { useCastTemporal } from "../lib/hooks/useCastTemporal"; import { Button } from "./Button"; import { DatePopoverButton } from "./DatePopoverButton"; @@ -133,7 +133,7 @@ export const NoteDialog: FC<{ const styles = create({ dialogContainer: { - maxWidth: "20rem", + maxWidth: "22rem", }, editorWrapperForEllipsis: { display: "flex", @@ -150,6 +150,7 @@ const styles = create({ }, secondaryColor: { color: colors.secondary, + transition: transitions.color, }, lineThroughTextDecoration: { textDecoration: "line-through", From 6b23c44a42be6ffedbb448fe96b9894c8410ceee Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Sun, 10 Mar 2024 17:12:31 +0100 Subject: [PATCH 7/8] Close #5 --- components/DayNotes.tsx | 130 ++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 33 deletions(-) diff --git a/components/DayNotes.tsx b/components/DayNotes.tsx index 14602f1..862c174 100644 --- a/components/DayNotes.tsx +++ b/components/DayNotes.tsx @@ -1,59 +1,101 @@ import { ExtractRow, NotNull, cast, useQuery } from "@evolu/react"; import { create, props } from "@stylexjs/stylex"; -import { FC, memo, useState } from "react"; +import { FC, memo, useMemo, useState } from "react"; import { Temporal } from "temporal-polyfill"; import { evolu } from "../lib/Db"; import { getNoteUrl } from "../lib/Routing"; import { spacing } from "../lib/Tokens.stylex"; -import { SqliteDateTime } from "../lib/temporal/castTemporal"; import { useCastTemporal } from "../lib/hooks/useCastTemporal"; +import { SqliteDateTime } from "../lib/temporal/castTemporal"; import { Button } from "./Button"; import { EditorOneLine } from "./EditorOneLine"; import { Formatted } from "./Formatted"; import { Link } from "./Link"; import { NoteDialog } from "./NoteDialog"; +import { ReadonlyArray, pipe } from "effect"; -// TODO: -// SELECT * -// FROM Note -// WHERE start BETWEEN '2024-02-08 00:00:00' AND '2024-02-08 23:59:59' -// OR COALESCE(end, start) BETWEEN '2024-02-08 00:00:00' AND '2024-02-08 23:59:59' -// OR (start <= '2024-02-08 00:00:00' AND COALESCE(end, start) >= '2024-02-08 23:59:59'); const notesByDay = (startOfDay: SqliteDateTime, endOfDay: SqliteDateTime) => evolu.createQuery((db) => db .selectFrom("note") .select(["id", "content", "start", "end"]) - .where("isDeleted", "is not", cast(true)) // Filter null value and ensure non-null type. .where("content", "is not", null) .where("start", "is not", null) .$narrowType<{ content: NotNull; start: NotNull }>() - .where((eb) => eb.between("start", startOfDay, endOfDay)) + .where("isDeleted", "is not", cast(true)) + .where((eb) => + eb.or([ + // Note starts on the date. + eb.between("start", startOfDay, endOfDay), + // Note ends on the date. + eb.between(eb.fn.coalesce("end", "start"), startOfDay, endOfDay), + // Note spans over the date. + eb.and([ + eb("start", "<=", startOfDay), + eb(eb.fn.coalesce("end", "start"), ">=", endOfDay), + ]), + ]), + ) .orderBy(["start"]), ); export type NotesByDayRow = ExtractRow>; +type NotesByDayRowWithComparison = NotesByDayRow & { + startsBefore: boolean; + endsAfter: boolean; +}; + export const DayNotes: FC<{ day: Temporal.PlainDate; isVisible: boolean; }> = ({ day, isVisible }) => { const castTemporal = useCastTemporal(); - const startOfDay = castTemporal( - day.toPlainDateTime(Temporal.PlainTime.from("00:00")), + + const startOfDay = day.toPlainDateTime(Temporal.PlainTime.from("00:00")); + const endOfDay = day.toPlainDateTime(Temporal.PlainTime.from("23:59:59")); + + const { rows } = useQuery( + notesByDay(castTemporal(startOfDay), castTemporal(endOfDay)), ); - const endOfDay = castTemporal( - day.toPlainDateTime(Temporal.PlainTime.from("23:59:59")), + + const sortedRowsWithComparison = useMemo( + () => + pipe( + rows.map( + (row): NotesByDayRowWithComparison => ({ + ...row, + startsBefore: + Temporal.PlainDateTime.compare( + castTemporal(row.start), + startOfDay, + ) === -1, + endsAfter: + row.end != null && + Temporal.PlainDateTime.compare( + castTemporal(row.end), + endOfDay, + ) === 1, + }), + ), + // Overlapping notes first, then startsBefore, then rest. + ReadonlyArray.partition((row) => row.startsBefore && row.endsAfter), + ([rest, overlapping]) => + overlapping.concat( + ...ReadonlyArray.partition(rest, (row) => !row.startsBefore), + ), + ), + [castTemporal, endOfDay, rows, startOfDay], ); - const { rows } = useQuery(notesByDay(startOfDay, endOfDay)); - return rows.map((row) => ( + + return sortedRowsWithComparison.map((row) => ( )); }; const DayNote = memo<{ - row: NotesByDayRow; + row: NotesByDayRowWithComparison; isVisible: boolean; }>(function DayNote({ row, isVisible }) { return ( @@ -80,9 +122,10 @@ const DayNote = memo<{ }); const TimeButton: FC<{ - row: NotesByDayRow; + row: NotesByDayRowWithComparison; isVisible: boolean; }> = ({ row, isVisible }) => { + const castTemporal = useCastTemporal(); const [dialogIsShown, setDialogIsShown] = useState(false); const handleButtonPress = () => { @@ -93,9 +136,6 @@ const TimeButton: FC<{ setDialogIsShown(false); }; - const castTemporal = useCastTemporal(); - const start = castTemporal(row.start).toPlainTime(); - return ( <> {dialogIsShown && ( @@ -103,23 +143,37 @@ const TimeButton: FC<{ )}