web: changes to use new grouping api

This commit is contained in:
Abdullah Atta
2023-12-05 15:34:49 +05:00
parent 8b5370812c
commit f5ceabcaf5
9 changed files with 146 additions and 102 deletions

View File

@@ -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";
@@ -203,8 +204,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;
@@ -261,18 +262,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: <Loading sx={{ my: 2 }} />,
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: {

View File

@@ -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 : <Announcements />}
<VirtualizedList
virtualizerRef={listRef}
estimatedSize={DEFAULT_ITEM_HEIGHT}
estimatedSize={50}
getItemKey={(index) => 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 (
<GroupHeader
groupingKey={group}
refresh={refresh}
title={item.title}
isFocused={index === focusedGroupIndex}
index={index}
onSelectGroup={() => {
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 <div style={{ height: 50, width: "100%" }}></div>;
return (
<ListItemWrapper
key={item}
items={items}
id={item}
context={context}
group={group}
compact={compact}
/>
<>
{resolvedItem.group && group ? (
<GroupHeader
groupingKey={group}
refresh={refresh}
title={resolvedItem.group.title}
isFocused={index === focusedGroupIndex}
index={index}
onSelectGroup={() => {
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}
<ListItemWrapper
key={resolvedItem.item.id}
item={resolvedItem.item}
data={resolvedItem.data}
context={context}
group={group}
compact={compact}
/>
</>
);
}}
/>

View File

@@ -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<Item>;
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 <div style={{ height: DEFAULT_ITEM_HEIGHT, width: "100%" }} />;
const { data, item } = resolvedItem;
switch (item.type) {
case "note": {
return (

View File

@@ -37,7 +37,7 @@ import React from "react";
type ResolvedItemProps<TItemType extends ItemType> = {
type: TItemType;
items: VirtualizedGrouping<ItemMap[TItemType]>;
id: string;
index: number;
children: (item: {
item: ItemMap[TItemType];
data: unknown;
@@ -46,8 +46,11 @@ type ResolvedItemProps<TItemType extends ItemType> = {
export function ResolvedItem<TItemType extends ItemType>(
props: ResolvedItemProps<TItemType>
) {
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<TItemType extends ItemType>(
return <>{children(result.value)}</>;
}
export function useResolvedItem(props: Omit<ResolvedItemProps, "children">) {
const { id, items } = props;
const result = usePromise(() => items.item(id, resolveItems), [id, items]);
export function useResolvedItem(
props: Omit<ResolvedItemProps<ItemType>, "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<string, Item>) {
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<string, number> = {};
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<string, number> = {};
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<string, NoteResolvedData> = {};
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;
}

View File

@@ -34,6 +34,7 @@ export type VirtualizedListProps<T> = {
renderItem: (props: { item: T; index: number }) => JSX.Element | null;
scrollMargin?: number;
itemGap?: number;
overscan?: number;
} & BoxProps;
export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
const {
@@ -47,6 +48,7 @@ export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
virtualizerRef,
itemWrapperProps,
itemGap,
overscan = 5,
...containerProps
} = props;
const containerRef = useRef<HTMLDivElement>(null);
@@ -58,7 +60,7 @@ export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
getScrollElement: () =>
scrollElement || containerRef.current?.closest(".ms-container") || null,
scrollMargin: scrollMargin || containerRef.current?.offsetTop || 0,
overscan: 5
overscan
});
if (virtualizerRef) virtualizerRef.current = virtualizer;

View File

@@ -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 <Placeholder context="notes" />;
return (
<ListContainer

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { EVENTS } from "../common";
import {
GroupHeader,
GroupOptions,
Item,
MaybeDeletedItem,

View File

@@ -207,7 +207,9 @@ function MenuContainer(props: PropsWithChildren<MenuContainerProps>) {
{title}
</Text>
)}
<ScrollContainer suppressScrollX>{children}</ScrollContainer>
<ScrollContainer suppressScrollX style={{ maxHeight: 400 }}>
{children}
</ScrollContainer>
{/* <FlexScrollContainer>{children}</FlexScrollContainer> */}
</Box>
);
@@ -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";

View File

@@ -38,6 +38,7 @@ export type MenuPopupItem = BaseMenuItem<"popup"> & {
};
export type LazyMenuItemsLoader = BaseMenuItem<"lazy-loader"> & {
loader?: React.ReactNode;
items: () => Promise<MenuItem[]>;
};