replace ListView with VirtualizedList

This commit is contained in:
Jamie Pine
2022-09-04 12:46:10 -07:00
parent 8134959504
commit 7be22c96e5
3 changed files with 113 additions and 126 deletions

View File

@@ -18,11 +18,11 @@ import {
} from 'phosphor-react';
import React, { memo, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { FileList } from '../explorer/FileList';
import { Inspector } from '../explorer/Inspector';
import { WithContextMenu } from '../layout/MenuOverlay';
import { TopBar } from '../layout/TopBar';
import ExplorerContextMenu from './ExplorerContextMenu';
import { VirtualizedList } from './VirtualizedList';
interface Props {
data: ExplorerData;
@@ -39,37 +39,31 @@ export default function Explorer(props: Props) {
}
});
return (
<div className="relative">
<ExplorerContextMenu>
<ExplorerContent data={props.data} />
</ExplorerContextMenu>
</div>
);
}
const ExplorerContent = (props: Props) => {
const { selectedRowIndex, showInspector } = useExplorerStore((store) => ({
selectedRowIndex: store.selectedRowIndex,
showInspector: store.showInspector
}));
return (
<div className="relative flex flex-col w-full bg-gray-650">
<TopBar />
<div className="relative flex flex-row w-full max-h-full">
<FileList data={props.data?.items || []} context={props.data.context} />
{showInspector && (
<div className="min-w-[260px] max-w-[260px]">
{props.data.items[selectedRowIndex]?.id && (
<Inspector
key={props.data.items[selectedRowIndex].id}
data={props.data.items[selectedRowIndex]}
/>
<div className="relative">
<ExplorerContextMenu>
<div className="relative flex flex-col w-full bg-gray-650">
<TopBar />
<div className="relative flex flex-row w-full max-h-full">
<VirtualizedList data={props.data?.items || []} context={props.data.context} />
{showInspector && (
<div className="min-w-[260px] max-w-[260px]">
{props.data.items[selectedRowIndex]?.id && (
<Inspector
key={props.data.items[selectedRowIndex].id}
data={props.data.items[selectedRowIndex]}
/>
)}
</div>
)}
</div>
)}
</div>
</div>
</ExplorerContextMenu>
</div>
);
};
}

View File

@@ -1,7 +1,7 @@
import { ExplorerLayoutMode, useExplorerStore } from '@sd/client';
import { ExplorerContext, ExplorerItem, FilePath } from '@sd/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import React, { memo, useCallback, useLayoutEffect, useRef, useState } from 'react';
import React, { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useKey, useWindowSize } from 'rooks';
@@ -16,17 +16,47 @@ interface Props {
data: ExplorerItem[];
}
export const FileList: React.FC<Props> = (props) => {
// const size = useWindowSize();
const [goingUp, setGoingUp] = useState(false);
export const VirtualizedList: React.FC<Props> = ({ data, context }) => {
const scrollRef = useRef<HTMLDivElement>(null);
const innerRef = useRef<HTMLDivElement>(null);
const { selectedRowIndex, layoutMode } = useExplorerStore((state) => ({
selectedRowIndex: state.selectedRowIndex,
layoutMode: state.layoutMode
}));
const [goingUp, setGoingUp] = useState(false);
const [width, setWidth] = useState(0);
const { gridItemSize, layoutMode, listItemSize, selectedRowIndex } = useExplorerStore(
(state) => ({
selectedRowIndex: state.selectedRowIndex,
gridItemSize: state.gridItemSize,
layoutMode: state.layoutMode,
listItemSize: state.listItemSize
})
);
const set = useExplorerStore.getState().set;
useLayoutEffect(() => {
setWidth(innerRef.current?.offsetWidth || 0);
}, []);
const amountOfColumns = Math.floor(width / gridItemSize) || 8,
amountOfRows = layoutMode === 'grid' ? Math.ceil(data.length / amountOfColumns) : data.length,
itemSize = layoutMode === 'grid' ? gridItemSize + 25 : listItemSize;
const rowVirtualizer = useVirtualizer({
count: amountOfRows,
getScrollElement: () => scrollRef.current,
overscan: 500,
estimateSize: () => itemSize,
measureElement: (index) => itemSize
});
// useEffect(() => {
// if (selectedRowIndex === 0 && goingUp) rowVirtualizer.scrollToIndex(0);
// if (selectedRowIndex !== -1)
// rowVirtualizer.scrollToIndex(goingUp ? selectedRowIndex - 1 : selectedRowIndex);
// }, [goingUp, selectedRowIndex, rowVirtualizer]);
useKey('ArrowUp', (e) => {
e.preventDefault();
setGoingUp(true);
@@ -37,7 +67,7 @@ export const FileList: React.FC<Props> = (props) => {
useKey('ArrowDown', (e) => {
e.preventDefault();
setGoingUp(false);
if (selectedRowIndex !== -1 && selectedRowIndex !== (props.data.length ?? 1) - 1)
if (selectedRowIndex !== -1 && selectedRowIndex !== (data.length ?? 1) - 1)
set({ selectedRowIndex: selectedRowIndex + 1 });
});
@@ -65,94 +95,58 @@ export const FileList: React.FC<Props> = (props) => {
return (
<div style={{ marginTop: -TOP_BAR_HEIGHT }} className="w-full pl-2 cursor-default">
<Virtualizer items={props.data} />
</div>
);
};
function Virtualizer({ items }: { items: ExplorerItem[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const innerRef = useRef<HTMLDivElement>(null);
const { gridItemSize, layoutMode, listItemSize, selectedRowIndex } = useExplorerStore(
(state) => ({
selectedRowIndex: state.selectedRowIndex,
gridItemSize: state.gridItemSize,
layoutMode: state.layoutMode,
listItemSize: state.listItemSize
})
);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(innerRef.current?.offsetWidth || 0);
}, []);
const amountOfColumns = Math.floor(width / gridItemSize) || 8,
amountOfRows = layoutMode === 'grid' ? Math.ceil(items.length / amountOfColumns) : items.length,
itemSize = layoutMode === 'grid' ? gridItemSize + 25 : listItemSize;
const rowVirtualizer = useVirtualizer({
count: amountOfRows,
getScrollElement: () => parentRef.current,
overscan: 500,
estimateSize: () => itemSize,
measureElement: (index) => itemSize
});
return (
<div ref={parentRef} className="h-screen custom-scroll explorer-scroll">
<div
ref={innerRef}
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
marginTop: `${TOP_BAR_HEIGHT}px`
}}
className="relative w-full"
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
className="absolute top-0 left-0 flex w-full"
key={virtualRow.key}
>
{layoutMode === 'list' ? (
<WrappedItem
kind="list"
isSelected={selectedRowIndex === virtualRow.index}
index={virtualRow.index}
item={items[virtualRow.index]}
/>
) : (
[...Array(amountOfColumns)].map((_, i) => {
const index = virtualRow.index * amountOfColumns + i;
const item = items[index];
return (
<div key={index} className="w-32 h-32">
<div className="flex">
{item && (
<WrappedItem
kind="grid"
isSelected={selectedRowIndex === index}
index={index}
item={item}
/>
)}
<div ref={scrollRef} className="h-screen custom-scroll explorer-scroll">
<div
ref={innerRef}
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
marginTop: `${TOP_BAR_HEIGHT}px`
}}
className="relative w-full"
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
className="absolute top-0 left-0 flex w-full"
key={virtualRow.key}
>
{layoutMode === 'list' ? (
<WrappedItem
kind="list"
isSelected={selectedRowIndex === virtualRow.index}
index={virtualRow.index}
item={data[virtualRow.index]}
/>
) : (
[...Array(amountOfColumns)].map((_, i) => {
const index = virtualRow.index * amountOfColumns + i;
const item = data[index];
return (
<div key={index} className="w-32 h-32">
<div className="flex">
{item && (
<WrappedItem
kind="grid"
isSelected={selectedRowIndex === index}
index={index}
item={item}
/>
)}
</div>
</div>
</div>
);
})
)}
</div>
))}
);
})
)}
</div>
))}
</div>
</div>
</div>
);
}
};
interface WrappedItemProps {
item: ExplorerItem;
@@ -176,23 +170,22 @@ const WrappedItem: React.FC<WrappedItemProps> = memo(({ item, index, isSelected,
if (kind === 'list') {
return (
<FileRow
selected={isSelected}
data={item}
index={index}
onClick={onClick}
onDoubleClick={onDoubleClick}
index={index}
selected={isSelected}
/>
);
}
return (
<FileItem
onDoubleClick={onDoubleClick}
index={index}
data={item}
selected={isSelected}
index={index}
onClick={onClick}
// size={100}
onDoubleClick={onDoubleClick}
selected={isSelected}
/>
);
});

View File

@@ -1,5 +1,5 @@
import { LockClosedIcon, PhotoIcon } from '@heroicons/react/24/outline';
import { CogIcon, PlusIcon } from '@heroicons/react/24/solid';
import { CogIcon, LockClosedIcon, PhotoIcon } from '@heroicons/react/24/outline';
import { PlusIcon } from '@heroicons/react/24/solid';
import {
AppPropsContext,
useCurrentLibrary,