From ad142630d3fb6665ea02b0ae5c086ac954a41b8a Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 16 Jan 2025 16:20:52 -0700 Subject: [PATCH 1/5] create virtua list component --- web/package-lock.json | 34 ++++- web/package.json | 3 +- web/src/app/test/list/page.tsx | 55 +++++++ .../list/BusterList/BusterListReactWindow.tsx | 4 +- .../list/BusterList/BusterListVirtua.tsx | 134 ++++++++++++++++++ web/src/components/list/BusterList/index.ts | 5 +- .../list/BusterList/useListContextMenu.tsx | 45 ++++++ 7 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 web/src/app/test/list/page.tsx create mode 100644 web/src/components/list/BusterList/BusterListVirtua.tsx create mode 100644 web/src/components/list/BusterList/useListContextMenu.tsx diff --git a/web/package-lock.json b/web/package-lock.json index d8b09ce91..09a270737 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -93,7 +93,8 @@ "split-pane-react": "^0.1.3", "tailwind-merge": "^2.6.0", "utility-types": "^3.11.0", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "virtua": "^0.39.3" }, "devDependencies": { "@types/canvas-confetti": "^1.9.0", @@ -584,7 +585,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -15886,6 +15886,36 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/virtua": { + "version": "0.39.3", + "resolved": "https://registry.npmjs.org/virtua/-/virtua-0.39.3.tgz", + "integrity": "sha512-Ep3aiJXSGPm1UUniThr5mGDfG0upAleP7pqQs5mvvCgM1wPhII1ZKa7eNCWAJRLkC+InpXKokKozyaaj/aMYOQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0", + "solid-js": ">=1.0", + "svelte": ">=5.0", + "vue": ">=3.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", diff --git a/web/package.json b/web/package.json index a0b746d5d..ecabd5b56 100644 --- a/web/package.json +++ b/web/package.json @@ -98,7 +98,8 @@ "split-pane-react": "^0.1.3", "tailwind-merge": "^2.6.0", "utility-types": "^3.11.0", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "virtua": "^0.39.3" }, "devDependencies": { "@types/canvas-confetti": "^1.9.0", diff --git a/web/src/app/test/list/page.tsx b/web/src/app/test/list/page.tsx new file mode 100644 index 000000000..7fb6b8bb2 --- /dev/null +++ b/web/src/app/test/list/page.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { BusterListColumn, BusterListRow } from '@/components/list'; +import { BusterListVirtua } from '@/components/list/BusterList/BusterListVirtua'; +import { faker } from '@faker-js/faker'; +import { useEffect, useState } from 'react'; + +const generateRows = (numberToGenerate: number): BusterListRow[] => { + return Array.from({ length: numberToGenerate }, (_, index) => ({ + id: faker.string.uuid(), + data: { + name: faker.person.fullName() + ` ${index}`, + email: faker.internet.email(), + phone: faker.phone.number() + } + })); +}; +const rows: BusterListRow[] = Array.from({ length: 10 }, (_, index) => { + const rows = generateRows(faker.number.int({ min: 10, max: 10 })); + return [ + { + id: `section-${index + 1}`, + rowSection: { + title: `Section ${index + 1}`, + secondaryTitle: rows.length.toString() + }, + data: {} + }, + ...rows + ]; +}).flat(); + +const columns: BusterListColumn[] = [ + { title: 'Name', dataIndex: 'name' }, + { title: 'Email', dataIndex: 'email' }, + { title: 'Phone', dataIndex: 'phone' } +]; + +export default function ListTest() { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setTimeout(() => { + setMounted(true); + }, 100); + }, []); + + if (!mounted) return null; + + return ( +
+ +
+ ); +} diff --git a/web/src/components/list/BusterList/BusterListReactWindow.tsx b/web/src/components/list/BusterList/BusterListReactWindow.tsx index ad8c05d02..6c849ebf1 100644 --- a/web/src/components/list/BusterList/BusterListReactWindow.tsx +++ b/web/src/components/list/BusterList/BusterListReactWindow.tsx @@ -9,7 +9,7 @@ import { getAllIdsInSection } from './helpers'; import { BusterListHeader } from './BusterListHeader'; import { BusterListRowComponentSelector } from './BusterListRowComponentSelector'; -export const BusterList: React.FC = ({ +export const BusterListReactWindow: React.FC = ({ columns, rows, selectedRowKeys, @@ -194,5 +194,5 @@ export const BusterList: React.FC = ({ ); }; -BusterList.displayName = 'BusterList'; +BusterListReactWindow.displayName = 'BusterList'; // Add a memoized checkbox component diff --git a/web/src/components/list/BusterList/BusterListVirtua.tsx b/web/src/components/list/BusterList/BusterListVirtua.tsx new file mode 100644 index 000000000..09e008b31 --- /dev/null +++ b/web/src/components/list/BusterList/BusterListVirtua.tsx @@ -0,0 +1,134 @@ +import { VList } from 'virtua'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { BusterListProps } from './interfaces'; +import { useMemoizedFn } from 'ahooks'; +import { getAllIdsInSection } from './helpers'; +import { HEIGHT_OF_ROW, HEIGHT_OF_SECTION_ROW } from './config'; +import { useListContextMenu } from './useListContextMenu'; +import { BusterListHeader } from './BusterListHeader'; +import { BusterListContentMenu } from './BusterListContentMenu'; +import { BusterListRowComponentSelector } from './BusterListRowComponentSelector'; + +export const BusterListVirtua = React.memo( + ({ + columns, + rows, + selectedRowKeys, + onSelectChange, + emptyState, + showHeader = true, + contextMenu, + showSelectAll = true + }: BusterListProps) => { + const contextMenuRef = useRef(null); + const showEmptyState = (!rows || rows.length === 0) && !!emptyState; + + const globalCheckStatus = useMemo(() => { + if (!selectedRowKeys) return 'unchecked'; + if (selectedRowKeys.length === 0) return 'unchecked'; + if (selectedRowKeys.length === rows.length) return 'checked'; + return 'indeterminate'; + }, [selectedRowKeys?.length, rows.length]); + + const { contextMenuPosition, setContextMenuPosition, onContextMenuClick } = useListContextMenu({ + contextMenu + }); + + const onGlobalSelectChange = useMemoizedFn((v: boolean) => { + onSelectChange?.(v ? rows.map((row) => row.id) : []); + }); + + const onSelectSectionChange = useMemoizedFn((v: boolean, id: string) => { + if (!onSelectChange) return; + const idsInSection = getAllIdsInSection(rows, id); + + if (v === false) { + onSelectChange(selectedRowKeys?.filter((d) => !idsInSection.includes(d)) || []); + } else { + onSelectChange(selectedRowKeys?.concat(idsInSection) || []); + } + }); + + const onSelectChangePreflight = useMemoizedFn((v: boolean, id: string) => { + if (!onSelectChange || !selectedRowKeys) return; + if (v === false) { + onSelectChange(selectedRowKeys?.filter((d) => d !== id)); + } else { + onSelectChange(selectedRowKeys?.concat(id) || []); + } + }); + + const itemSize = useMemoizedFn((index: number) => { + const row = rows[index]; + return row.rowSection ? HEIGHT_OF_SECTION_ROW : HEIGHT_OF_ROW; + }); + + const itemData = useMemo(() => { + return { + columns, + rows, + selectedRowKeys, + onSelectChange: onSelectChange ? onSelectChangePreflight : undefined, + onSelectSectionChange: onSelectChange ? onSelectSectionChange : undefined, + onContextMenuClick + }; + }, [columns, rows, selectedRowKeys, onSelectChange, onSelectSectionChange, onContextMenuClick]); + + useEffect(() => { + if (contextMenu && contextMenuPosition?.show) { + const listenForClickAwayFromContextMenu = (e: MouseEvent) => { + if (!contextMenuRef.current?.contains(e.target as Node)) { + setContextMenuPosition((v) => ({ + ...v!, + show: false + })); + } + }; + document.addEventListener('click', listenForClickAwayFromContextMenu); + return () => { + document.removeEventListener('click', listenForClickAwayFromContextMenu); + }; + } + }, [contextMenuRef, contextMenuPosition?.show, contextMenu]); + + return ( +
+ {showHeader && !showEmptyState && ( + + )} + + {!showEmptyState && ( + + {rows.map((row, index) => ( +
+ +
+ ))} +
+ )} + + {showEmptyState && ( +
{emptyState}
+ )} + + {contextMenu && contextMenuPosition?.id && ( + + )} +
+ ); + } +); + +BusterListVirtua.displayName = 'BusterListVirtua'; diff --git a/web/src/components/list/BusterList/index.ts b/web/src/components/list/BusterList/index.ts index 84d85002e..336c3d6b9 100644 --- a/web/src/components/list/BusterList/index.ts +++ b/web/src/components/list/BusterList/index.ts @@ -1,3 +1,6 @@ -export * from './BusterListReactWindow'; export * from './interfaces'; export * from './BusterListSelectedOptionPopup'; + +import { BusterListVirtua } from './BusterListVirtua'; + +export { BusterListVirtua as BusterList }; diff --git a/web/src/components/list/BusterList/useListContextMenu.tsx b/web/src/components/list/BusterList/useListContextMenu.tsx new file mode 100644 index 000000000..d1502ad23 --- /dev/null +++ b/web/src/components/list/BusterList/useListContextMenu.tsx @@ -0,0 +1,45 @@ +import { useMemoizedFn } from 'ahooks'; +import { useState } from 'react'; +import { BusterListContextMenu } from './interfaces'; +import React from 'react'; + +export const useListContextMenu = ({ contextMenu }: { contextMenu?: BusterListContextMenu }) => { + const [contextMenuPosition, setContextMenuPosition] = useState<{ + x: number; + y: number; + scrollYPosition: number; + show: boolean; + id: string; + } | null>(null); + + const onContextMenuClick = useMemoizedFn((e: React.MouseEvent, id: string) => { + if (!contextMenu) return; + e.stopPropagation(); + e.preventDefault(); + const x = e.clientX - 5; + const y = e.clientY - 5; // offset the top by 30px + const menuWidth = 250; // width of the menu + const menuHeight = 200; // height of the menu + const pageWidth = window.innerWidth; + const pageHeight = window.innerHeight; + + // Ensure the menu does not render offscreen horizontally + const adjustedX = Math.min(Math.max(0, x), pageWidth - menuWidth); + // Ensure the menu does not render offscreen vertically, considering the offset + const adjustedY = Math.min(Math.max(0, y), pageHeight - menuHeight); + + setContextMenuPosition({ + x: adjustedX, + y: adjustedY, + show: true, + id: id, + scrollYPosition: window.scrollY + }); + }); + + return { + contextMenuPosition, + onContextMenuClick, + setContextMenuPosition + }; +}; From 30c2ec5af62e1cd64765beb948db82fc0cacae71 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 16 Jan 2025 16:22:10 -0700 Subject: [PATCH 2/5] only debounce if there is text --- .../datasets/[datasetId]/permissions/useDebounceSearch.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/web/src/app/app/datasets/[datasetId]/permissions/useDebounceSearch.ts b/web/src/app/app/datasets/[datasetId]/permissions/useDebounceSearch.ts index 275e298f3..9b4c2af0d 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/useDebounceSearch.ts +++ b/web/src/app/app/datasets/[datasetId]/permissions/useDebounceSearch.ts @@ -27,12 +27,16 @@ export const useDebounceSearch = ({ items, searchPredicate }: UseDebounceSear (text: string) => { updateFilteredItems(text); }, - { wait: 200 } + { wait: 150 } ); const handleSearchChange = useMemoizedFn((text: string) => { setSearchText(text); - debouncedSearch(text); + if (!text) { + updateFilteredItems(text); + } else { + debouncedSearch(text); + } }); useEffect(() => { From 33bcd9f34ea1e0c9917e9bfe4c2f271f1e683d89 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 16 Jan 2025 16:40:02 -0700 Subject: [PATCH 3/5] prefetch on demand --- .../datasets/permissions/queryRequests.ts | 8 +- web/src/api/createReactQuery.ts | 3 + .../permissions/PermissionAppSegments.tsx | 89 ++++++++++++++----- .../permissions/PermissionsAppContainer.tsx | 6 +- .../PermissionDatasetGroups.tsx | 2 +- ...PermissionListPermissionGroupContainer.tsx | 16 ++-- 6 files changed, 92 insertions(+), 32 deletions(-) diff --git a/web/src/api/busterv2/datasets/permissions/queryRequests.ts b/web/src/api/busterv2/datasets/permissions/queryRequests.ts index 7087eb24a..a45b9f008 100644 --- a/web/src/api/busterv2/datasets/permissions/queryRequests.ts +++ b/web/src/api/busterv2/datasets/permissions/queryRequests.ts @@ -46,7 +46,7 @@ export const useDatasetListPermissionGroups = (dataset_id: string) => { return useCreateReactQuery({ queryKey: [PERMISSION_GROUP_QUERY_KEY, dataset_id], queryFn, - staleTime: 1000 * 5 // 5 seconds + enabled: !!dataset_id }); }; @@ -81,7 +81,8 @@ export const useDatasetListDatasetGroups = (dataset_id: string) => { return useCreateReactQuery({ queryKey: [LIST_DATASET_GROUPS_QUERY_KEY, dataset_id], - queryFn + queryFn, + enabled: !!dataset_id }); }; @@ -90,7 +91,8 @@ export const useDatasetListPermissionUsers = (dataset_id: string) => { return useCreateReactQuery({ queryKey: ['list_permission_users', dataset_id], - queryFn + queryFn, + enabled: !!dataset_id }); }; diff --git a/web/src/api/createReactQuery.ts b/web/src/api/createReactQuery.ts index dd1eb003a..d90955b1f 100644 --- a/web/src/api/createReactQuery.ts +++ b/web/src/api/createReactQuery.ts @@ -27,6 +27,8 @@ interface CreateQueryProps extends UseQueryOptions { useErrorNotification?: boolean; } +export const STALE_TIME = 1000 * 10; + export const useCreateReactQuery = ({ queryKey, queryFn, @@ -50,6 +52,7 @@ export const useCreateReactQuery = ({ retry: 1, refetchOnWindowFocus, refetchOnMount, + staleTime: STALE_TIME, ...rest }); diff --git a/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx b/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx index 6488d649e..5e1f3b708 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx @@ -1,39 +1,64 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { AppSegmented } from '@/components'; import { PermissionApps } from './config'; -import { useMemoizedFn } from 'ahooks'; +import { useMemoizedFn, useSet } from 'ahooks'; import { SegmentedValue } from 'antd/es/segmented'; import { Divider } from 'antd'; - -const options: { label: string; value: PermissionApps }[] = [ - { - label: 'Overview', - value: PermissionApps.OVERVIEW - }, - { - label: 'Permission Groups', - value: PermissionApps.PERMISSION_GROUPS - }, - { - label: 'Dataset Groups', - value: PermissionApps.DATASET_GROUPS - }, - { - label: 'Users', - value: PermissionApps.USERS - } -]; +import { + useDatasetListDatasetGroups, + useDatasetListPermissionGroups, + useDatasetListPermissionUsers +} from '@/api/busterv2'; export const PermissionAppSegments: React.FC<{ selectedApp: PermissionApps; setSelectedApp: (app: PermissionApps) => void; -}> = React.memo(({ selectedApp, setSelectedApp }) => { + datasetId: string; +}> = React.memo(({ selectedApp, setSelectedApp, datasetId }) => { + const [prefetchedRoutes, setPrefetchedRoutes] = useSet(); + + useDatasetListDatasetGroups(prefetchedRoutes.has(PermissionApps.DATASET_GROUPS) ? datasetId : ''); + useDatasetListPermissionUsers(prefetchedRoutes.has(PermissionApps.USERS) ? datasetId : ''); + useDatasetListPermissionGroups( + prefetchedRoutes.has(PermissionApps.PERMISSION_GROUPS) ? datasetId : '' + ); + const handleSelect = useMemoizedFn((app: SegmentedValue) => { setSelectedApp(app as PermissionApps); }); + const onHoverRoute = useMemoizedFn((route: string) => { + setPrefetchedRoutes.add(route); + }); + + const options = React.useMemo( + () => + [ + { + label: 'Overview', + value: PermissionApps.OVERVIEW + }, + { + label: 'Permission Groups', + value: PermissionApps.PERMISSION_GROUPS + }, + { + label: 'Dataset Groups', + value: PermissionApps.DATASET_GROUPS + }, + { + label: 'Users', + value: PermissionApps.USERS + } + ].map((option) => ({ + ...option, + label: + })), + [] + ); + return (
@@ -43,3 +68,23 @@ export const PermissionAppSegments: React.FC<{ }); PermissionAppSegments.displayName = 'PermissionAppSegments'; + +const PrefetchRouteSegmentItem = React.memo( + ({ + value, + label, + onHover + }: { + value: PermissionApps; + label: string; + onHover: (route: PermissionApps) => void; + }) => { + return ( + onHover(value)}> + {label} + + ); + } +); + +PrefetchRouteSegmentItem.displayName = 'PrefetchRouteSegmentItem'; diff --git a/web/src/app/app/datasets/[datasetId]/permissions/PermissionsAppContainer.tsx b/web/src/app/app/datasets/[datasetId]/permissions/PermissionsAppContainer.tsx index df36d47eb..c0893f1d6 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/PermissionsAppContainer.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/PermissionsAppContainer.tsx @@ -30,7 +30,11 @@ export const PermissionsAppContainer: React.FC<{ datasetId: string }> = React.me return ( <> - + diff --git a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionDatasetGroups/PermissionDatasetGroups.tsx b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionDatasetGroups/PermissionDatasetGroups.tsx index b8aba8226..2f8addc38 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionDatasetGroups/PermissionDatasetGroups.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionDatasetGroups/PermissionDatasetGroups.tsx @@ -1,4 +1,4 @@ -import { useDatasetListDatasetGroups, useListDatasetGroups } from '@/api/busterv2'; +import { useDatasetListDatasetGroups } from '@/api/busterv2'; import React, { useState } from 'react'; import { useDebounceSearch } from '../useDebounceSearch'; import { useMemoizedFn } from 'ahooks'; diff --git a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx index bfc8afe0d..1b92bb39d 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx @@ -119,11 +119,7 @@ export const PermissionListPermissionGroupContainer: React.FC<{ showSelectAll={false} selectedRowKeys={selectedRowKeys} onSelectChange={setSelectedRowKeys} - emptyState={ -
- No permission groups found -
- } + emptyState={} />
); @@ -180,3 +176,13 @@ const PermissionGroupAssignedCell = React.memo( ); PermissionGroupAssignedCell.displayName = 'PermissionGroupAssignedCell'; + +const EmptyState = React.memo(() => { + return ( +
+ No permission groups found +
+ ); +}); + +EmptyState.displayName = 'EmptyState'; From 7e9daac4772d8417ada7667e5ed959736a652b03 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 16 Jan 2025 17:24:15 -0700 Subject: [PATCH 4/5] add a popup for permissions --- .../PermissionGroupSelectedPopup.tsx | 31 +++++++++++++++++++ ...PermissionListPermissionGroupContainer.tsx | 15 +++++++-- .../PermissionPermissionGroup.tsx | 6 ++-- .../datasets/[datasetId]/permissions/page.tsx | 2 +- .../BusterInfiniteList/BusterInfiniteList.tsx | 6 ++-- ...BusterListSelectedOptionPopupContainer.tsx | 8 +++-- 6 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionGroupSelectedPopup.tsx diff --git a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionGroupSelectedPopup.tsx b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionGroupSelectedPopup.tsx new file mode 100644 index 000000000..70e5c979f --- /dev/null +++ b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionGroupSelectedPopup.tsx @@ -0,0 +1,31 @@ +import { BusterListSelectedOptionPopupContainer } from '@/components/list'; +import { Button } from 'antd'; +import React from 'react'; + +export const PermissionGroupSelectedPopup: React.FC<{ + selectedRowKeys: string[]; + onSelectChange: (selectedRowKeys: string[]) => void; +}> = React.memo(({ selectedRowKeys, onSelectChange }) => { + return ( + + ]} + /> + ); +}); + +PermissionGroupSelectedPopup.displayName = 'PermissionGroupSelectedPopup'; + +const PermissionGroupAssignButton: React.FC<{ + selectedRowKeys: string[]; + onSelectChange: (selectedRowKeys: string[]) => void; +}> = ({ selectedRowKeys, onSelectChange }) => { + return ; +}; diff --git a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx index 1b92bb39d..af6598322 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionListPermissionGroupContainer.tsx @@ -3,12 +3,13 @@ import { useDatasetUpdatePermissionGroups } from '@/api/busterv2/datasets'; import { BusterListColumn, BusterListRowItem } from '@/components/list'; -import { BusterInfiniteList } from '@/components/list/BusterInfiniteList'; import { useMemoizedFn } from 'ahooks'; import { Select } from 'antd'; import { createStyles } from 'antd-style'; import React, { useMemo, useState } from 'react'; import { Text } from '@/components/text'; +import { PermissionGroupSelectedPopup } from './PermissionGroupSelectedPopup'; +import { BusterInfiniteList } from '@/components/list/BusterInfiniteList'; export const PermissionListPermissionGroupContainer: React.FC<{ filteredPermissionGroups: ListPermissionGroupsResponse[]; @@ -111,7 +112,7 @@ export const PermissionListPermissionGroupContainer: React.FC<{ ); return ( -
+
} /> + +
+
+ +
+
); }); @@ -131,7 +141,6 @@ const useStyles = createStyles(({ css, token }) => ({ container: css` border: 0.5px solid ${token.colorBorder}; border-radius: ${token.borderRadius}px; - overflow: hidden; ` })); diff --git a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionPermissionGroup.tsx b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionPermissionGroup.tsx index 9d289b9cd..1c1d8cc3a 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionPermissionGroup.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/_PermissionPermissionGroup/PermissionPermissionGroup.tsx @@ -30,13 +30,13 @@ export const PermissionPermissionGroup: React.FC<{ }); return ( - <> +
-
+
- +
); }); diff --git a/web/src/app/app/datasets/[datasetId]/permissions/page.tsx b/web/src/app/app/datasets/[datasetId]/permissions/page.tsx index 8a276e95b..a3fc30a79 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/page.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/page.tsx @@ -10,7 +10,7 @@ export default async function Page({ params }: { params: { datasetId: string } } return ( -
+
diff --git a/web/src/components/list/BusterInfiniteList/BusterInfiniteList.tsx b/web/src/components/list/BusterInfiniteList/BusterInfiniteList.tsx index f628f3033..6d2410b15 100644 --- a/web/src/components/list/BusterInfiniteList/BusterInfiniteList.tsx +++ b/web/src/components/list/BusterInfiniteList/BusterInfiniteList.tsx @@ -104,7 +104,7 @@ export const BusterInfiniteList: React.FC = ({ return (
{showHeader && !showEmptyState && ( = ({ )} {!showEmptyState && ( -
+ <> {rows.map((row) => ( ))} -
+ )} {showEmptyState && ( diff --git a/web/src/components/list/BusterList/BusterListSelectedOptionPopup/BusterListSelectedOptionPopupContainer.tsx b/web/src/components/list/BusterList/BusterListSelectedOptionPopup/BusterListSelectedOptionPopupContainer.tsx index 14b5bdef9..546438f21 100644 --- a/web/src/components/list/BusterList/BusterListSelectedOptionPopup/BusterListSelectedOptionPopupContainer.tsx +++ b/web/src/components/list/BusterList/BusterListSelectedOptionPopup/BusterListSelectedOptionPopupContainer.tsx @@ -8,11 +8,13 @@ import { AnimatePresence, motion } from 'framer-motion'; export const BusterListSelectedOptionPopupContainer: React.FC<{ selectedRowKeys: string[]; onSelectChange: (selectedRowKeys: string[]) => void; - buttons: React.ReactNode[]; - show: boolean; -}> = ({ selectedRowKeys, onSelectChange, buttons = [], show }) => { + buttons?: React.ReactNode[]; + show?: boolean; +}> = ({ selectedRowKeys, onSelectChange, buttons = [], show: showProp }) => { const token = useAntToken(); + const show = showProp ?? selectedRowKeys.length > 0; + return ( {show && ( From 48436e5c1883ecb968fb93e7fd99fade26ba6ff4 Mon Sep 17 00:00:00 2001 From: Nate Kelley <133379588+nate-kelley-buster@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:55:16 -0800 Subject: [PATCH 5/5] Update web/src/components/list/BusterList/BusterListReactWindow.tsx Co-Authored-By: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- .../[datasetId]/permissions/PermissionAppSegments.tsx | 4 ++-- web/src/components/list/BusterList/BusterListReactWindow.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx b/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx index 5e1f3b708..4d6480256 100644 --- a/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx +++ b/web/src/app/app/datasets/[datasetId]/permissions/PermissionAppSegments.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState } from 'react'; +import React from 'react'; import { AppSegmented } from '@/components'; import { PermissionApps } from './config'; import { useMemoizedFn, useSet } from 'ahooks'; @@ -10,7 +10,7 @@ import { useDatasetListDatasetGroups, useDatasetListPermissionGroups, useDatasetListPermissionUsers -} from '@/api/busterv2'; +} from '@/api/buster-rest'; export const PermissionAppSegments: React.FC<{ selectedApp: PermissionApps; diff --git a/web/src/components/list/BusterList/BusterListReactWindow.tsx b/web/src/components/list/BusterList/BusterListReactWindow.tsx index 759c182ec..ba650a882 100644 --- a/web/src/components/list/BusterList/BusterListReactWindow.tsx +++ b/web/src/components/list/BusterList/BusterListReactWindow.tsx @@ -194,5 +194,5 @@ export const BusterListReactWindow: React.FC = ({
); }; -BusterListReactWindow.displayName = 'BusterList'; +BusterListReactWindow.displayName = 'BusterListReactWindow'; // Add a memoized checkbox component