From 554441f3dfeefabb7a71df66088bc97ab5050ceb Mon Sep 17 00:00:00 2001 From: waynelwz <100347187+waynelwz@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:55:47 +0800 Subject: [PATCH] optimize(console): grid with extensive columns resulting in prolonged response times (#3114) --- console/package.json | 3 +- .../hooks/useDatastoreQueryParams.tsx | 2 +- .../src/store/StoreUpdater/index.tsx | 10 +- .../GridDatastoreTable/components/Preview.tsx | 4 +- .../hooks/useDatastoreColumns.tsx | 2 +- .../starwhale-ui/src/GridTable/GridTable.tsx | 10 +- .../components/ConfigViews/ConfigViews.tsx | 5 +- .../src/GridTable/hooks/useGrid.ts | 13 +- .../GridTable/hooks/useGridConfigColumns.tsx | 15 +-- .../GridTable/hooks/useGridCurrentView.tsx | 23 +--- .../src/GridTable/hooks/useGridData.ts | 44 ------ .../src/GridTable/hooks/useGridQuery.tsx | 23 ++-- .../src/GridTable/hooks/useGridSelection.ts | 5 +- .../src/GridTable/hooks/useGridSort.ts | 23 ---- .../src/GridTable/hooks/useStore.ts | 10 +- .../GridTable/store/StoreProvider/index.tsx | 9 +- .../GridTable/store/StoreUpdater/index.tsx | 55 ++++++-- .../starwhale-ui/src/GridTable/store/store.ts | 125 ++++++++++++++++-- .../starwhale-ui/src/Search/Search.tsx | 1 + .../starwhale-ui/src/Search/SearchState.tsx | 6 +- .../starwhale-ui/src/Viewer/DataViewer.tsx | 2 + .../src/base/data-table/data-custom-table.tsx | 3 +- .../base/data-table/headers/header-bar.tsx | 25 ++-- .../base/data-table/headers/header-cell.tsx | 16 +-- .../src/base/data-table/headers/header.tsx | 6 +- .../src/base/data-table/headers/headers.tsx | 28 ++-- .../base/data-table/inner-table-element.tsx | 23 ++-- .../base/data-table/measure-column-widths.tsx | 34 +++-- .../Dataset/DatasetVersionOverviewFiles.tsx | 10 +- console/vite.config.ts | 1 + console/yarn.lock | 5 + 31 files changed, 306 insertions(+), 235 deletions(-) delete mode 100644 console/packages/starwhale-ui/src/GridTable/hooks/useGridData.ts delete mode 100644 console/packages/starwhale-ui/src/GridTable/hooks/useGridSort.ts diff --git a/console/package.json b/console/package.json index 3839edca08..f1c17382c1 100644 --- a/console/package.json +++ b/console/package.json @@ -156,7 +156,8 @@ "xterm-addon-fit": "^0.5.0", "xterm-addon-web-links": "^0.4.0", "yarn": "^1.22.19", - "zustand": "v4.3.8" + "zustand": "v4.3.8", + "zustand-computed": "^1.3.7" }, "scripts": { "typecheck": "tsc --noEmit -p ./tsconfig.json", diff --git a/console/packages/starwhale-core/src/datastore/hooks/useDatastoreQueryParams.tsx b/console/packages/starwhale-core/src/datastore/hooks/useDatastoreQueryParams.tsx index 76c4c2db3a..c4b244f7e7 100644 --- a/console/packages/starwhale-core/src/datastore/hooks/useDatastoreQueryParams.tsx +++ b/console/packages/starwhale-core/src/datastore/hooks/useDatastoreQueryParams.tsx @@ -65,8 +65,8 @@ export function getQuery({ options, tableName }: TableQueryParamsT) { start, limit, rawResult: true, - ignoreNonExistingTable: true, encodeWithType: true, + ignoreNonExistingTable: true, } if (revision) { raw.revision = revision diff --git a/console/packages/starwhale-core/src/store/StoreUpdater/index.tsx b/console/packages/starwhale-core/src/store/StoreUpdater/index.tsx index 23b8f85f41..585e8d5787 100644 --- a/console/packages/starwhale-core/src/store/StoreUpdater/index.tsx +++ b/console/packages/starwhale-core/src/store/StoreUpdater/index.tsx @@ -50,16 +50,8 @@ const StoreUpdater = ({ initialState, onInit, }: StoreUpdaterProps) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // const { reset } = useStore(selector, shallow) const store = useStoreApi() - // useEffect(() => { - // return () => { - // // reset() - // } - // }, [reset]) - useDirectStoreUpdater('editable', editable, store.setState) useDirectStoreUpdater('panelGroup', panelGroup, store.setState) useDirectStoreUpdater('onStateChange', onStateChange, store.setState) @@ -67,7 +59,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('onEvalSectionDelete', onEvalSectionDelete, store.setState) useDirectStoreUpdater('onSave', onSave, store.setState) useDirectStoreUpdater('initialState', initialState, store.setState) - // + // init useDirectStoreUpdater('onInit', onInit, store.setState) return null diff --git a/console/packages/starwhale-ui/src/GridDatastoreTable/components/Preview.tsx b/console/packages/starwhale-ui/src/GridDatastoreTable/components/Preview.tsx index 25f99b9f76..075c4f315e 100644 --- a/console/packages/starwhale-ui/src/GridDatastoreTable/components/Preview.tsx +++ b/console/packages/starwhale-ui/src/GridDatastoreTable/components/Preview.tsx @@ -319,10 +319,10 @@ function TabControl({
Annotation Type
- {Array.from(annotationTypes).map((type: any) => { + {Array.from(annotationTypes).map((type: any, index) => { return ( { const { checked } = e.target diff --git a/console/packages/starwhale-ui/src/GridDatastoreTable/hooks/useDatastoreColumns.tsx b/console/packages/starwhale-ui/src/GridDatastoreTable/hooks/useDatastoreColumns.tsx index 74a7e13a36..614f7cf2f5 100644 --- a/console/packages/starwhale-ui/src/GridDatastoreTable/hooks/useDatastoreColumns.tsx +++ b/console/packages/starwhale-ui/src/GridDatastoreTable/hooks/useDatastoreColumns.tsx @@ -55,7 +55,6 @@ export function useDatastoreColumns( name, }) }) - return arr.sort(sortColumn) }, [columnTypes]) @@ -104,6 +103,7 @@ export function useDatastoreColumns( renderCell: RenderMixedCell as any, mapDataToValue: (record: Record): RecordAttr => { return RecordAttr.decode(record, name) + // return record }, // search bar getFilters, diff --git a/console/packages/starwhale-ui/src/GridTable/GridTable.tsx b/console/packages/starwhale-ui/src/GridTable/GridTable.tsx index d32d5893b6..862f624937 100644 --- a/console/packages/starwhale-ui/src/GridTable/GridTable.tsx +++ b/console/packages/starwhale-ui/src/GridTable/GridTable.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef } from 'react' +import React, { useRef } from 'react' import { Skeleton } from 'baseui/skeleton' import { areEqual } from 'react-window' import { useStyletron } from 'baseui' @@ -84,7 +84,6 @@ const selector = (state: IGridState) => ({ function GridTable({ isLoading, columns, - rows, rowActions, compareable = false, selectable = false, @@ -108,13 +107,6 @@ function GridTable({ const styles = useStyles({ theme }) const { onRemove, onIncludedRowsChange, onRowHighlightChange, getId } = useStore(selector) const store = useStoreApi() - // @FIXME inline set move to store updater ? - useDirectStoreUpdater( - 'getColumns', - useCallback(() => columns, [columns]), - store.setState - ) - useDirectStoreUpdater('rows', rows, store.setState) useDirectStoreUpdater('wrapperRef', wrapperRef, store.setState) const { diff --git a/console/packages/starwhale-ui/src/GridTable/components/ConfigViews/ConfigViews.tsx b/console/packages/starwhale-ui/src/GridTable/components/ConfigViews/ConfigViews.tsx index b7f5a6e68c..2eaa7ec3cb 100644 --- a/console/packages/starwhale-ui/src/GridTable/components/ConfigViews/ConfigViews.tsx +++ b/console/packages/starwhale-ui/src/GridTable/components/ConfigViews/ConfigViews.tsx @@ -14,7 +14,6 @@ import { useStore, useStoreApi } from '../../hooks/useStore' import { themedUseStyletron } from '@starwhale/ui/theme/styletron' import Button from '../../../Button' import { shallow } from 'zustand/shallow' -import useGrid from '../../hooks/useGrid' const ALLRUNS = 'all' @@ -23,16 +22,16 @@ const selector = (s: ITableState) => ({ views: s.views, viewEditing: s.viewEditing, viewModelShow: s.viewModelShow, + originalColumns: s.originalColumns, }) function ConfigViews() { const store = useStoreApi() - const { currentView, views, viewModelShow, viewEditing } = useStore(selector, shallow) + const { currentView, views, viewModelShow, viewEditing, originalColumns } = useStore(selector, shallow) const { onShowViewModel, onCurrentViewIdChange, checkDuplicateViewName, onViewUpdate, setViews } = store.getState() const [t] = useTranslation() const [isManageViewOpen, setIsManageViewOpen] = React.useState(false) const [selectId, setSelectId] = React.useState(currentView?.id ?? '') - const { originalColumns } = useGrid() useEffect(() => { if (currentView) { diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGrid.ts b/console/packages/starwhale-ui/src/GridTable/hooks/useGrid.ts index 8f98df133f..bab93e09bb 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGrid.ts +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useGrid.ts @@ -1,8 +1,6 @@ import useGridQueryText from './useGridQueryText' import useGridSave from './useGridSave' import useGridSelection from './useGridSelection' -import useGridSort from './useGridSort' -import useGirdData from './useGridData' import useGridQuery from './useGridQuery' import { IGridState } from '../types' import { useStore } from './useStore' @@ -13,14 +11,17 @@ import useGridCellPreview from './useGridCellPreview' const selector = (s: IGridState) => ({ initStore: s.initStore, + originalColumns: s.originalColumns, + rows: s.compute?.rows ?? [], + sortIndex: s.compute?.sortIndex, + sortDirection: s.currentView.sortDirection, + columns: s.compute?.columns ?? [], }) function useGrid() { - const { initStore } = useStore(selector, shallow) - const { rows, originalColumns } = useGirdData() - const { ids, isAllRuns, columns, currentView } = useGridCurrentView(originalColumns) + const { initStore, originalColumns, rows, columns, sortIndex, sortDirection } = useStore(selector, shallow) + const { ids, isAllRuns, currentView } = useGridCurrentView() const { onSave, onSaveAs, changed } = useGridSave() - const { sortIndex, sortDirection } = useGridSort() const { textQuery, setTextQuery } = useGridQueryText() const { selectedRowIds, diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridConfigColumns.tsx b/console/packages/starwhale-ui/src/GridTable/hooks/useGridConfigColumns.tsx index f97d7bdbfd..b9aec613f3 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridConfigColumns.tsx +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useGridConfigColumns.tsx @@ -2,18 +2,17 @@ import React from 'react' import { useStore } from './useStore' import { shallow } from 'zustand/shallow' import { IGridState } from '../types' -import useGirdData from './useGridData' import { ExtraPropsT, ConfigColumns, StatefulConfigColumns } from '../components/ConfigColumns' import useGridCurrentView from './useGridCurrentView' const selector = (state: IGridState) => ({ onCurrentViewColumnsChange: state.onCurrentViewColumnsChange, + columns: state.columns, }) function useGridConfigColumns() { - const { onCurrentViewColumnsChange } = useStore(selector, shallow) - const { originalColumns } = useGirdData() - const { currentView } = useGridCurrentView(originalColumns) + const { onCurrentViewColumnsChange, columns } = useStore(selector, shallow) + const { currentView } = useGridCurrentView() const renderStatefulConfigColumns = React.useCallback( (props?: ExtraPropsT) => { @@ -21,12 +20,12 @@ function useGridConfigColumns() { ) }, - [currentView, onCurrentViewColumnsChange, originalColumns] + [currentView, onCurrentViewColumnsChange, columns] ) const renderConfigColumns = React.useCallback( @@ -36,12 +35,12 @@ function useGridConfigColumns() { {...props} // @ts-ignore view={currentView} - columns={originalColumns} + columns={columns} onColumnsChange={onCurrentViewColumnsChange} /> ) }, - [currentView, onCurrentViewColumnsChange, originalColumns] + [currentView, onCurrentViewColumnsChange, columns] ) return { diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridCurrentView.tsx b/console/packages/starwhale-ui/src/GridTable/hooks/useGridCurrentView.tsx index 5376695e71..e42716197a 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridCurrentView.tsx +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useGridCurrentView.tsx @@ -1,16 +1,16 @@ -import _ from 'lodash' import React from 'react' -import { ColumnT, ConfigT } from '../../base/data-table/types' +import { ConfigT } from '../../base/data-table/types' import { useStore } from '@starwhale/ui/GridTable/hooks/useStore' import { ITableState } from '../store' const selector = (state: ITableState) => ({ currentView: state.currentView, views: state.views, + columns: state.columns ?? [], }) -function useGridCurrentView(columns: ColumnT[]) { - const { currentView: view } = useStore(selector) +function useGridCurrentView() { + const { currentView: view, columns } = useStore(selector) const columnIds = React.useMemo(() => { return columns.map((c) => c.key) @@ -27,20 +27,6 @@ function useGridCurrentView(columns: ColumnT[]) { return ids }, [view, columnIds]) - const $columns = React.useMemo(() => { - const { pinnedIds = [] }: ConfigT = view - const columnsMap = _.keyBy(columns, (c) => c.key) as Record - return $ids - .filter((id: any) => id in columnsMap) - .map((id: any) => { - const _pin = columnsMap[id].pin ?? undefined - return { - ...columnsMap[id], - pin: pinnedIds.includes(id) ? 'LEFT' : _pin, - } - }) as ColumnT[] - }, [view, columns, $ids]) - const $view = React.useMemo(() => { return { ...view, @@ -54,7 +40,6 @@ function useGridCurrentView(columns: ColumnT[]) { return { ids: $ids, - columns: $columns, currentView: $view, isAllRuns, } diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridData.ts b/console/packages/starwhale-ui/src/GridTable/hooks/useGridData.ts deleted file mode 100644 index f67824a2a8..0000000000 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridData.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useDatastoreColumns } from '@starwhale/ui/GridDatastoreTable' -import { useStoreApi } from './useStore' -import React, { useMemo } from 'react' - -function useGirdData() { - const { getId, getColumns, columnTypes, columnHints, records, fillable } = useStoreApi().getState() - - const $tablePropsColumns = React.useMemo(() => { - if (!getColumns || typeof getColumns !== 'function') return undefined - - return getColumns?.() ?? [] - }, [getColumns]) - - const $columns = useDatastoreColumns({ - fillWidth: !!fillable, - columnTypes, - columnHints, - }) - - const $originalColumns = React.useMemo(() => { - return $tablePropsColumns && $tablePropsColumns.length > 0 ? $tablePropsColumns : $columns - }, [$tablePropsColumns, $columns]) - - const rows = useMemo( - () => - records?.map((raw, index) => { - // console.log(raw, getId) - return { - id: getId?.(raw) ?? index.toFixed(), - data: raw, - } - }) ?? [], - [records, getId] - ) - - return { - originalColumns: $originalColumns, - rows, - } -} - -export { useGirdData } - -export default useGirdData diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridQuery.tsx b/console/packages/starwhale-ui/src/GridTable/hooks/useGridQuery.tsx index 02680fda7d..70228b052d 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridQuery.tsx +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useGridQuery.tsx @@ -3,40 +3,35 @@ import { useStore } from './useStore' import { ConfigQuery, ConfigQueryInline, ExtraPropsT } from '../components/Query' import { shallow } from 'zustand/shallow' import { IGridState } from '../types' -import useGirdData from './useGridData' import ConfigSimpleQuery from '../components/Query/ConfigSimpleQuery' import Button from '@starwhale/ui/Button' import useTranslation from '@/hooks/useTranslation' -import { useTrace } from '@starwhale/core' const non: any = [] const selector = (state: IGridState) => ({ queries: state.currentView?.queries || non, onCurrentViewQueriesChange: state.onCurrentViewQueriesChange, + columns: state.columns, }) function useGridQuery() { - const trace = useTrace('grid-table-user-grid-query') - const { queries, onCurrentViewQueriesChange: onChange } = useStore(selector, shallow) - const { originalColumns } = useGirdData() + const { queries, onCurrentViewQueriesChange: onChange, columns } = useStore(selector, shallow) const [isSimpleQuery, setIsSimpleQuery] = React.useState(true) const [t] = useTranslation() const hasFilter = React.useMemo(() => { - return originalColumns?.find((column) => column.filterable) - }, [originalColumns]) + return columns?.find((column) => column.filterable) + }, [columns]) const renderConfigQuery = React.useCallback(() => { - trace({ queries }) - return ( <>
{isSimpleQuery ? ( - + ) : ( - + )}
{hasFilter && ( @@ -47,13 +42,13 @@ function useGridQuery() {
) - }, [trace, originalColumns, queries, onChange, isSimpleQuery, hasFilter, t]) + }, [columns, queries, onChange, isSimpleQuery, hasFilter, t]) const renderConfigQueryInline = React.useCallback( (props: ExtraPropsT) => { - return + return }, - [originalColumns, queries, onChange] + [columns, queries, onChange] ) return { diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridSelection.ts b/console/packages/starwhale-ui/src/GridTable/hooks/useGridSelection.ts index 35deb279da..8dde16184f 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridSelection.ts +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useGridSelection.ts @@ -1,6 +1,5 @@ import React from 'react' import { ITableState } from '../store' -import useGridData from './useGridData' import { shallow } from 'zustand/shallow' import { useStore } from './useStore' @@ -9,11 +8,11 @@ const selector = (state: ITableState) => ({ onSelectMany: state.onSelectMany, onSelectNone: state.onSelectNone, onSelectOne: state.onSelectOne, + rows: state.compute?.rows, }) function useGridSelection() { - const { rows } = useGridData() - const { rowSelectedIds, onSelectMany, onSelectNone, onSelectOne } = useStore(selector, shallow) + const { rows, rowSelectedIds, onSelectMany, onSelectNone, onSelectOne } = useStore(selector, shallow) const selectedRowIds = React.useMemo(() => { if (rowSelectedIds) { diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useGridSort.ts b/console/packages/starwhale-ui/src/GridTable/hooks/useGridSort.ts deleted file mode 100644 index 53daa96e3d..0000000000 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useGridSort.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useMemo } from 'react' -import useGirdData from './useGridData' -import useGridCurrentView from './useGridCurrentView' - -function useGridSort() { - const { originalColumns } = useGirdData() - const { columns, currentView } = useGridCurrentView(originalColumns) - - const [$sortIndex, $sortDirection] = useMemo(() => { - const { sortBy, sortDirection } = currentView || {} - const sortIndex = columns?.findIndex((c) => c.key === sortBy) - return [sortIndex, sortDirection] - }, [currentView, columns]) - - return { - sortIndex: $sortIndex, - sortDirection: $sortDirection, - } -} - -export { useGridSort } - -export default useGridSort diff --git a/console/packages/starwhale-ui/src/GridTable/hooks/useStore.ts b/console/packages/starwhale-ui/src/GridTable/hooks/useStore.ts index 4cba7c2d8b..b2c44f6d52 100644 --- a/console/packages/starwhale-ui/src/GridTable/hooks/useStore.ts +++ b/console/packages/starwhale-ui/src/GridTable/hooks/useStore.ts @@ -2,17 +2,16 @@ import { useContext, useMemo } from 'react' import { useStore as useZustandStore } from 'zustand' import type { StoreApi } from 'zustand' -import StoreContext from '../contexts/GridStoreContext' +// import StoreContext from '../contexts/GridStoreContext' import { IGridState } from '../types' +import { shallow } from 'zustand/shallow' +import StoreContext from '../store/StoreProvider' const zustandErrorMessage = 'Could not find zustand store context value.' type ExtractState = StoreApi extends { getState: () => infer T } ? T : never -function useStore( - selector: (state: IGridState) => StateSlice, - equalityFn?: (a: StateSlice, b: StateSlice) => boolean -) { +function useStore(selector: (state: IGridState) => StateSlice, equalityFn = shallow) { const store = useContext(StoreContext) if (store === null) { @@ -31,6 +30,7 @@ const useStoreApi = () => { return useMemo( () => ({ + useStore: store, getState: store.getState, setState: store.setState, subscribe: store.subscribe, diff --git a/console/packages/starwhale-ui/src/GridTable/store/StoreProvider/index.tsx b/console/packages/starwhale-ui/src/GridTable/store/StoreProvider/index.tsx index c9e8af7211..074f91f2db 100644 --- a/console/packages/starwhale-ui/src/GridTable/store/StoreProvider/index.tsx +++ b/console/packages/starwhale-ui/src/GridTable/store/StoreProvider/index.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import StoreContext from '../../contexts/GridStoreContext' +import React, { createContext } from 'react' import createCustomStore, { IStore, ITableState } from '../store' type IStoreProviderProps = { @@ -9,6 +8,10 @@ type IStoreProviderProps = { children: React.ReactNode } +const StoreContext = createContext(null) + +export const { Provider } = StoreContext + export function StoreProvider({ storeKey = 'store', initState, store, children }: IStoreProviderProps) { const storeRef = React.useRef() if (!storeRef.current) { @@ -16,3 +19,5 @@ export function StoreProvider({ storeKey = 'store', initState, store, children } } return {children} } + +export default StoreContext diff --git a/console/packages/starwhale-ui/src/GridTable/store/StoreUpdater/index.tsx b/console/packages/starwhale-ui/src/GridTable/store/StoreUpdater/index.tsx index ec651d6c32..87bfce3fe5 100644 --- a/console/packages/starwhale-ui/src/GridTable/store/StoreUpdater/index.tsx +++ b/console/packages/starwhale-ui/src/GridTable/store/StoreUpdater/index.tsx @@ -6,6 +6,7 @@ import { val } from '../../utils' import { IGridState, ITableProps } from '../../types' import { ITableState } from '../store' import { ConfigT } from '@starwhale/ui/base/data-table/types' +import { useDatastoreColumns } from '@starwhale/ui/GridDatastoreTable' type StoreUpdaterProps = ITableProps @@ -42,11 +43,36 @@ export function useDirectStoreUpdater( }, [value]) } +const useStoreComputeUpdater = ( + deps, + fn: (selectedState: IGridState, previousSelectedState: IGridState) => void, + subscribe +) => { + useEffect(() => { + const unsub = subscribe( + (state) => deps.map((dep) => (typeof dep === 'function' ? dep(state) : state[dep])), + // @ts-ignore + fn, + { equalityFn: shallow } + ) + + return () => { + // reset() + unsub() + } + }, [deps, fn, subscribe]) + + return subscribe +} + const globalGetId = (record: any) => val(record.id) const selector = (s: ITableState) => ({ reset: s.reset, setCurrentView: s.setCurrentView, + computeColumns: s.computeColumns, + computeSortIndex: s.computeSortIndex, + computeRows: s.computeRows, }) const StoreUpdater = ({ @@ -74,15 +100,19 @@ const StoreUpdater = ({ onRemove, removable, selectable, + columns, }: StoreUpdaterProps) => { - const { reset, setCurrentView } = useStore(selector, shallow) + const { setCurrentView, computeColumns, computeSortIndex, computeRows } = useStore(selector, shallow) const store = useStoreApi() - - useEffect(() => { - return () => { - // reset() - } - }, [reset]) + const $columns = useDatastoreColumns({ + fillWidth: !!fillable, + columnTypes, + columnHints, + }) + // subscribe before + useStoreComputeUpdater(['columns', 'currentView'], computeColumns, store.subscribe) + useStoreComputeUpdater(['columns', (s) => s.currentView.sortBy], computeSortIndex, store.subscribe) + useStoreComputeUpdater(['records'], computeRows, store.subscribe) // config useDirectStoreUpdater('sortable', sortable, store.setState) @@ -105,13 +135,18 @@ const StoreUpdater = ({ useDirectStoreUpdater('onRowSelectedChange', onRowSelectedChange, store.setState) // data useDirectStoreUpdater('page', page, store.setState) - useDirectStoreUpdater('rows', rows, store.setState) - useDirectStoreUpdater('records', records, store.setState) useDirectStoreUpdater('columnTypes', columnTypes, store.setState) useDirectStoreUpdater('columnHints', columnHints, store.setState) useDirectStoreUpdater('rowSelectedIds', rowSelectedIds, store.setState) - + // useStoreEmptyUpdater(currentView, setCurrentView) + // columns + useDirectStoreUpdater('columns', columns ?? $columns, store.setState) + useDirectStoreUpdater('originalColumns', columns, store.setState) + // rows + useDirectStoreUpdater('rows', rows, store.setState) + useDirectStoreUpdater('records', records, store.setState) + return null } diff --git a/console/packages/starwhale-ui/src/GridTable/store/store.ts b/console/packages/starwhale-ui/src/GridTable/store/store.ts index 08822d8bbe..b57959b713 100644 --- a/console/packages/starwhale-ui/src/GridTable/store/store.ts +++ b/console/packages/starwhale-ui/src/GridTable/store/store.ts @@ -57,12 +57,36 @@ export interface IViewInteractiveState { viewModelShow: boolean onShowViewModel: (viewModelShow: boolean, viewEditing: ConfigT | null) => void } + +export interface IComputeState { + computeColumns: () => void + computeSortIndex: () => void + computeRows: () => void + compute: IComputeData +} +export interface IComputeData { + columns: any[] + rows: any[] + sortIndex: number + sortDirection: SortDirectionsT +} +export interface IUpdaterState { + originalColumns?: any[] + columns?: any[] + records?: any[] + getId?: (record: any) => any + onPageChange?: (page: number, pageSize: number) => void + page?: any + onSave?: (view: any) => void +} export type ITableState = IViewState & ICurrentViewState & IViewInteractiveState & ITableStateInitState & IRowState & - ICompareState + ICompareState & + IComputeState & + IUpdaterState // , ['zustand/persist', unknown] export type IStateCreator = StateCreator< @@ -404,7 +428,7 @@ const createRowSlice: IStateCreator = (set, get, store) => { update( { rowSelectedIds, - rowSelectedRecords: selectedRecords.filter((r) => rowSelectedIds.includes(getId(r))), + rowSelectedRecords: selectedRecords.filter((r) => rowSelectedIds.includes(getId?.(r))), }, 'setRowSelectedIds' ) @@ -436,6 +460,75 @@ const createCompareSlice: IStateCreator = (set, get, store) => ({ ), }) +const getColumnsIds = (state: ITableState) => { + const { currentView: view, columns } = state + const { pinnedIds = [], ids = [] }: ConfigT = view + const columnIds = columns?.map((c) => c.key) ?? [] + if (!view.id || (view.id === 'all' && !view.updateColumn)) { + return Array.from(new Set([...pinnedIds, ...columnIds])) + } + return ids +} + +const getColumns = (state: ITableState) => { + const { currentView: view, columns } = state + if (!columns) return [] + if (!view) return columns + const { pinnedIds = [] }: ConfigT = view + const columnsMap = _.keyBy(columns, (c) => c.key) + const ids = getColumnsIds(state) + return ids + .filter((id: any) => id in columnsMap) + .map((id: any) => { + const _pin = columnsMap[id].pin ?? undefined + return { + ...columnsMap[id], + pin: pinnedIds.includes(id) ? 'LEFT' : _pin, + } + }) +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const createComputeSlice: IStateCreator = (set, get, store) => { + const update = (updateAttrs: Partial, name?: string) => { + const curr = get().compute + set( + { + compute: { + ...curr, + ...updateAttrs, + }, + }, + undefined, + name + ) + } + + return { + compute: { columns: [], rows: [], sortIndex: -1, sortDirection: 'DESC' }, + computeColumns: () => { + return update({ columns: getColumns(get()) }, 'setColumns') + }, + computeSortIndex: () => { + const { sortBy } = get().currentView || {} + const sortIndex = get().columns?.findIndex((c) => c.key === sortBy) + return update({ sortIndex }, 'computeSortIndex') + }, + computeRows: () => { + const { getId, records } = store.getState() + const rows = + records?.map((raw, index) => { + return { + id: getId?.(raw) ?? index.toFixed(), + data: raw, + } + }) ?? [] + + return update({ rows }, 'computeRows') + }, + } +} + export function createCustomStore(key: string, initState: Partial = rawInitialState, isPersist = false) { const name = `table/${key}` // @@ -452,15 +545,31 @@ export function createCustomStore(key: string, initState: Partial = ...createRowSlice(...a), // @ts-ignore ...createCompareSlice(...a), + // @ts-ignore + ...createComputeSlice(...a), ...initState, key: name, }) + if (isPersist) { - return create()(subscribeWithSelector(devtools(persist(actions as any, { name })))) + return create()( + subscribeWithSelector( + devtools( + persist(actions as any, { + name, + partialize: (state) => + Object.fromEntries( + Object.entries(state).filter(([_key]) => !Object.keys(rawInitialState).includes(_key)) + ), + }) + ) + ) + ) } + const useStore = create()( subscribeWithSelector( - devtools(actions as any, { + devtools(actions, { serialize: { options: { undefined: true, @@ -476,11 +585,10 @@ export function createCustomStore(key: string, initState: Partial = stateSanitizer: (state) => { return { ...state, - // rowSelectedRecords: '<>', - // columnTypes: '<>', - // records: '<>', rows: '<>', - wrapperRef: '<>', + columns: '<>', + wrapperRef: '<>', + compute: '<>', } }, }) @@ -488,6 +596,7 @@ export function createCustomStore(key: string, initState: Partial = ) return useStore as UseBoundStore> } + export type IStore = ReturnType export default createCustomStore diff --git a/console/packages/starwhale-ui/src/Search/Search.tsx b/console/packages/starwhale-ui/src/Search/Search.tsx index c969d154c5..a5c79b04a5 100644 --- a/console/packages/starwhale-ui/src/Search/Search.tsx +++ b/console/packages/starwhale-ui/src/Search/Search.tsx @@ -121,6 +121,7 @@ export default function Search({ value = [], onChange, getFilters }: ISearchProp }) tmps.push( { return !!value } -const trace = traceCreator('search-state') +// const trace = traceCreator('search-state') const reducer = (state, action) => { let next = { ...state } @@ -124,7 +124,7 @@ const reducer = (state, action) => { default: throw new Error('Unexpected action') } - trace('✅', { type: action.type }, action.payload, { next }) + // trace('✅', { type: action.type }, action.payload, { next }) return next } diff --git a/console/packages/starwhale-ui/src/Viewer/DataViewer.tsx b/console/packages/starwhale-ui/src/Viewer/DataViewer.tsx index 6e8540d107..b56bdaea16 100644 --- a/console/packages/starwhale-ui/src/Viewer/DataViewer.tsx +++ b/console/packages/starwhale-ui/src/Viewer/DataViewer.tsx @@ -58,6 +58,8 @@ export default function DataViewer({ switch (_type) { case ArtifactType.Image: { + if (!summary) return null + if (mimeType === MIMES.GRAYSCALE) { return } diff --git a/console/packages/starwhale-ui/src/base/data-table/data-custom-table.tsx b/console/packages/starwhale-ui/src/base/data-table/data-custom-table.tsx index 3e4f18d265..1fe9e13eab 100644 --- a/console/packages/starwhale-ui/src/base/data-table/data-custom-table.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/data-custom-table.tsx @@ -401,9 +401,9 @@ export function DataTable({ const itemData = React.useMemo(() => { return { - // columnHighlightIndex, // warning: this can cause performance problem, and inline edit will have wrong behaviour so use row own behaviour // rowHighlightIndex, + // columnHighlightIndex, isRowSelected, isQueryInline, isSelectable, @@ -538,6 +538,7 @@ export function DataTable({ width, scrollbarWidth: scrollbarWidth(), rowActions, + ...itemIndexs, }} > {/* headers outside to make scroll not covered header bar */} diff --git a/console/packages/starwhale-ui/src/base/data-table/headers/header-bar.tsx b/console/packages/starwhale-ui/src/base/data-table/headers/header-bar.tsx index d21bc4134f..cc75709c30 100644 --- a/console/packages/starwhale-ui/src/base/data-table/headers/header-bar.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/headers/header-bar.tsx @@ -7,7 +7,8 @@ import { LocaleContext } from 'baseui/locale' import { DataTableLocaleT } from '../locale' import { IGridState } from '../../../GridTable/types' import { useStore } from '../../../GridTable/hooks/useStore' -import useGrid from '../../../GridTable/hooks/useGrid' +import useGridQuery from '@starwhale/ui/GridTable/hooks/useGridQuery' +import useGridConfigColumns from '@starwhale/ui/GridTable/hooks/useGridConfigColumns' const selector = (s: IGridState) => ({ queryinline: s.queryinline, @@ -23,7 +24,8 @@ function HeaderBar(props: { wrapperWidth: any }) { // @ts-ignore const locale: { datatable: DataTableLocaleT } = React.useContext(LocaleContext) const { wrapperRef, queryinline, columnleinline, removable, selectable } = useStore(selector) - const { renderConfigQueryInline, renderConfigColumns } = useGrid() + const { renderConfigQueryInline } = useGridQuery() + const { renderConfigColumns } = useGridConfigColumns() const [isShowQuery, setIsShowQuery] = React.useState(false) const [isShowConfigColumns, setIsShowConfigColumns] = React.useState(false) @@ -36,19 +38,16 @@ function HeaderBar(props: { wrapperWidth: any }) { }, columnleinline && { label: locale.datatable.columnConfig, type: 'column' }, ].filter(Boolean), - [queryinline, locale] + [queryinline, locale, columnleinline] ) - const handleColumnOptionSelect = React.useCallback( - (option: any) => { - if (option.type === 'query') { - setIsShowQuery(true) - } else if (option.type === 'column') { - setIsShowConfigColumns(true) - } - }, - [props] - ) + const handleColumnOptionSelect = React.useCallback((option: any) => { + if (option.type === 'query') { + setIsShowQuery(true) + } else if (option.type === 'column') { + setIsShowConfigColumns(true) + } + }, []) if (!columnleinline && !queryinline) { if (!removable && !selectable) { diff --git a/console/packages/starwhale-ui/src/base/data-table/headers/header-cell.tsx b/console/packages/starwhale-ui/src/base/data-table/headers/header-cell.tsx index 0798891626..468b5a526d 100644 --- a/console/packages/starwhale-ui/src/base/data-table/headers/header-cell.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/headers/header-cell.tsx @@ -1,9 +1,7 @@ import * as React from 'react' -import { useStyletron } from 'baseui' -import { ChevronDown, ChevronUp } from 'baseui/icon' import { isFocusVisible } from '@/utils/focusVisible' -import { StatefulPopover, PLACEMENT, Popover, TRIGGER_TYPE } from 'baseui/popover' +import { StatefulPopover, PLACEMENT, TRIGGER_TYPE } from 'baseui/popover' import { StatefulMenu } from 'baseui/menu' import IconFont from '../../../IconFont' import cn from 'classnames' @@ -17,7 +15,6 @@ import { DataTableLocaleT } from '../locale' import { IGridState } from '@starwhale/ui/GridTable/types' import { useStore } from '@starwhale/ui/GridTable/hooks/useStore' import { shallow } from 'zustand/shallow' -import useGridSort from '@starwhale/ui/GridTable/hooks/useGridSort' type HeaderCellPropsT = { index: number @@ -28,7 +25,6 @@ type HeaderCellPropsT = { isSelectedAll?: boolean isQueryInline?: boolean isSelectedIndeterminate?: boolean - selectedRowIds: Set onMouseEnter: (num: number) => void onMouseLeave: (num: number) => void onSelectAll?: () => void @@ -45,6 +41,7 @@ type HeaderCellPropsT = { compareable?: boolean removable?: boolean querySlot?: React.ReactNode + selectedRowIds?: any[] } const selector = (s: IGridState) => ({ @@ -54,17 +51,16 @@ const selector = (s: IGridState) => ({ wrapperRef: s.wrapperRef, sortable: s.sortable, rowSelectedIds: s.rowSelectedIds, + sortIndex: s.compute.sortIndex, }) const HeaderCell = React.forwardRef((props, ref) => { - //@ts-ignore + // @ts-ignore const locale: { datatable: DataTableLocaleT } = React.useContext(LocaleContext) const [css, theme] = themedUseStyletron() const [focusVisible, setFocusVisible] = React.useState(false) const checkboxRef = React.useRef(null) - const { sortable, rowSelectedIds, queryinline, columnleinline } = useStore(selector, shallow) - - const { sortIndex, sortDirection } = useGridSort() + const { sortIndex, sortable, rowSelectedIds, queryinline, columnleinline } = useStore(selector, shallow) const handleFocus = (event: React.SyntheticEvent) => { if (isFocusVisible(event as any)) { @@ -88,7 +84,7 @@ const HeaderCell = React.forwardRef((props, re sortable && { label: locale.datatable.columnSortAsc, type: 'sortAsc' }, sortable && { label: locale.datatable.columnSortDesc, type: 'sortDesc' }, ].filter(Boolean), - [props.isPin, locale] + [props.isPin, locale, sortable] ) const handleColumnOptionSelect = React.useCallback( diff --git a/console/packages/starwhale-ui/src/base/data-table/headers/header.tsx b/console/packages/starwhale-ui/src/base/data-table/headers/header.tsx index 1fa3eaadd8..a42bb30473 100644 --- a/console/packages/starwhale-ui/src/base/data-table/headers/header.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/headers/header.tsx @@ -42,6 +42,8 @@ export type HeaderContextT = { wrapperWidth?: number scrollbarWidth: number width: number + overscanColumnStartIndex: number + overscanColumnStopIndex: number } export const HeaderContext = React.createContext({ @@ -79,6 +81,8 @@ export const HeaderContext = React.createContext({ widths: new Map(), scrollbarWidth: 0, width: 0, + overscanColumnStartIndex: 0, + overscanColumnStopIndex: 10, }) HeaderContext.displayName = 'HeaderContext' type HeaderProps = { @@ -91,7 +95,7 @@ type HeaderProps = { isQueryInline?: boolean isSelectedAll?: boolean isSelectedIndeterminate?: boolean - selectedRowIds: Set + selectedRowIds: any[] onMouseEnter: (num: number) => void onMouseLeave: () => void onResize: (columnIndex: number, delta: number) => void diff --git a/console/packages/starwhale-ui/src/base/data-table/headers/headers.tsx b/console/packages/starwhale-ui/src/base/data-table/headers/headers.tsx index 7b0c85f291..8bb4c38e2b 100644 --- a/console/packages/starwhale-ui/src/base/data-table/headers/headers.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/headers/headers.tsx @@ -1,13 +1,10 @@ import React, { useCallback } from 'react' import { Tooltip, PLACEMENT } from 'baseui/tooltip' -import cn from 'classnames' import Header, { HeaderContext, HEADER_ROW_HEIGHT } from './header' import type { ColumnT, SortDirectionsT } from '../types' -import { LocaleContext } from 'baseui/locale' import { themedUseStyletron } from '../../../theme/styletron' import { useStore, useStoreApi } from '../../../GridTable/hooks/useStore' import { IGridState } from '../../../GridTable/types' -import useGrid from '../../../GridTable/hooks/useGrid' import HeaderBar from './header-bar' import useTranslation from '@/hooks/useTranslation' @@ -16,17 +13,18 @@ const sum = (ns: number[]): number => ns.reduce((s, n) => s + n, 0) const selector = (s: IGridState) => ({ queryinline: s.queryinline, compare: s.compare, + selectedRowIds: s.rowSelectedIds, }) export default function Headers({ width }: { width: number }) { - const [css, theme] = themedUseStyletron() - const locale = React.useContext(LocaleContext) + const [, theme] = themedUseStyletron() const ctx = React.useContext(HeaderContext) const [resizeIndex, setResizeIndex] = React.useState(-1) - const { queryinline } = useStore(selector) + const { queryinline, selectedRowIds } = useStore(selector) const { onNoSelect, onCompareUpdate, onCurrentViewColumnsPin, onCurrentViewSort, compare } = useStoreApi().getState() - const { columns, selectedRowIds } = useGrid() + const { columns } = ctx + const [t] = useTranslation() const $columns = React.useMemo( @@ -63,6 +61,14 @@ export default function Headers({ width }: { width: number }) { const isFoucs = column.key === compare?.comparePinnedKey const isPin = !!column.pin + if ( + (columnIndex < ctx.overscanColumnStartIndex || columnIndex > ctx.overscanColumnStopIndex) && + columnIndex !== 0 && + !column.pin + ) { + return
+ } + return (
{ - return $columns.filter((v) => v.pin !== 'LEFT').map(headerRender) + return $columns.filter((v) => v.pin !== 'LEFT' && v.pin !== 'RIGHT').map(headerRender) }, [$columns, headerRender]) const headersRightCount = React.useMemo(() => { @@ -213,7 +223,7 @@ export default function Headers({ width }: { width: number }) { {headers.length > 0 && ( <>
React.ReactNode) }) { @@ -49,7 +49,6 @@ const filterColumns = (columns: ColumnT[], attr: string, value: any) => { // replaces the content of the virtualized window with contents. in this case, // we are prepending a table header row before the table rows (children to the fn). const InnerTableElement = React.forwardRef((props, ref) => { - const [css] = themedUseStyletron() const ctx = React.useContext(HeaderContext) let viewState = RENDERING if (ctx.loading) { @@ -108,7 +107,7 @@ const InnerTableElement = React.forwardRef { @@ -172,15 +171,12 @@ const InnerTableElement = React.forwardRef(undefined) const [isFocus, setIsFocus] = React.useState(false) @@ -228,7 +224,7 @@ const InnerTableElement = React.forwardRef diff --git a/console/packages/starwhale-ui/src/base/data-table/measure-column-widths.tsx b/console/packages/starwhale-ui/src/base/data-table/measure-column-widths.tsx index 6dbdf3cc0e..2d5c46711d 100644 --- a/console/packages/starwhale-ui/src/base/data-table/measure-column-widths.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/measure-column-widths.tsx @@ -1,5 +1,5 @@ // @ts-nocehck -import React, { useRef } from 'react' +import React, { startTransition, useEffect, useRef } from 'react' import { useStyletron } from 'baseui' import HeaderCell from './headers/header-cell' import type { ColumnT, RowT } from './types' @@ -137,19 +137,33 @@ export default function MeasureColumnWidths({ // 1. Refresh only when there is a width updating ,and the minised of the width is more than 2px if (nextWidth !== widthMap.get(columnIndex) && Math.abs(nextWidth - prevWidth) > 5) { widthMap.set(columnIndex, nextWidth) - - const widths = columns.map((tmp) => widthMap.get(tmp.key)).filter(Boolean) - - // 1.Refresh at 100% of done - if (widths.length === columns.length) { - setWidthMap(widthMap) - debounceWideChange(widthMap) - } + setWidthMap(widthMap) } }, - [columns, debounceWideChange, widthMap] + [widthMap] ) + useEffect(() => { + let updated = false + // console.log('updated', widthMap, measuredWidths) + columns.forEach((tmp) => { + if (!widthMap.has(tmp.key)) return + if (!measuredWidths.get(tmp.key) && widthMap.get(tmp.key)) { + measuredWidths.set(tmp.key, widthMap.get(tmp.key)) + updated = true + } else if (Math.abs(measuredWidths.get(tmp.key) - Number(widthMap.get(tmp.key))) > 5) { + measuredWidths.set(tmp.key, widthMap.get(tmp.key)) + updated = true + } + }) + if (updated) { + // console.log('updated', measuredWidths) + startTransition(() => { + debounceWideChange(measuredWidths) + }) + } + }, [columns, widthMap, measuredWidths, debounceWideChange]) + const $columns = React.useMemo(() => { return columns.map((column, i) => { if (measuredWidths.has(column.key)) return null diff --git a/console/src/pages/Dataset/DatasetVersionOverviewFiles.tsx b/console/src/pages/Dataset/DatasetVersionOverviewFiles.tsx index 66a8216409..2ab25b3c0a 100644 --- a/console/src/pages/Dataset/DatasetVersionOverviewFiles.tsx +++ b/console/src/pages/Dataset/DatasetVersionOverviewFiles.tsx @@ -5,7 +5,6 @@ import { createUseStyles } from 'react-jss' import { useDatasetVersion } from '@/domain/dataset/hooks/useDatasetVersion' import { getMeta } from '@/domain/dataset/utils' import useFetchDatastoreByTable from '@starwhale/core/datastore/hooks/useFetchDatastoreByTable' -import { useDatastoreColumns } from '@starwhale/ui/GridDatastoreTable' import GridCombineTable from '@starwhale/ui/GridTable/GridCombineTable' import useDatastorePage from '@starwhale/core/datastore/hooks/useDatastorePage' import { ITableState, useDatasetStore } from '@starwhale/ui/GridTable/store' @@ -118,13 +117,6 @@ export default function DatasetVersionFiles() { !!datasetVersion?.versionInfo?.indexTable ) - const $columns = useDatastoreColumns({ - showPrivate: false, - showLink: false, - columnTypes, - columnHints, - }) - return (
{ ;(function () { var hm = document.createElement('script') hm.src = 'https://hm.baidu.com/hm.js?82145850946f2ffce3c1366524ebe861' + hm.async = true var s = document.getElementsByTagName('script')[0] s.parentNode.insertBefore(hm, s) })() diff --git a/console/yarn.lock b/console/yarn.lock index f474465d7c..65cb79e01b 100644 --- a/console/yarn.lock +++ b/console/yarn.lock @@ -25699,6 +25699,11 @@ z-schema@~5.0.2: optionalDependencies: commander "^10.0.0" +zustand-computed@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/zustand-computed/-/zustand-computed-1.3.7.tgz#560f8b554196c8f83dcfaf51ccb32e7f8eb8b85e" + integrity sha512-7Upkc4eVdcLaBfdcMMg06kSBarbkaqM9xdcWCpE4N7edBwbTTyd6IsYcXs8IRvHuRk8JBZrjbDePwQxpti0yKA== + zustand@v4.3.8: version "4.3.8" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.8.tgz#37113df8e9e1421b0be1b2dca02b49b76210e7c4"