Skip to content

Commit 141bcc3

Browse files
committed
feat(FR-1695): improve BAIFileExplorer UX with refresh functionality and loading optimization
1 parent 8ebf1de commit 141bcc3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+193
-80
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Object.defineProperty(window, 'matchMedia', {
2+
writable: true,
3+
value: jest.fn().mockImplementation((query) => ({
4+
matches: false,
5+
media: query,
6+
onchange: null,
7+
addListener: jest.fn(), // deprecated
8+
removeListener: jest.fn(), // deprecated
9+
addEventListener: jest.fn(),
10+
removeEventListener: jest.fn(),
11+
dispatchEvent: jest.fn(),
12+
})),
13+
});

react/src/components/BAIFetchKeyButton.tsx renamed to packages/backend.ai-ui/src/components/BAIFetchKeyButton.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import useControllableState_deprecated from '../hooks/useControllableState';
1+
import { omitNullAndUndefinedFields } from '../helper';
22
import { useInterval, useIntervalValue } from '../hooks/useIntervalValue';
33
import { ReloadOutlined } from '@ant-design/icons';
4+
import { useControllableValue } from 'ahooks';
45
import { Button, ButtonProps, Tooltip } from 'antd';
56
import dayjs from 'dayjs';
7+
import _ from 'lodash';
68
import React, { useEffect, useLayoutEffect, useState } from 'react';
79
import { useTranslation } from 'react-i18next';
810

