Split DataSource & DataSourceView

Summary:
This diff is primarily cosmetic, just pushing code around to make the API more intuitive. Most importantly, DataSource was split into DataSource and DataSourceView classes, the latter being accessible through `datasource.view`.

The benefit of this is two fold:
1. Conceptually it is much clearer now which operations operate on the _source_ records, and which ones on the derived _view_.
2. This will make it easier in the future to support multiple views to be based on a single data source.

This refactoring also nicely found 2 cases where datasource logic and view logic were mixed.

The only semantic change in this diff is that both DataSource and DataSourceView are now iterable, so that one can do a `for (const record of ds)` / `for (const record of ds.view)`

Reviewed By: nikoant

Differential Revision: D26976838

fbshipit-source-id: 3726e92b3c6ee3417dc66cbbe6e288797eecf70e
This commit is contained in:
Michel Weststrate
2021-03-16 14:54:53 -07:00
committed by Facebook GitHub Bot
parent d73f6578a7
commit 602152665b
7 changed files with 611 additions and 466 deletions

View File

@@ -96,7 +96,7 @@ export const DataSourceRenderer: <T extends object, C>(
const parentRef = React.useRef<null | HTMLDivElement>(null);
const virtualizer = useVirtual({
size: dataSource.output.length,
size: dataSource.view.size,
parentRef,
useObserver: _testHeight
? () => ({height: _testHeight, width: 1000})
@@ -148,7 +148,7 @@ export const DataSourceRenderer: <T extends object, C>(
}
}
dataSource.setOutputChangeListener((event) => {
dataSource.view.setListener((event) => {
switch (event.type) {
case 'reset':
rerender(UpdatePrio.HIGH, true);
@@ -171,7 +171,7 @@ export const DataSourceRenderer: <T extends object, C>(
return () => {
unmounted = true;
dataSource.setOutputChangeListener(undefined);
dataSource.view.setListener(undefined);
};
},
[dataSource, setForceUpdate, useFixedRowHeight, _testHeight],
@@ -185,15 +185,15 @@ export const DataSourceRenderer: <T extends object, C>(
useLayoutEffect(function updateWindow() {
const start = virtualizer.virtualItems[0]?.index ?? 0;
const end = start + virtualizer.virtualItems.length;
if (start !== dataSource.windowStart && !followOutput.current) {
if (start !== dataSource.view.windowStart && !followOutput.current) {
onRangeChange?.(
start,
end,
dataSource.output.length,
dataSource.view.size,
parentRef.current?.scrollTop ?? 0,
);
}
dataSource.setWindow(start, end);
dataSource.view.setWindow(start, end);
});
/**
@@ -223,7 +223,7 @@ export const DataSourceRenderer: <T extends object, C>(
useLayoutEffect(function scrollToEnd() {
if (followOutput.current) {
virtualizer.scrollToIndex(
dataSource.output.length - 1,
dataSource.view.size - 1,
/* smooth is not typed by react-virtual, but passed on to the DOM as it should*/
{
align: 'end',
@@ -255,7 +255,7 @@ export const DataSourceRenderer: <T extends object, C>(
onKeyDown={onKeyDown}
tabIndex={0}>
{virtualizer.virtualItems.map((virtualRow) => {
const entry = dataSource.getEntry(virtualRow.index);
const value = dataSource.view.get(virtualRow.index);
// the position properties always change, so they are not part of the TableRow to avoid invalidating the memoized render always.
// Also all row containers are renderd as part of same component to have 'less react' framework code in between*/}
return (
@@ -270,7 +270,7 @@ export const DataSourceRenderer: <T extends object, C>(
transform: `translateY(${virtualRow.start}px)`,
}}
ref={useFixedRowHeight ? undefined : virtualRow.measureRef}>
{itemRenderer(entry.value, virtualRow.index, context)}
{itemRenderer(value, virtualRow.index, context)}
</div>
);
})}

View File

@@ -197,7 +197,7 @@ export function DataTable<T extends object>(
(e: React.KeyboardEvent<any>) => {
let handled = true;
const shiftPressed = e.shiftKey;
const outputSize = dataSource.output.length;
const outputSize = dataSource.view.size;
const windowSize = virtualizerRef.current!.virtualItems.length;
switch (e.key) {
case 'ArrowUp':
@@ -244,7 +244,7 @@ export function DataTable<T extends object>(
useEffect(
function updateFilter() {
dataSource.setFilter(
dataSource.view.setFilter(
computeDataTableFilter(state.searchValue, state.columns),
);
},
@@ -257,11 +257,11 @@ export function DataTable<T extends object>(
useEffect(
function updateSorting() {
if (state.sorting === undefined) {
dataSource.setSortBy(undefined);
dataSource.setReversed(false);
dataSource.view.setSortBy(undefined);
dataSource.view.setReversed(false);
} else {
dataSource.setSortBy(state.sorting.key);
dataSource.setReversed(state.sorting.direction === 'desc');
dataSource.view.setSortBy(state.sorting.key);
dataSource.view.setReversed(state.sorting.direction === 'desc');
}
},
[dataSource, state.sorting],
@@ -342,7 +342,7 @@ export function DataTable<T extends object>(
savePreferences(stateRef.current, lastOffset.current);
// if the component unmounts, we reset the SFRW pipeline to
// avoid wasting resources in the background
dataSource.reset();
dataSource.view.reset();
// clean ref
if (props.tableManagerRef) {
(props.tableManagerRef as MutableRefObject<any>).current = undefined;

View File

@@ -342,7 +342,7 @@ export function getSelectedItem<T>(
): T | undefined {
return selection.current < 0
? undefined
: dataSource.getItem(selection.current);
: dataSource.view.get(selection.current);
}
export function getSelectedItems<T>(
@@ -351,7 +351,7 @@ export function getSelectedItems<T>(
): T[] {
return [...selection.items]
.sort()
.map((i) => dataSource.getItem(i))
.map((i) => dataSource.view.get(i))
.filter(Boolean) as any[];
}