diff --git a/apps/mobile/app/components/list-items/note/index.tsx b/apps/mobile/app/components/list-items/note/index.tsx index f40c1a5ca..7b916c2c1 100644 --- a/apps/mobile/app/components/list-items/note/index.tsx +++ b/apps/mobile/app/components/list-items/note/index.tsx @@ -45,10 +45,7 @@ import { ReminderTime } from "../../ui/reminder-time"; import { TimeSince } from "../../ui/time-since"; import Heading from "../../ui/typography/heading"; import Paragraph from "../../ui/typography/paragraph"; -import { - NotebooksWithDateEdited, - TagsWithDateEdited -} from "../../../stores/resolve-items"; +import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common"; type NoteItemProps = { item: Note | BaseTrashItem; diff --git a/apps/mobile/app/components/list-items/note/wrapper.tsx b/apps/mobile/app/components/list-items/note/wrapper.tsx index 20e2cdc80..5ddfd5fba 100644 --- a/apps/mobile/app/components/list-items/note/wrapper.tsx +++ b/apps/mobile/app/components/list-items/note/wrapper.tsx @@ -36,10 +36,7 @@ import { tabBarRef } from "../../../utils/global-refs"; import NotePreview from "../../note-history/preview"; import SelectionWrapper from "../selection-wrapper"; -import { - NotebooksWithDateEdited, - TagsWithDateEdited -} from "../../../stores/resolve-items"; +import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common"; export const openNote = async ( item: Note, diff --git a/apps/mobile/app/components/list/list-item.wrapper.tsx b/apps/mobile/app/components/list/list-item.wrapper.tsx index 175e1c170..101de4c1f 100644 --- a/apps/mobile/app/components/list/list-item.wrapper.tsx +++ b/apps/mobile/app/components/list/list-item.wrapper.tsx @@ -35,12 +35,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { View } from "react-native"; import { db } from "../../common/database"; import { eSendEvent } from "../../services/event-manager"; -import { - NotebooksWithDateEdited, - TagsWithDateEdited, - isNoteResolvedData, - resolveItems -} from "../../stores/resolve-items"; import { RouteName } from "../../stores/use-navigation-store"; import { eOpenJumpToDialog } from "../../utils/events"; import { SectionHeader } from "../list-items/headers/section-header"; @@ -48,6 +42,12 @@ import { NoteWrapper } from "../list-items/note/wrapper"; import { NotebookWrapper } from "../list-items/notebook/wrapper"; import ReminderItem from "../list-items/reminder"; import TagItem from "../list-items/tag"; +import { + NotebooksWithDateEdited, + TagsWithDateEdited, + isNoteResolvedData, + resolveItems +} from "@notesnook/common"; type ListItemWrapperProps = { group?: GroupingKey; @@ -92,7 +92,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) { notebooks.current = data.notebooks; reminder.current = data.reminder; color.current = data.color; - attachmentsCount.current = data.attachmentsCount || 0; + attachmentsCount.current = data.attachments?.total || 0; } else if ( resolvedItem.item.type === "notebook" && typeof data === "number" diff --git a/apps/mobile/app/screens/notebook/index.tsx b/apps/mobile/app/screens/notebook/index.tsx index 8dfe2e92f..72dad6ca3 100644 --- a/apps/mobile/app/screens/notebook/index.tsx +++ b/apps/mobile/app/screens/notebook/index.tsx @@ -44,7 +44,7 @@ import { View } from "react-native"; import { SIZE } from "../../utils/size"; import { IconButton } from "../../components/ui/icon-button"; import { PressableButton } from "../../components/ui/pressable"; -import { resolveItems } from "../../stores/resolve-items"; +import { resolveItems } from "@notesnook/common"; const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => { const [notes, setNotes] = useState>(); diff --git a/apps/mobile/app/screens/notes/index.tsx b/apps/mobile/app/screens/notes/index.tsx index 04d85b4ec..601939bfb 100644 --- a/apps/mobile/app/screens/notes/index.tsx +++ b/apps/mobile/app/screens/notes/index.tsx @@ -31,7 +31,7 @@ import { eUnSubscribeEvent } from "../../services/event-manager"; import Navigation, { NavigationProps } from "../../services/navigation"; -import { resolveItems } from "../../stores/resolve-items"; +import { resolveItems } from "@notesnook/common"; import useNavigationStore, { HeaderRightButton, NotesScreenParams, diff --git a/apps/mobile/app/stores/create-db-collection-store.ts b/apps/mobile/app/stores/create-db-collection-store.ts index 1efe49f6b..46ace3c58 100644 --- a/apps/mobile/app/stores/create-db-collection-store.ts +++ b/apps/mobile/app/stores/create-db-collection-store.ts @@ -19,7 +19,7 @@ along with this program. If not, see . import { Item, VirtualizedGrouping } from "@notesnook/core"; import create, { State, StoreApi, UseBoundStore } from "zustand"; -import { resolveItems } from "./resolve-items"; +import { resolveItems } from "@notesnook/common"; import { useSettingStore } from "./use-setting-store"; export interface DBCollectionStore extends State { diff --git a/apps/web/src/components/editor/header.tsx b/apps/web/src/components/editor/header.tsx index 9ff804723..85144f965 100644 --- a/apps/web/src/components/editor/header.tsx +++ b/apps/web/src/components/editor/header.tsx @@ -30,7 +30,7 @@ import { useMenuTrigger } from "../../hooks/use-menu"; import { MenuItem } from "@notesnook/ui"; import { navigate } from "../../navigation"; import { Tag } from "@notesnook/core"; -import usePromise from "../../hooks/use-promise"; +import { usePromise } from "@notesnook/common"; type HeaderProps = { readonly: boolean }; function Header(props: HeaderProps) { diff --git a/apps/web/src/components/filtered-list/index.tsx b/apps/web/src/components/filtered-list/index.tsx index abc20e0bd..b7d5431ad 100644 --- a/apps/web/src/components/filtered-list/index.tsx +++ b/apps/web/src/components/filtered-list/index.tsx @@ -59,6 +59,7 @@ export function FilteredList(props: FilteredListProps) { inputRef={inputRef} data-test-id={"filter-input"} autoFocus + sx={{ m: 0 }} placeholder={ items.length <= 0 ? placeholders.empty : placeholders.filter } @@ -84,7 +85,9 @@ export function FilteredList(props: FilteredListProps) { display: "flex", justifyContent: "space-between", alignItems: "center", - py: 2 + py: 2, + width: "100%", + mt: 1 }} onClick={async () => { await _createNewItem(query); diff --git a/apps/web/src/components/list-container/index.tsx b/apps/web/src/components/list-container/index.tsx index b6f409302..3a740c2ba 100644 --- a/apps/web/src/components/list-container/index.tsx +++ b/apps/web/src/components/list-container/index.tsx @@ -31,13 +31,7 @@ import { ListLoader } from "../loaders/list-loader"; import ScrollContainer from "../scroll-container"; import { useKeyboardListNavigation } from "../../hooks/use-keyboard-list-navigation"; import { Context } from "./types"; -import { - VirtualizedGrouping, - GroupingKey, - Item, - isGroupHeader -} from "@notesnook/core"; -import { useResolvedItem } from "./resolved-item"; +import { VirtualizedGrouping, GroupingKey, Item } from "@notesnook/core"; import { ItemProps, ScrollerProps, @@ -45,6 +39,7 @@ import { VirtuosoHandle } from "react-virtuoso"; import Skeleton from "react-loading-skeleton"; +import { useResolvedItem } from "@notesnook/common"; export const CustomScrollbarsVirtualList = forwardRef< HTMLDivElement, diff --git a/apps/web/src/components/list-container/list-profiles.tsx b/apps/web/src/components/list-container/list-profiles.tsx index 52a5a31e2..25dd5d2d5 100644 --- a/apps/web/src/components/list-container/list-profiles.tsx +++ b/apps/web/src/components/list-container/list-profiles.tsx @@ -26,8 +26,8 @@ import Reminder from "../reminder"; import { Context } from "./types"; import { getSortValue } from "@notesnook/core/dist/utils/grouping"; import { GroupingKey, Item } from "@notesnook/core"; +import { isNoteResolvedData } from "@notesnook/common"; import { Attachment } from "../attachment"; -import { isNoteResolvedData } from "./resolved-item"; const SINGLE_LINE_HEIGHT = 1.4; const DEFAULT_LINE_HEIGHT = diff --git a/apps/web/src/components/list-container/resolved-item.tsx b/apps/web/src/components/list-container/resolved-item.tsx deleted file mode 100644 index b8ea89230..000000000 --- a/apps/web/src/components/list-container/resolved-item.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/* -This file is part of the Notesnook project (https://notesnook.com/) - -Copyright (C) 2023 Streetwriters (Private) Limited - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -import { - Color, - Item, - ItemMap, - ItemType, - Reminder, - VirtualizedGrouping -} from "@notesnook/core"; -import usePromise from "../../hooks/use-promise"; -import { - NotebooksWithDateEdited, - TagsWithDateEdited, - WithDateEdited -} from "./types"; -import { db } from "../../common/db"; -import React from "react"; - -type ResolvedItemProps = { - type: TItemType; - items: VirtualizedGrouping; - index: number; - children: (item: { - item: ItemMap[TItemType]; - data: unknown; - }) => React.ReactNode; -}; -export function ResolvedItem( - props: ResolvedItemProps -) { - const { index, items, children, type } = props; - const result = usePromise( - () => items.item(index, resolveItems), - [index, items] - ); - - if (result.status === "rejected" || !result.value) return null; - - if (result.value.item.type !== type) return null; - return <>{children(result.value)}; -} - -export function useResolvedItem( - props: Omit, "children" | "type"> -) { - const { index, items } = props; - const result = usePromise( - () => items.item(index, resolveItems), - [index, items] - ); - - if (result.status === "rejected" || !result.value) return null; - return result.value; -} - -function withDateEdited< - T extends { dateEdited: number } | { dateModified: number } ->(items: T[]): WithDateEdited { - let latestDateEdited = 0; - items.forEach((item) => { - const date = "dateEdited" in item ? item.dateEdited : item.dateModified; - if (latestDateEdited < date) latestDateEdited = date; - }); - return { dateEdited: latestDateEdited, items }; -} - -export async function resolveItems(ids: string[], items: Item[]) { - if (!ids.length || !items.length) return []; - - const { type } = items[0]; - if (type === "note") return resolveNotes(ids); - else if (type === "notebook") { - return Promise.all(ids.map((id) => db.notebooks.totalNotes(id))); - } else if (type === "tag") { - return Promise.all( - ids.map((id) => db.relations.from({ id, type: "tag" }, "note").count()) - ); - } - return []; -} - -type NoteResolvedData = { - notebooks?: NotebooksWithDateEdited; - reminder?: Reminder; - color?: Color; - tags?: TagsWithDateEdited; -}; -async function resolveNotes(ids: string[]) { - console.time("relations"); - const relations = [ - ...(await db.relations - .to({ type: "note", ids }, ["notebook", "tag", "color"]) - .get()), - ...(await db.relations.from({ type: "note", ids }, "reminder").get()) - ]; - console.timeEnd("relations"); - const relationIds: { - notebooks: Set; - colors: Set; - tags: Set; - reminders: Set; - } = { - colors: new Set(), - notebooks: new Set(), - tags: new Set(), - reminders: new Set() - }; - - const grouped: Record< - string, - { - notebooks: string[]; - color?: string; - tags: string[]; - reminder?: string; - } - > = {}; - for (const relation of relations) { - const noteId = - relation.toType === "reminder" ? relation.fromId : relation.toId; - const data = grouped[noteId] || { - notebooks: [], - tags: [] - }; - - if (relation.toType === "reminder" && !data.reminder) { - data.reminder = relation.toId; - relationIds.reminders.add(relation.toId); - } else if (relation.fromType === "notebook" && data.notebooks.length < 2) { - data.notebooks.push(relation.fromId); - relationIds.notebooks.add(relation.fromId); - } else if (relation.fromType === "tag" && data.tags.length < 3) { - data.tags.push(relation.fromId); - relationIds.tags.add(relation.fromId); - } else if (relation.fromType === "color" && !data.color) { - data.color = relation.fromId; - relationIds.colors.add(relation.fromId); - } - grouped[noteId] = data; - } - - console.time("resolve"); - const resolved = { - notebooks: await db.notebooks.all.records( - Array.from(relationIds.notebooks) - ), - tags: await db.tags.all.records(Array.from(relationIds.tags)), - colors: await db.colors.all.records(Array.from(relationIds.colors)), - reminders: await db.reminders.all.records(Array.from(relationIds.reminders)) - }; - console.timeEnd("resolve"); - - const data: NoteResolvedData[] = []; - for (const noteId of ids) { - const group = grouped[noteId]; - if (!group) { - data.push({}); - continue; - } - - data.push({ - color: group.color ? resolved.colors[group.color] : undefined, - reminder: group.reminder ? resolved.reminders[group.reminder] : undefined, - tags: withDateEdited( - group.tags.map((id) => resolved.tags[id]).filter(Boolean) - ), - notebooks: withDateEdited( - group.notebooks.map((id) => resolved.notebooks[id]).filter(Boolean) - ) - }); - } - return data; -} - -export function isNoteResolvedData(data: unknown): data is NoteResolvedData { - return ( - typeof data === "object" && - !!data && - "notebooks" in data && - "reminder" in data && - "color" in data && - "tags" in data - ); -} diff --git a/apps/web/src/components/note/index.tsx b/apps/web/src/components/note/index.tsx index 6c309f1b7..dda6ef878 100644 --- a/apps/web/src/components/note/index.tsx +++ b/apps/web/src/components/note/index.tsx @@ -76,9 +76,12 @@ import { isReminderActive, isReminderToday } from "@notesnook/core/dist/collections/reminders"; -import { getFormattedReminderTime, pluralize } from "@notesnook/common"; import { - Reminder as ReminderType, + NoteResolvedData, + getFormattedReminderTime, + pluralize +} from "@notesnook/common"; +import { Color, Note, Notebook as NotebookItem, @@ -86,22 +89,14 @@ import { DefaultColors } from "@notesnook/core"; import { MenuItem } from "@notesnook/ui"; -import { - Context, - NotebooksWithDateEdited, - TagsWithDateEdited -} from "../list-container/types"; +import { Context } from "../list-container/types"; import { SchemeColors } from "@notesnook/theme"; import FileSaver from "file-saver"; -type NoteProps = { - tags?: TagsWithDateEdited; - color?: Color; - notebooks?: NotebooksWithDateEdited; +type NoteProps = NoteResolvedData & { item: Note; context?: Context; date: number; - reminder?: ReminderType; simplified?: boolean; compact?: boolean; }; @@ -111,6 +106,7 @@ function Note(props: NoteProps) { tags, color, notebooks, + attachments, item, date, reminder, @@ -121,17 +117,6 @@ function Note(props: NoteProps) { const note = item; const isOpened = useStore((store) => store.selectedNote === note.id); - const attachments = []; - - // useAttachmentStore((store) => - // store.attachments.filter((a) => a.noteIds.includes(note.id)) - // ); - const failed = []; - - // useMemo( - // () => attachments.filter((a) => a.failed), - // [attachments] - // ); const primary: SchemeColors = color ? color.colorCode : "accent-selected"; return ( @@ -226,21 +211,21 @@ function Note(props: NoteProps) { datetime={date} /> - {attachments.length > 0 && ( + {attachments?.total ? ( - {attachments.length} + {attachments.total} - )} + ) : null} - {failed.length > 0 && ( - + {attachments?.failed ? ( + - {failed.length} + {attachments.failed} - )} + ) : null} {note.pinned && !props.context && ( @@ -289,7 +274,8 @@ export default React.memo(Note, function (prevProps, nextProps) { prevItem.dateModified === nextItem.dateModified && prevProps.notebooks?.dateEdited === nextProps.notebooks?.dateEdited && prevProps.tags?.dateEdited === nextProps.tags?.dateEdited && - prevProps.reminder?.dateModified === nextProps.reminder?.dateModified + prevProps.reminder?.dateModified === nextProps.reminder?.dateModified && + prevProps.attachments === nextProps.attachments ); }); diff --git a/apps/web/src/components/properties/index.tsx b/apps/web/src/components/properties/index.tsx index 8bf101c47..0e376fbc3 100644 --- a/apps/web/src/components/properties/index.tsx +++ b/apps/web/src/components/properties/index.tsx @@ -36,13 +36,11 @@ import { store as noteStore } from "../../stores/note-store"; import { AnimatedFlex } from "../animated"; import Toggle from "./toggle"; import ScrollContainer from "../scroll-container"; -import { getFormattedDate } from "@notesnook/common"; +import { ResolvedItem, getFormattedDate, usePromise } from "@notesnook/common"; import { ScopedThemeProvider } from "../theme-provider"; import { PreviewSession } from "../editor/types"; -import usePromise from "../../hooks/use-promise"; import { ListItemWrapper } from "../list-container/list-profiles"; import { VirtualizedList } from "../virtualized-list"; -import { ResolvedItem } from "../list-container/resolved-item"; import { SessionItem } from "../session-item"; import { COLORS } from "../../common/constants"; import { DefaultColors } from "@notesnook/core"; diff --git a/apps/web/src/components/route-container/index.tsx b/apps/web/src/components/route-container/index.tsx index 2fded9d01..36dea70cb 100644 --- a/apps/web/src/components/route-container/index.tsx +++ b/apps/web/src/components/route-container/index.tsx @@ -23,8 +23,7 @@ import { ArrowLeft, Menu, Search, Plus, Close } from "../icons"; import { useStore } from "../../stores/app-store"; import { useStore as useSearchStore } from "../../stores/search-store"; import useMobile from "../../hooks/use-mobile"; -import usePromise from "../../hooks/use-promise"; -import { debounce } from "@notesnook/common"; +import { debounce, usePromise } from "@notesnook/common"; import Field from "../field"; export type RouteContainerButtons = { diff --git a/apps/web/src/components/unlock/index.tsx b/apps/web/src/components/unlock/index.tsx index 0dd083cb1..3cff655e8 100644 --- a/apps/web/src/components/unlock/index.tsx +++ b/apps/web/src/components/unlock/index.tsx @@ -26,7 +26,7 @@ import { useStore as useAppStore } from "../../stores/app-store"; import Field from "../field"; import { showToast } from "../../utils/toast"; import { ErrorText } from "../error-text"; -import usePromise from "../../hooks/use-promise"; +import { usePromise } from "@notesnook/common"; type UnlockProps = { noteId: string; diff --git a/apps/web/src/dialogs/add-tags-dialog.tsx b/apps/web/src/dialogs/add-tags-dialog.tsx index 374115acf..49b163251 100644 --- a/apps/web/src/dialogs/add-tags-dialog.tsx +++ b/apps/web/src/dialogs/add-tags-dialog.tsx @@ -32,9 +32,9 @@ import { store as editorStore } from "../stores/editor-store"; import { Perform } from "../common/dialog-controller"; import { FilteredList } from "../components/filtered-list"; import { ItemReference, Tag } from "@notesnook/core/dist/types"; -import { ResolvedItem } from "../components/list-container/resolved-item"; import { create } from "zustand"; import { VirtualizedGrouping } from "@notesnook/core"; +import { ResolvedItem } from "@notesnook/common"; type SelectedReference = { id: string; @@ -134,6 +134,8 @@ function AddTagsDialog(props: AddTagsDialogProps) { onCreateNewItem={async (title) => { const tagId = await db.tags.add({ title }); if (!tagId) return; + await useStore.getState().refresh(); + setTags(useStore.getState().tags); const { selected, setSelected } = useSelectionStore.getState(); setSelected([...selected, { id: tagId, new: true, op: "add" }]); }} diff --git a/apps/web/src/dialogs/attachments-dialog.tsx b/apps/web/src/dialogs/attachments-dialog.tsx index 1e9fd7667..796de1f73 100644 --- a/apps/web/src/dialogs/attachments-dialog.tsx +++ b/apps/web/src/dialogs/attachments-dialog.tsx @@ -29,7 +29,7 @@ import { Text } from "@theme-ui/components"; import { store, useStore } from "../stores/attachment-store"; -import { formatBytes } from "@notesnook/common"; +import { ResolvedItem, formatBytes, usePromise } from "@notesnook/common"; import Dialog from "../components/dialog"; import { ChevronDown, @@ -57,13 +57,11 @@ import { VirtualizedGrouping } from "@notesnook/core"; import { Multiselect } from "../common/multi-select"; -import { ResolvedItem } from "../components/list-container/resolved-item"; import { VirtualizedTable, VirtualizedTableRowProps } from "../components/virtualized-table"; import { FlexScrollContainer } from "../components/scroll-container"; -import usePromise from "../hooks/use-promise"; type ToolbarAction = { title: string; diff --git a/apps/web/src/dialogs/move-note-dialog.tsx b/apps/web/src/dialogs/move-note-dialog.tsx index 617eb09fb..73d9bc77e 100644 --- a/apps/web/src/dialogs/move-note-dialog.tsx +++ b/apps/web/src/dialogs/move-note-dialog.tsx @@ -46,8 +46,7 @@ import { TreeEnvironmentRef } from "react-complex-tree"; import { FlexScrollContainer } from "../components/scroll-container"; -import { pluralize } from "@notesnook/common"; -import usePromise from "../hooks/use-promise"; +import { pluralize, usePromise } from "@notesnook/common"; type MoveDialogProps = { onClose: Perform; noteIds: string[] }; type NotebookReference = { diff --git a/apps/web/src/dialogs/reminder-preview-dialog.tsx b/apps/web/src/dialogs/reminder-preview-dialog.tsx index f2d62e280..8be7a3369 100644 --- a/apps/web/src/dialogs/reminder-preview-dialog.tsx +++ b/apps/web/src/dialogs/reminder-preview-dialog.tsx @@ -25,8 +25,7 @@ import { Reminder } from "@notesnook/core/dist/types"; import IconTag from "../components/icon-tag"; import { Clock, Refresh } from "../components/icons"; import Note from "../components/note"; -import { getFormattedReminderTime } from "@notesnook/common"; -import usePromise from "../hooks/use-promise"; +import { getFormattedReminderTime, usePromise } from "@notesnook/common"; export type ReminderPreviewDialogProps = { onClose: Perform; diff --git a/packages/common/src/components/index.ts b/packages/common/src/components/index.ts new file mode 100644 index 000000000..d55208242 --- /dev/null +++ b/packages/common/src/components/index.ts @@ -0,0 +1,20 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +export * from "./resolved-item"; diff --git a/packages/common/src/components/resolved-item.tsx b/packages/common/src/components/resolved-item.tsx new file mode 100644 index 000000000..876e40e14 --- /dev/null +++ b/packages/common/src/components/resolved-item.tsx @@ -0,0 +1,39 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { ItemMap, ItemType } from "@notesnook/core"; +import { + ResolvedItemOptions, + useResolvedItem +} from "../hooks/use-resolved-item"; + +type ResolvedItemProps = + ResolvedItemOptions & { + children: (item: { + item: ItemMap[TItemType]; + data: unknown; + }) => React.ReactNode; + }; +export function ResolvedItem( + props: ResolvedItemProps +) { + const { children } = props; + const result = useResolvedItem(props); + return result ? <>{children(result)} : null; +} diff --git a/packages/common/src/hooks/index.ts b/packages/common/src/hooks/index.ts index e6c2ba36b..ebb10c6cd 100644 --- a/packages/common/src/hooks/index.ts +++ b/packages/common/src/hooks/index.ts @@ -18,3 +18,5 @@ along with this program. If not, see . */ export * from "./use-time-ago"; +export * from "./use-promise"; +export * from "./use-resolved-item"; diff --git a/apps/web/src/hooks/use-promise.ts b/packages/common/src/hooks/use-promise.ts similarity index 98% rename from apps/web/src/hooks/use-promise.ts rename to packages/common/src/hooks/use-promise.ts index 7f6f8693a..b60dc74e1 100644 --- a/apps/web/src/hooks/use-promise.ts +++ b/packages/common/src/hooks/use-promise.ts @@ -55,7 +55,7 @@ export type PromiseFactoryFn = (signal: AbortSignal) => T | Promise; * @param factory Function that creates the promise. * @param deps If present, promise will be recreated if the values in the list change. */ -export default function usePromise( +export function usePromise( factory: PromiseFactoryFn, deps: DependencyList = [] ): PromiseResult { diff --git a/packages/common/src/hooks/use-resolved-item.ts b/packages/common/src/hooks/use-resolved-item.ts new file mode 100644 index 000000000..503819587 --- /dev/null +++ b/packages/common/src/hooks/use-resolved-item.ts @@ -0,0 +1,59 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import { ItemMap, ItemType, VirtualizedGrouping } from "@notesnook/core"; +import { usePromise } from "./use-promise"; +import { resolveItems } from "../utils/resolve-items"; + +export type ResolvedItemOptions = { + type?: TItemType; + items: VirtualizedGrouping; + index: number; +}; + +/** + * Fetches & resolves the item from VirtualizedGrouping + */ +export function useResolvedItem( + options: ResolvedItemOptions +) { + const { index, items, type } = options; + const result = usePromise( + () => items.item(index, resolveItems), + [index, items] + ); + + if (result.status === "rejected" || !result.value) return null; + if (type && result.value.item.type !== type) return null; + return result.value; +} + +/** + * Fetches but does not resolve the item from VirtualizedGrouping + */ +export function useUnresolvedItem( + options: ResolvedItemOptions +) { + const { index, items, type } = options; + const result = usePromise(() => items.item(index), [index, items]); + + if (result.status === "rejected" || !result.value) return null; + if (type && result.value.item.type !== type) return null; + return result.value; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 768b5a19a..b36ea2925 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -20,3 +20,4 @@ along with this program. If not, see . export * from "./database"; export * from "./utils"; export * from "./hooks"; +export * from "./components"; diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index 83e7c1793..66300ddc9 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -23,3 +23,4 @@ export * from "./time"; export * from "./debounce"; export * from "./random"; export * from "./string"; +export * from "./resolve-items"; diff --git a/apps/mobile/app/stores/resolve-items.ts b/packages/common/src/utils/resolve-items.ts similarity index 89% rename from apps/mobile/app/stores/resolve-items.ts rename to packages/common/src/utils/resolve-items.ts index 391604d44..ac346bbe3 100644 --- a/apps/mobile/app/stores/resolve-items.ts +++ b/packages/common/src/utils/resolve-items.ts @@ -16,10 +16,11 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { Color, Item, Notebook, Reminder, Tag } from "@notesnook/core"; -import { db } from "../common/database"; -export type WithDateEdited = { items: T[]; dateEdited: number }; +import { Color, Item, Reminder, Notebook, Tag } from "@notesnook/core"; +import { database as db } from "../database"; + +type WithDateEdited = { items: T[]; dateEdited: number }; export type NotebooksWithDateEdited = WithDateEdited; export type TagsWithDateEdited = WithDateEdited; @@ -49,22 +50,25 @@ export async function resolveItems(ids: string[], items: Item[]) { return []; } -type NoteResolvedData = { +export type NoteResolvedData = { notebooks?: NotebooksWithDateEdited; reminder?: Reminder; color?: Color; tags?: TagsWithDateEdited; - attachmentsCount?: number; + attachments?: { + failed: number; + total: number; + }; }; + async function resolveNotes(ids: string[]) { - console.time("relations"); const relations = [ ...(await db.relations .to({ type: "note", ids }, ["notebook", "tag", "color"]) .get()), ...(await db.relations.from({ type: "note", ids }, "reminder").get()) ]; - console.timeEnd("relations"); + const relationIds: { notebooks: Set; colors: Set; @@ -86,6 +90,7 @@ async function resolveNotes(ids: string[]) { reminder?: string; } > = {}; + for (const relation of relations) { const noteId = relation.toType === "reminder" ? relation.fromId : relation.toId; @@ -110,7 +115,6 @@ async function resolveNotes(ids: string[]) { grouped[noteId] = data; } - console.time("resolve"); const resolved = { notebooks: await db.notebooks.all.records( Array.from(relationIds.notebooks) @@ -119,7 +123,6 @@ async function resolveNotes(ids: string[]) { colors: await db.colors.all.records(Array.from(relationIds.colors)), reminders: await db.reminders.all.records(Array.from(relationIds.reminders)) }; - console.timeEnd("resolve"); const data: NoteResolvedData[] = []; for (const noteId of ids) { @@ -129,6 +132,7 @@ async function resolveNotes(ids: string[]) { continue; } + const attachments = db.attachments?.ofNote(noteId, "all"); data.push({ color: group.color ? resolved.colors[group.color] : undefined, reminder: group.reminder ? resolved.reminders[group.reminder] : undefined, @@ -138,11 +142,14 @@ async function resolveNotes(ids: string[]) { notebooks: withDateEdited( group.notebooks.map((id) => resolved.notebooks[id]).filter(Boolean) ), - attachmentsCount: - (await db.attachments?.ofNote(noteId, "all").ids())?.length || 0 + attachments: { + total: await attachments.count(), + failed: await attachments + .where((eb) => eb("attachments.failed", "is not", eb.lit(null))) + .count() + } }); } - return data; }