diff --git a/apps/web/src/components/group-header/index.tsx b/apps/web/src/components/group-header/index.tsx index c3cdd0805..dd9d6b2b8 100644 --- a/apps/web/src/components/group-header/index.tsx +++ b/apps/web/src/components/group-header/index.tsx @@ -28,7 +28,8 @@ import { SortDesc, DetailedView, CompactView, - Icon + Icon, + Loading } from "../icons"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { Button, Flex, Text } from "@theme-ui/components"; @@ -221,8 +222,8 @@ type GroupHeaderProps = { groupingKey: GroupingKey; index: number; - groups: GroupHeaderType[]; - onJump: (title: string) => void; + groups: () => Promise<{ index: number; group: GroupHeaderType }[]>; + onJump: (index: number) => void; refresh: () => void; onSelectGroup: () => void; isFocused: boolean; @@ -279,18 +280,29 @@ function GroupHeader(props: GroupHeaderProps) { onSelectGroup(); return; } - if (groups.length <= 0) return; e.stopPropagation(); - const items: MenuItem[] = groups.map((group) => { - const groupTitle = group.title.toString(); - return { - type: "button", - key: groupTitle, - title: groupTitle, - onClick: () => onJump(groupTitle), - checked: group.title === title - }; - }); + + const items: MenuItem[] = [ + { + key: "groups", + type: "lazy-loader", + loader: , + async items() { + const items = await groups(); + return items.map(({ group, index }) => { + const groupTitle = group.title.toString(); + return { + type: "button", + key: groupTitle, + title: groupTitle, + onClick: () => onJump(index), + checked: group.title === title + } as MenuItem; + }); + } + } + ]; + openMenu(items, { title: "Jump to group", position: { diff --git a/apps/web/src/components/list-container/index.tsx b/apps/web/src/components/list-container/index.tsx index dcd0e0274..570d87a4a 100644 --- a/apps/web/src/components/list-container/index.tsx +++ b/apps/web/src/components/list-container/index.tsx @@ -25,7 +25,7 @@ import { store as selectionStore } from "../../stores/selection-store"; import GroupHeader from "../group-header"; -import { DEFAULT_ITEM_HEIGHT, ListItemWrapper } from "./list-profiles"; +import { ListItemWrapper } from "./list-profiles"; import Announcements from "../announcements"; import { ListLoader } from "../loaders/list-loader"; import { FlexScrollContainer } from "../scroll-container"; @@ -39,6 +39,7 @@ import { } from "@notesnook/core"; import { VirtualizedList } from "../virtualized-list"; import { Virtualizer } from "@tanstack/react-virtual"; +import { useResolvedItem } from "./resolved-item"; type ListContainerProps = { group?: GroupingKey; @@ -125,72 +126,74 @@ function ListContainer(props: ListContainerProps) { {header ? header : } items.getKey(index)} items={items.ids} mode="dynamic" tabIndex={-1} + overscan={10} onBlur={() => setFocusedGroupIndex(-1)} onKeyDown={(e) => onKeyDown(e.nativeEvent)} itemWrapperProps={(_, index) => ({ onFocus: () => onFocus(index), onMouseDown: (e) => onMouseDown(e.nativeEvent, index) })} - renderItem={({ index, item }) => { - if (isGroupHeader(item)) { - if (!group) return null; - return ( - { - let endIndex; - for ( - let i = index + 1; - i < props.items.ids.length; - ++i - ) { - if (typeof props.items.ids[i] === "object") { - endIndex = i; - break; - } - } - setSelectedItems([ - ...selectionStore.get().selectedItems, - ...props.items.ids.slice( - index, - endIndex || props.items.ids.length - ) - ]); - }} - groups={props.items.groups} - onJump={(title: string) => { - const index = props.items.ids.findIndex( - (v) => isGroupHeader(v) && v.title === title - ); - if (index < 0) return; - listRef.current?.scrollToIndex(index, { - align: "center", - behavior: "auto" - }); - setFocusedGroupIndex(index); - }} - /> - ); - } + renderItem={function ItemRenderer({ index }) { + const resolvedItem = useResolvedItem({ index, items }); + // console.log("Rendering:", index); + if (!resolvedItem) + return
; return ( - + <> + {resolvedItem.group && group ? ( + { + let endIndex; + for ( + let i = index + 1; + i < props.items.ids.length; + ++i + ) { + if (typeof props.items.ids[i] === "object") { + endIndex = i; + break; + } + } + setSelectedItems([ + ...selectionStore.get().selectedItems, + ...props.items.ids.slice( + index, + endIndex || props.items.ids.length + ) + ]); + }} + groups={async () => + items.groups ? items.groups() : [] + } + onJump={(index) => { + listRef.current?.scrollToIndex(index, { + align: "center", + behavior: "auto" + }); + setFocusedGroupIndex(index); + }} + /> + ) : null} + + ); }} /> diff --git a/apps/web/src/components/list-container/list-profiles.tsx b/apps/web/src/components/list-container/list-profiles.tsx index 50ee675d2..52a5a31e2 100644 --- a/apps/web/src/components/list-container/list-profiles.tsx +++ b/apps/web/src/components/list-container/list-profiles.tsx @@ -25,9 +25,9 @@ import { db } from "../../common/db"; import Reminder from "../reminder"; import { Context } from "./types"; import { getSortValue } from "@notesnook/core/dist/utils/grouping"; -import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core"; +import { GroupingKey, Item } from "@notesnook/core"; import { Attachment } from "../attachment"; -import { isNoteResolvedData, useResolvedItem } from "./resolved-item"; +import { isNoteResolvedData } from "./resolved-item"; const SINGLE_LINE_HEIGHT = 1.4; const DEFAULT_LINE_HEIGHT = @@ -36,21 +36,16 @@ export const DEFAULT_ITEM_HEIGHT = SINGLE_LINE_HEIGHT * 4 * DEFAULT_LINE_HEIGHT; type ListItemWrapperProps = { group?: GroupingKey; - items: VirtualizedGrouping; - id: string; + item: Item; + data?: unknown; context?: Context; compact?: boolean; simplified?: boolean; }; export function ListItemWrapper(props: ListItemWrapperProps) { - const { group, compact, context, simplified } = props; + const { group, compact, context, simplified, item, data } = props; - const resolvedItem = useResolvedItem(props); - if (!resolvedItem) - return
; - - const { data, item } = resolvedItem; switch (item.type) { case "note": { return ( diff --git a/apps/web/src/components/list-container/resolved-item.tsx b/apps/web/src/components/list-container/resolved-item.tsx index 1f86ade6d..17b5b0901 100644 --- a/apps/web/src/components/list-container/resolved-item.tsx +++ b/apps/web/src/components/list-container/resolved-item.tsx @@ -37,7 +37,7 @@ import React from "react"; type ResolvedItemProps = { type: TItemType; items: VirtualizedGrouping; - id: string; + index: number; children: (item: { item: ItemMap[TItemType]; data: unknown; @@ -46,8 +46,11 @@ type ResolvedItemProps = { export function ResolvedItem( props: ResolvedItemProps ) { - const { id, items, children, type } = props; - const result = usePromise(() => items.item(id, resolveItems), [id, items]); + const { index, items, children, type } = props; + const result = usePromise( + () => items.item(index, resolveItems), + [index, items] + ); if (result.status !== "fulfilled" || !result.value) return null; @@ -55,9 +58,14 @@ export function ResolvedItem( return <>{children(result.value)}; } -export function useResolvedItem(props: Omit) { - const { id, items } = props; - const result = usePromise(() => items.item(id, resolveItems), [id, items]); +export function useResolvedItem( + props: Omit, "children" | "type"> +) { + const { index, items } = props; + const result = usePromise( + () => items.item(index, resolveItems), + [index, items] + ); if (result.status !== "fulfilled" || !result.value) return null; return result.value; @@ -74,20 +82,17 @@ function withDateEdited< return { dateEdited: latestDateEdited, items }; } -export async function resolveItems(ids: string[], items: Record) { - const { type } = items[ids[0]]; +export async function resolveItems(ids: string[], items: Item[]) { + const { type } = items[0]; if (type === "note") return resolveNotes(ids); else if (type === "notebook") { - const data: Record = {}; - for (const id of ids) data[id] = await db.notebooks.totalNotes(id); - return data; + return Promise.all(ids.map((id) => db.notebooks.totalNotes(id))); } else if (type === "tag") { - const data: Record = {}; - for (const id of ids) - data[id] = await db.relations.from({ id, type: "tag" }, "note").count(); - return data; + return Promise.all( + ids.map((id) => db.relations.from({ id, type: "tag" }, "note").count()) + ); } - return {}; + return []; } type NoteResolvedData = { @@ -161,17 +166,17 @@ async function resolveNotes(ids: string[]) { }; console.timeEnd("resolve"); - const data: Record = {}; + const data: NoteResolvedData[] = []; for (const noteId in grouped) { const group = grouped[noteId]; - data[noteId] = { + 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])), notebooks: withDateEdited( group.notebooks.map((id) => resolved.notebooks[id]) ) - }; + }); } return data; } diff --git a/apps/web/src/components/virtualized-list/index.tsx b/apps/web/src/components/virtualized-list/index.tsx index 2d0035f1e..242ca0947 100644 --- a/apps/web/src/components/virtualized-list/index.tsx +++ b/apps/web/src/components/virtualized-list/index.tsx @@ -34,6 +34,7 @@ export type VirtualizedListProps = { renderItem: (props: { item: T; index: number }) => JSX.Element | null; scrollMargin?: number; itemGap?: number; + overscan?: number; } & BoxProps; export function VirtualizedList(props: VirtualizedListProps) { const { @@ -47,6 +48,7 @@ export function VirtualizedList(props: VirtualizedListProps) { virtualizerRef, itemWrapperProps, itemGap, + overscan = 5, ...containerProps } = props; const containerRef = useRef(null); @@ -58,7 +60,7 @@ export function VirtualizedList(props: VirtualizedListProps) { getScrollElement: () => scrollElement || containerRef.current?.closest(".ms-container") || null, scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0, - overscan: 5 + overscan }); if (virtualizerRef) virtualizerRef.current = virtualizer; diff --git a/apps/web/src/views/all-notes.tsx b/apps/web/src/views/all-notes.tsx index a1418e880..91a61107b 100644 --- a/apps/web/src/views/all-notes.tsx +++ b/apps/web/src/views/all-notes.tsx @@ -42,6 +42,23 @@ function Home() { useStore.getState().refresh(); }, []); + // useEffect(() => { + // (async function () { + + // // const titles = + // // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""); + // // for (let i = 0; i < 10000; ++i) { + // // await db.notes.add({ + // // title: `${ + // // titles[getRandom(0, titles.length)] + // // } Some other title of mine` + // // }); + // // if (i % 100 === 0) console.log(i); + // // } + // // console.log("DONE"); + // })(); + // }, []); + if (!notes) return ; return ( . import { EVENTS } from "../common"; import { - GroupHeader, GroupOptions, Item, MaybeDeletedItem, diff --git a/packages/ui/src/components/menu/index.tsx b/packages/ui/src/components/menu/index.tsx index ad79611b7..dc966fd1c 100644 --- a/packages/ui/src/components/menu/index.tsx +++ b/packages/ui/src/components/menu/index.tsx @@ -207,7 +207,9 @@ function MenuContainer(props: PropsWithChildren) { {title} )} - {children} + + {children} + {/* {children} */} ); @@ -264,7 +266,15 @@ function LazyLoader(props: { })(); }, [item]); - return isLoading ? <> : <>{items.map(mapper)}; + return isLoading ? ( + item.loader ? ( + <>{item.loader} + ) : ( + <> + ) + ) : ( + <>{items.map(mapper)} + ); } export * from "./types"; diff --git a/packages/ui/src/components/menu/types.ts b/packages/ui/src/components/menu/types.ts index baa892a29..a2ef155f9 100644 --- a/packages/ui/src/components/menu/types.ts +++ b/packages/ui/src/components/menu/types.ts @@ -38,6 +38,7 @@ export type MenuPopupItem = BaseMenuItem<"popup"> & { }; export type LazyMenuItemsLoader = BaseMenuItem<"lazy-loader"> & { + loader?: React.ReactNode; items: () => Promise; };