mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
web: improve list item rendering performance
This commit is contained in:
committed by
Abdullah Atta
parent
159c4454a3
commit
05ccfcd9fb
@@ -38,7 +38,7 @@ import {
|
||||
isGroupHeader
|
||||
} from "@notesnook/core";
|
||||
import { VirtualizedList } from "../virtualized-list";
|
||||
import { Virtualizer } from "@tanstack/react-virtual";
|
||||
import { ScrollToOptions, Virtualizer } from "@tanstack/react-virtual";
|
||||
import { useResolvedItem } from "./resolved-item";
|
||||
|
||||
type ListContainerProps = {
|
||||
@@ -138,64 +138,18 @@ function ListContainer(props: ListContainerProps) {
|
||||
onFocus: () => onFocus(index),
|
||||
onMouseDown: (e) => onMouseDown(e.nativeEvent, 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 (
|
||||
<>
|
||||
{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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
context={{
|
||||
items,
|
||||
group,
|
||||
refresh,
|
||||
focusedGroupIndex,
|
||||
selectItems: setSelectedItems,
|
||||
scrollToIndex: listRef.current?.scrollToIndex,
|
||||
focusGroup: setFocusedGroupIndex,
|
||||
context,
|
||||
compact
|
||||
}}
|
||||
renderItem={ItemRenderer}
|
||||
/>
|
||||
</FlexScrollContainer>
|
||||
</>
|
||||
@@ -227,6 +181,85 @@ function ListContainer(props: ListContainerProps) {
|
||||
}
|
||||
export default ListContainer;
|
||||
|
||||
type ListContext = {
|
||||
items: VirtualizedGrouping<Item>;
|
||||
group: GroupingKey | undefined;
|
||||
refresh: () => void;
|
||||
focusedGroupIndex: number;
|
||||
selectItems: (items: any) => void;
|
||||
scrollToIndex?: (
|
||||
index: number,
|
||||
options?: ScrollToOptions | undefined
|
||||
) => void;
|
||||
focusGroup: (index: number) => void;
|
||||
context?: Context;
|
||||
compact?: boolean;
|
||||
};
|
||||
function ItemRenderer({
|
||||
index,
|
||||
context
|
||||
}: {
|
||||
index: number;
|
||||
context: ListContext;
|
||||
}) {
|
||||
const {
|
||||
items,
|
||||
group,
|
||||
refresh,
|
||||
focusedGroupIndex,
|
||||
focusGroup,
|
||||
selectItems,
|
||||
scrollToIndex,
|
||||
context: itemContext,
|
||||
compact
|
||||
} = context;
|
||||
const resolvedItem = useResolvedItem({ index, items });
|
||||
if (!resolvedItem) return <div style={{ height: 50, width: "100%" }}></div>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{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 < items.ids.length; ++i) {
|
||||
if (typeof items.ids[i] === "object") {
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
selectItems([
|
||||
...selectionStore.get().selectedItems,
|
||||
...items.ids.slice(index, endIndex || items.ids.length)
|
||||
]);
|
||||
}}
|
||||
groups={async () => (items.groups ? items.groups() : [])}
|
||||
onJump={(index) => {
|
||||
scrollToIndex?.(index, {
|
||||
align: "center",
|
||||
behavior: "auto"
|
||||
});
|
||||
focusGroup(index);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<ListItemWrapper
|
||||
key={resolvedItem.item.id}
|
||||
item={resolvedItem.item}
|
||||
data={resolvedItem.data}
|
||||
context={itemContext}
|
||||
group={group}
|
||||
compact={compact}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the element at the specified index into view and
|
||||
* wait until it renders into the DOM. This function keeps
|
||||
|
||||
@@ -52,7 +52,7 @@ export function ResolvedItem<TItemType extends ItemType>(
|
||||
[index, items]
|
||||
);
|
||||
|
||||
if (result.status !== "fulfilled" || !result.value) return null;
|
||||
if (result.status === "rejected" || !result.value) return null;
|
||||
|
||||
if (result.value.item.type !== type) return null;
|
||||
return <>{children(result.value)}</>;
|
||||
@@ -67,7 +67,7 @@ export function useResolvedItem(
|
||||
[index, items]
|
||||
);
|
||||
|
||||
if (result.status !== "fulfilled" || !result.value) return null;
|
||||
if (result.status === "rejected" || !result.value) return null;
|
||||
return result.value;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { Box, BoxProps } from "@theme-ui/components";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
export type VirtualizedListProps<T> = {
|
||||
export type VirtualizedListProps<T, C> = {
|
||||
virtualizerRef?: React.MutableRefObject<
|
||||
Virtualizer<Element, Element> | undefined
|
||||
>;
|
||||
@@ -30,13 +30,18 @@ export type VirtualizedListProps<T> = {
|
||||
estimatedSize: number;
|
||||
getItemKey: (index: number, items: T[]) => string;
|
||||
scrollElement?: Element | null;
|
||||
context?: C;
|
||||
itemWrapperProps?: (item: T, index: number) => BoxProps;
|
||||
renderItem: (props: { item: T; index: number }) => JSX.Element | null;
|
||||
renderItem: (props: {
|
||||
item: T;
|
||||
index: number;
|
||||
context: C;
|
||||
}) => JSX.Element | null;
|
||||
scrollMargin?: number;
|
||||
itemGap?: number;
|
||||
overscan?: number;
|
||||
} & BoxProps;
|
||||
export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
|
||||
export function VirtualizedList<T, C>(props: VirtualizedListProps<T, C>) {
|
||||
const {
|
||||
items,
|
||||
getItemKey,
|
||||
@@ -49,6 +54,7 @@ export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
|
||||
itemWrapperProps,
|
||||
itemGap,
|
||||
overscan = 5,
|
||||
context,
|
||||
...containerProps
|
||||
} = props;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -99,7 +105,12 @@ export function VirtualizedList<T>(props: VirtualizedListProps<T>) {
|
||||
}px)`
|
||||
}}
|
||||
>
|
||||
<Item key={row.key} item={items[row.index]} index={row.index} />
|
||||
<Item
|
||||
key={row.key}
|
||||
item={items[row.index]}
|
||||
index={row.index}
|
||||
context={context || ({} as C)}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -20,11 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { DependencyList, useEffect, useState } from "react";
|
||||
|
||||
export type PromiseResult<T> =
|
||||
| PromisePendingResult
|
||||
| PromisePendingResult<T>
|
||||
| (PromiseSettledResult<T> & { refresh: () => void });
|
||||
|
||||
export interface PromisePendingResult {
|
||||
export interface PromisePendingResult<T> {
|
||||
status: "pending";
|
||||
value?: T;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +63,10 @@ export default function usePromise<T>(
|
||||
|
||||
useEffect(function effect() {
|
||||
if (result.status !== "pending") {
|
||||
setResult({ status: "pending" });
|
||||
setResult((s) => ({
|
||||
...s,
|
||||
status: "pending"
|
||||
}));
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
Reference in New Issue
Block a user