blob: 53e55b60543999a1528e13a81cec890c80a80eae [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { VariableSizeList as List } from 'react-window';
import { FlattenedItem, Folder } from './types';
import DatasourcePanelItem from './DatasourcePanelItem';
const BORDER_WIDTH = 2;
const HEADER_ITEM_HEIGHT = 50;
const METRIC_OR_COLUMN_ITEM_HEIGHT = 32;
const SUBTITLE_ITEM_HEIGHT = 32;
const DIVIDER_ITEM_HEIGHT = 16;
const flattenFolderStructure = (
foldersToFlatten: Folder[],
collapsedFolderIds: Set<string>,
depth = 0,
folderMap: Map<string, Folder> = new Map(),
): { flattenedItems: FlattenedItem[]; folderMap: Map<string, Folder> } => {
const flattenedItems: FlattenedItem[] = [];
foldersToFlatten.forEach((folder, idx) => {
folderMap.set(folder.id, folder);
flattenedItems.push({
type: 'header',
folderId: folder.id,
depth,
height: HEADER_ITEM_HEIGHT,
});
if (!collapsedFolderIds.has(folder.id)) {
flattenedItems.push({
type: 'subtitle',
folderId: folder.id,
depth,
height: SUBTITLE_ITEM_HEIGHT,
totalItems: folder.totalItems,
showingItems: folder.showingItems,
});
folder.items.forEach(item => {
flattenedItems.push({
type: 'item',
folderId: folder.id,
depth,
item,
height: METRIC_OR_COLUMN_ITEM_HEIGHT,
});
});
if (folder.subFolders && folder.subFolders.length > 0) {
const { flattenedItems: subItems } = flattenFolderStructure(
folder.subFolders,
collapsedFolderIds,
depth + 1,
folderMap,
);
flattenedItems.push(...subItems);
}
}
if (depth === 0 && idx !== foldersToFlatten.length - 1) {
flattenedItems.push({
type: 'divider',
folderId: folder.id,
depth,
height: DIVIDER_ITEM_HEIGHT,
});
}
});
return { flattenedItems, folderMap };
};
interface DatasourceItemsProps {
width: number;
height: number;
folders: Folder[];
}
export const DatasourceItems = ({
width,
height,
folders,
}: DatasourceItemsProps) => {
const listRef = useRef<List>(null);
const [collapsedFolderIds, setCollapsedFolderIds] = useState<Set<string>>(
new Set(
folders.filter(folder => folder.isCollapsed).map(folder => folder.id),
),
);
const { flattenedItems, folderMap } = useMemo(
() => flattenFolderStructure(folders, collapsedFolderIds),
[folders, collapsedFolderIds],
);
const handleToggleCollapse = useCallback((folderId: string) => {
setCollapsedFolderIds(prevIds => {
const newIds = new Set(prevIds);
if (newIds.has(folderId)) {
newIds.delete(folderId);
} else {
newIds.add(folderId);
}
return newIds;
});
}, []);
useEffect(() => {
// reset the list cache when flattenedItems length changes to recalculate the heights
listRef.current?.resetAfterIndex(0);
}, [flattenedItems]);
const getItemSize = useCallback(
(index: number) => flattenedItems[index].height,
[flattenedItems],
);
const itemData = useMemo(
() => ({
flattenedItems,
folderMap,
width,
onToggleCollapse: handleToggleCollapse,
collapsedFolderIds,
}),
[
flattenedItems,
folderMap,
width,
handleToggleCollapse,
collapsedFolderIds,
],
);
return (
<List
ref={listRef}
width={width - BORDER_WIDTH}
height={height}
itemSize={getItemSize}
itemCount={flattenedItems.length}
itemData={itemData}
overscanCount={5}
>
{DatasourcePanelItem}
</List>
);
};