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