9-
interface BAIAutoRefetchButtonProps
11+
interface BAIFetchKeyButtonProps
1012
extends Omit<ButtonProps, 'value' | 'onChange' | 'loading'> {
1113
value: string;
1214
loading?: boolean;
@@ -18,7 +20,7 @@ interface BAIAutoRefetchButtonProps
1820
hidden?: boolean;
1921
pauseWhenHidden?: boolean;
2022
}
21-
const BAIFetchKeyButton: React.FC<BAIAutoRefetchButtonProps> = ({
23+
const BAIFetchKeyButton: React.FC<BAIFetchKeyButtonProps> = ({
2224
loading,
2325
onChange,
2426
showLastLoadTime,
@@ -29,11 +31,14 @@ const BAIFetchKeyButton: React.FC<BAIAutoRefetchButtonProps> = ({
2931
pauseWhenHidden = true,
3032
...buttonProps
3133
}) => {
34+
'use memo';
35+
3236
const { t } = useTranslation();
33-
const [lastLoadTime, setLastLoadTime] = useControllableState_deprecated(
34-
{
37+
const [lastLoadTime, setLastLoadTime] = useControllableValue(
38+
// To use the default value when lastLoadTimeProp is undefined, we need to omit the value field
39+
omitNullAndUndefinedFields({
3540
value: lastLoadTimeProp,
36-
},
41+
}),
3742
{
3843
defaultValue: new Date(),
3944
},
@@ -61,7 +66,7 @@ const BAIFetchKeyButton: React.FC<BAIAutoRefetchButtonProps> = ({
6166
const loadTimeMessage = useIntervalValue(
6267
() => {
6368
if (lastLoadTime) {
64-
return `${t('general.LastUpdated')}: ${dayjs(lastLoadTime).fromNow()}`;
69+
return `${t('comp:BAIFetchKeyButton.LastUpdated')}: ${dayjs(lastLoadTime).fromNow()}`;
6570
}
6671
return '';
6772
},
@@ -90,7 +95,7 @@ const BAIFetchKeyButton: React.FC<BAIAutoRefetchButtonProps> = ({
9095
return hidden ? null : (
9196
<Tooltip title={tooltipTitle} placement="topLeft">
9297
<Button
93-
title={tooltipTitle ? undefined : t('general.Refresh')}
98+
title={tooltipTitle ? undefined : t('comp:BAIFetchKeyButton.Refresh')}
9499
loading={displayLoading}
95100
size={size}
96101
icon={<ReloadOutlined />}

packages/backend.ai-ui/src/components/baiClient/FileExplorer/BAIFileExplorer.tsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
filterOutEmpty,
44
localeCompare,
55
} from '../../../helper';
6+
import BAIFetchKeyButton from '../../BAIFetchKeyButton';
67
import BAIFlex from '../../BAIFlex';
78
import BAILink from '../../BAILink';
89
import BAIUnmountAfterClose from '../../BAIUnmountAfterClose';
@@ -43,6 +44,7 @@ export interface BAIFileExplorerProps {
4344
enableDownload?: boolean;
4445
enableDelete?: boolean;
4546
enableWrite?: boolean;
47+
onChangeFetchKey?: (fetchKey: string) => void;
4648
}
4749

4850
const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
@@ -70,22 +72,13 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
7072
files,
7173
directoryTree,
7274
isFetching,
75+
isLoading: isFirstFetching,
7376
currentPath,
7477
navigateDown,
7578
navigateToPath,
7679
refetch,
7780
} = useSearchVFolderFiles(targetVFolderId, fetchKey);
7881

79-
const [fetchedFilesCache, setFetchedFilesCache] = useState<
80-
Array<VFolderFile>
81-
>([]);
82-
83-
useEffect(() => {
84-
if (!_.isNil(files?.items)) {
85-
setFetchedFilesCache(files.items);
86-
}
87-
}, [files]);
88-
8982
const breadCrumbItems: Array<ItemType> = useMemo(() => {
9083
const pathParts = currentPath === '.' ? [] : currentPath.split('/');
9184

@@ -153,7 +146,7 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
153146
render: (name, record) => (
154147
<EditableFileName
155148
fileInfo={record}
156-
existingFiles={fetchedFilesCache}
149+
existingFiles={files?.items || []}
157150
disabled={!enableWrite}
158151
onEndEdit={() => {
159152
refetch();
@@ -247,7 +240,6 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
247240
};
248241
}, []);
249242

250-
const mergedLoading = files?.items !== fetchedFilesCache || isFetching;
251243
return (
252244
<FolderInfoContext.Provider value={{ targetVFolderId, currentPath }}>
253245
{isDragMode && (
@@ -297,27 +289,34 @@ const BAIFileExplorer: React.FC<BAIFileExplorerProps> = ({
297289
refetch();
298290
}
299291
}}
292+
extra={
293+
<BAIFetchKeyButton
294+
loading={isFetching}
295+
value={'_not_used_key_'}
296+
onChange={() => {
297+
refetch();
298+
}}
299+
/>
300+
}
300301
/>
301302
</BAIFlex>
302303

303304
<BAITable
304305
rowKey="name"
305306
scroll={{ x: 'max-content' }}
306-
dataSource={fetchedFilesCache}
307+
dataSource={files?.items}
307308
columns={tableColumns}
308309
// If no files have been loaded yet (including cache), show spinner loading
309-
spinnerLoading={!files?.items ? mergedLoading : undefined}
310+
spinnerLoading={isFirstFetching}
310311
// If files have been loaded before, use normal loading style (opacity)
311-
loading={
312-
files?.items && files?.items.length >= 0 ? mergedLoading : undefined
313-
}
312+
loading={!isFirstFetching && isFetching}
314313
pagination={false}
315314
rowSelection={{
316315
type: 'checkbox',
317316
selectedRowKeys: _.map(selectedItems, 'name'),
318317
onChange: (selectedRowKeys) => {
319318
setSelectedItems(
320-
fetchedFilesCache?.filter((file) =>
319+
files?.items?.filter((file) =>
321320
selectedRowKeys.includes(file.name),
322321
) || [],
323322
);

packages/backend.ai-ui/src/components/baiClient/FileExplorer/ExplorerActionControls.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ interface ExplorerActionControlsProps {
3838
onUpload: (files: Array<RcFile>, currentPath: string) => void;
3939
enableDelete?: boolean;
4040
enableWrite?: boolean;
41+
// onClickRefresh?: (key: string) => void;
42+
extra?: React.ReactNode;
4143
}
4244

4345
const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
@@ -46,6 +48,7 @@ const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
4648
onUpload,
4749
enableDelete = false,
4850
enableWrite = false,
51+
extra,
4952
}) => {
5053
const { t } = useTranslation();
5154
const { lg } = Grid.useBreakpoint();
@@ -163,6 +166,7 @@ const ExplorerActionControls: React.FC<ExplorerActionControlsProps> = ({
163166
toggleCreateModal();
164167
}}
165168
/>
169+
{extra}
166170
</BAIFlex>
167171
);
168172
};

packages/backend.ai-ui/src/components/baiClient/FileExplorer/hooks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const useSearchVFolderFiles = (vfolder: string, fetchKey?: string) => {
3838
data: files,
3939
refetch,
4040
isFetching,
41+
isLoading,
4142
} = useQuery({
4243
queryKey: ['searchVFolderFiles', vfolder, currentPath, fetchKey],
4344
queryFn: () =>
@@ -61,6 +62,7 @@ export const useSearchVFolderFiles = (vfolder: string, fetchKey?: string) => {
6162
navigateToPath,
6263
refetch,
6364
isFetching,
65+
isLoading,
6466
};
6567
};
6668

packages/backend.ai-ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export { default as BAIConfirmModalWithInput } from './BAIConfirmModalWithInput'
5757
export type { BAIConfirmModalWithInputProps } from './BAIConfirmModalWithInput';
5858
export { default as BAIButton } from './BAIButton';
5959
export type { BAIButtonProps } from './BAIButton';
60+
export { default as BAIFetchKeyButton } from './BAIFetchKeyButton';
6061
export * from './Table';
6162
export * from './fragments';
6263
export * from './provider';

packages/backend.ai-ui/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ export { default as useViewer } from './useViewer';
3838
export type { ErrorResponse } from './useErrorMessageResolver';
3939
export type { ESMClientErrorResponse } from './useErrorMessageResolver';
4040
export { default as useGetAvailableFolderName } from './useGetAvailableFolderName';
41+
export { useInterval, useIntervalValue } from './useIntervalValue';

react/src/hooks/useIntervalValue.test.tsx renamed to packages/backend.ai-ui/src/hooks/useIntervalValue.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import '../__test__/matchMedia.mock.js';
12
import { useInterval, useIntervalValue } from './useIntervalValue';
23
import { render, screen, act } from '@testing-library/react';
34
import React from 'react';

packages/backend.ai-ui/src/locale/de.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
"SuccessFullyRemoved": "Erfolgreich entfernt {{count}} Versionen.",
5454
"Version": "Version"
5555
},
56+
"comp:BAIFetchKeyButton": {
57+
"LastUpdated": "Zuletzt aktualisiert",
58+
"Refresh": "Aktualisieren"
59+
},
5660
"comp:BAIImportArtifactModal": {
5761
"ExcludedVersions": "{{count}} Versionen sind ausgeschlossen.",
5862
"FailedToPullVersions": "Versäumte, Versionen zu ziehen.",

0 commit comments

Comments
 (0)