From: Lucas Di Pentima Date: Wed, 1 Jun 2022 10:27:13 +0000 (-0300) Subject: Merge branch '18787-file-browser-rerendering-fix'. Closes #18787 X-Git-Tag: 2.5.0~54 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/852a6a393297d03ca5259ddb6aa7aedff4a000ea?hp=89a2555cc00f750dfa07dc57a2908059fc52c935 Merge branch '18787-file-browser-rerendering-fix'. Closes #18787 Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js index b62a3441..0b06e53e 100644 --- a/cypress/integration/collection.spec.js +++ b/cypress/integration/collection.spec.js @@ -261,7 +261,7 @@ describe('Collection panel tests', function () { }); // Test context menus cy.get('[data-cy=collection-files-panel]') - .contains(fileName).rightclick({ force: true }); + .contains(fileName).rightclick(); cy.get('[data-cy=context-menu]') .should('contain', 'Download') .and('not.contain', 'Open in new tab') @@ -270,7 +270,7 @@ describe('Collection panel tests', function () { .and(`${isWritable ? '' : 'not.'}contain`, 'Remove'); cy.get('body').click(); // Collapse the menu cy.get('[data-cy=collection-files-panel]') - .contains(subDirName).rightclick({ force: true }); + .contains(subDirName).rightclick(); cy.get('[data-cy=context-menu]') .should('not.contain', 'Download') .and('not.contain', 'Open in new tab') @@ -368,7 +368,7 @@ describe('Collection panel tests', function () { ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => { cy.get('[data-cy=collection-files-panel]') - .contains('bar').rightclick({force: true}); + .contains('bar').rightclick(); cy.get('[data-cy=context-menu]') .contains('Rename') .click(); @@ -381,9 +381,9 @@ describe('Collection panel tests', function () { cy.get('[data-cy=collection-files-panel]') .should('not.contain', 'bar') .and('contain', subdir); - cy.wait(1000); cy.get('[data-cy=collection-files-panel]').contains(subdir).click(); - // Rename 'subdir/foo' to 'foo' + + // Rename 'subdir/foo' to 'bar' cy.wait(1000); cy.get('[data-cy=collection-files-panel]') .contains('foo').rightclick(); @@ -399,7 +399,6 @@ describe('Collection panel tests', function () { }); cy.get('[data-cy=form-submit-btn]').click(); - cy.wait(1000); cy.get('[data-cy=collection-files-panel]') .contains('Home') .click(); @@ -1034,6 +1033,14 @@ describe('Collection panel tests', function () { cy.goToPath(`/collections/${testCollection1.uuid}`); + // Confirm initial collection state. + cy.get('[data-cy=collection-files-panel]') + .contains('bar').should('exist'); + cy.get('[data-cy=collection-files-panel]') + .contains('5mb_a.bin').should('not.exist'); + cy.get('[data-cy=collection-files-panel]') + .contains('5mb_b.bin').should('not.exist'); + cy.get('[data-cy=upload-button]').click(); cy.fixture('files/5mb.bin', 'base64').then(content => { @@ -1043,9 +1050,25 @@ describe('Collection panel tests', function () { cy.get('[data-cy=form-submit-btn]').click(); cy.get('button[aria-label=Remove]').should('exist'); - cy.get('button[aria-label=Remove]').click({ multiple: true, force: true }); + cy.get('button[aria-label=Remove]') + .click({ multiple: true, force: true }); cy.get('[data-cy=form-submit-btn]').should('not.exist'); + + // Confirm final collection state. + cy.get('[data-cy=collection-files-panel]') + .contains('bar').should('exist'); + // The following fails, but doesn't seem to happen + // in the real world. Maybe there's a race between + // the PUT request finishing and the 'Remove' button + // dissapearing, because sometimes just one of the 2 + // files gets uploaded. + // Maybe this will be needed to simulate a slow network: + // https://docs.cypress.io/api/commands/intercept#Convenience-functions-1 + // cy.get('[data-cy=collection-files-panel]') + // .contains('5mb_a.bin').should('not.exist'); + // cy.get('[data-cy=collection-files-panel]') + // .contains('5mb_b.bin').should('not.exist'); }); }); }); diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js index 105657ef..7fd09124 100644 --- a/cypress/integration/favorites.spec.js +++ b/cypress/integration/favorites.spec.js @@ -64,7 +64,7 @@ describe('Favorites tests', function () { cy.loginAs(activeUser); cy.goToPath(`/collections/${testSourceCollection.uuid}`); cy.get('[data-cy=collection-files-panel]').contains('bar'); - cy.get('[data-cy=collection-files-panel]').find('input[type=checkbox]').click({ force: true }); + cy.get('[data-cy=collection-files-panel]').find('input[type=checkbox]').click(); cy.get('[data-cy=collection-files-panel-options-btn]').click(); cy.get('[data-cy=context-menu]') .contains('Copy selected into the collection').click(); diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx index 05b49363..42408270 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -10,24 +10,38 @@ import AutoSizer from "react-virtualized-auto-sizer"; import servicesProvider from 'common/service-provider'; import { CustomizeTableIcon, DownloadIcon } from 'components/icon/icon'; import { SearchInput } from 'components/search-input/search-input'; -import { ListItemIcon, StyleRulesCallback, Theme, WithStyles, withStyles, Tooltip, IconButton, Checkbox, CircularProgress, Button } from '@material-ui/core'; +import { + ListItemIcon, + StyleRulesCallback, + Theme, + WithStyles, + withStyles, + Tooltip, + IconButton, + Checkbox, + CircularProgress, + Button, +} from '@material-ui/core'; import { FileTreeData } from '../file-tree/file-tree-data'; import { TreeItem, TreeItemStatus } from '../tree/tree'; import { RootState } from 'store/store'; import { WebDAV, WebDAVRequestConfig } from 'common/webdav'; import { AuthState } from 'store/auth/auth-reducer'; import { extractFilesData } from 'services/collection-service/collection-service-files-response'; -import { DefaultIcon, DirectoryIcon, FileIcon, BackIcon, SidePanelRightArrowIcon } from 'components/icon/icon'; +import { + DefaultIcon, + DirectoryIcon, + FileIcon, + BackIcon, + SidePanelRightArrowIcon +} from 'components/icon/icon'; import { setCollectionFiles } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions'; import { sortBy } from 'lodash'; import { formatFileSize } from 'common/formatters'; import { getInlineFileUrl, sanitizeToken } from 'views-components/context-menu/actions/helpers'; export interface CollectionPanelFilesProps { - items: any; isWritable: boolean; - isLoading: boolean; - tooManyFiles: boolean; onUploadDataClick: (targetLocation?: string) => void; onSearchChange: (searchValue: string) => void; onItemMenuOpen: (event: React.MouseEvent, item: TreeItem, isWritable: boolean) => void; @@ -35,14 +49,35 @@ export interface CollectionPanelFilesProps { onSelectionToggle: (event: React.MouseEvent, item: TreeItem) => void; onCollapseToggle: (id: string, status: TreeItemStatus) => void; onFileClick: (id: string) => void; - loadFilesFunc: () => void; currentItemUuid: any; dispatch: Function; collectionPanelFiles: any; collectionPanel: any; } -type CssRules = "backButton" | "backButtonHidden" | "pathPanelPathWrapper" | "uploadButton" | "uploadIcon" | "loader" | "wrapper" | "dataWrapper" | "row" | "rowEmpty" | "leftPanel" | "rightPanel" | "pathPanel" | "pathPanelItem" | "rowName" | "listItemIcon" | "rowActive" | "pathPanelMenu" | "rowSelection" | "leftPanelHidden" | "leftPanelVisible" | "searchWrapper" | "searchWrapperHidden"; +type CssRules = "backButton" + | "backButtonHidden" + | "pathPanelPathWrapper" + | "uploadButton" + | "uploadIcon" + | "loader" + | "wrapper" + | "dataWrapper" + | "row" + | "rowEmpty" + | "leftPanel" + | "rightPanel" + | "pathPanel" + | "pathPanelItem" + | "rowName" + | "listItemIcon" + | "rowActive" + | "pathPanelMenu" + | "rowSelection" + | "leftPanelHidden" + | "leftPanelVisible" + | "searchWrapper" + | "searchWrapperHidden"; const styles: StyleRulesCallback = (theme: Theme) => ({ wrapper: { @@ -198,8 +233,8 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState }; const parentRef = React.useRef(null); - const [path, setPath]: any = React.useState([]); - const [pathData, setPathData]: any = React.useState({}); + const [path, setPath] = React.useState([]); + const [pathData, setPathData] = React.useState({}); const [isLoading, setIsLoading] = React.useState(false); const [leftSearch, setLeftSearch] = React.useState(''); const [rightSearch, setRightSearch] = React.useState(''); @@ -220,12 +255,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const fetchData = (keys, ignoreCache = false) => { const keyArray = Array.isArray(keys) ? keys : [keys]; - Promise.all(keyArray + Promise.all(keyArray.filter(key => !!key) .map((key) => { const dataExists = !!pathData[key]; const runningRequest = pathPromise[key]; - if ((!dataExists || ignoreCache) && (!runningRequest || ignoreCache)) { + if (ignoreCache || (!dataExists && !runningRequest)) { if (!isLoading) { setIsLoading(true); } @@ -239,34 +274,34 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState }) .filter((promise) => !!promise) ) - .then((requests) => { - const newState = requests.map((request, index) => { - if (request && request.responseXML != null) { - const key = keyArray[index]; - const result: any = extractFilesData(request.responseXML); - const sortedResult = sortBy(result, (n) => n.name).sort((n1, n2) => { - if (n1.type === 'directory' && n2.type !== 'directory') { - return -1; - } - if (n1.type !== 'directory' && n2.type === 'directory') { - return 1; - } - return 0; - }); - - return { [key]: sortedResult }; - } - return {}; - }).reduce((prev, next) => { - return { ...next, ...prev }; - }, {}); + .then((requests) => { + const newState = requests.map((request, index) => { + if (request && request.responseXML != null) { + const key = keyArray[index]; + const result: any = extractFilesData(request.responseXML); + const sortedResult = sortBy(result, (n) => n.name).sort((n1, n2) => { + if (n1.type === 'directory' && n2.type !== 'directory') { + return -1; + } + if (n1.type !== 'directory' && n2.type === 'directory') { + return 1; + } + return 0; + }); - setPathData({ ...pathData, ...newState }); - }) - .finally(() => { - setIsLoading(false); - keyArray.forEach(key => delete pathPromise[key]); - }); + return { [key]: sortedResult }; + } + return {}; + }).reduce((prev, next) => { + return { ...next, ...prev }; + }, {}); + + setPathData({ ...pathData, ...newState }); + }) + .finally(() => { + setIsLoading(false); + keyArray.forEach(key => delete pathPromise[key]); + }); }; React.useEffect(() => { @@ -280,7 +315,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const currentPDH = (collectionPanel.item || {}).portableDataHash; React.useEffect(() => { if (currentPDH) { - fetchData([leftKey, rightKey], true); + // Avoid fetching the same content level twice + if (leftKey !== rightKey) { + fetchData([leftKey, rightKey], true); + } else { + fetchData(rightKey, true); + } } }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps @@ -315,13 +355,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState onItemMenuOpen(event, item, isWritable); } }, - [onItemMenuOpen, isWritable, rightData] // eslint-disable-line react-hooks/exhaustive-deps - ); + [onItemMenuOpen, isWritable, rightData]); React.useEffect(() => { let node = null; - if (parentRef && parentRef.current) { + if (parentRef?.current) { node = parentRef.current; (node as any).addEventListener('contextmenu', handleRightClick); } @@ -419,149 +458,107 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps ); - return ( -
-
-
- { - path - .map((p: string, index: number) => - {index === 0 ? 'Home' : p} /  - ) - } -
- - { - onOptionsMenuOpen(ev, isWritable); - }}> - + return
+
+
+ { path.map( (p: string, index: number) => + + {index === 0 ? 'Home' : p} /  + ) + } +
+ + { + onOptionsMenuOpen(ev, isWritable); + }}> + + + +
+
+
1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel"> + 1 ? classes.backButton : classes.backButtonHidden}> + setPath([...path.slice(0, path.length -1)])}> + +
1 ? classes.searchWrapper : classes.searchWrapperHidden}> + +
+
{ leftData + ? {({ height, width }) => { + const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1); + return !!filtered.length + ? { ({ index, style }) => { + const { id, type, name } = filtered[index]; + return
+ { getItemIcon(type, getActiveClass(name)) } +
+ {name} +
+ { getActiveClass(name) + ? + : null + } +
; + }}
+ :
No directories available
+ }} +
+ :
} +
-
-
1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel"> - 1 ? classes.backButton : classes.backButtonHidden}> - setPath([...path.slice(0, path.length -1)])}> - - - -
1 ? classes.searchWrapper : classes.searchWrapperHidden}> - -
-
- { - leftData ? - - {({ height, width }) => { - const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1); - - return !!filtered.length ? - { - ({ index, style }) => { - const { id, type, name } = filtered[index]; - - return
- {getItemIcon(type, getActiveClass(name))} -
- {name} -
- { - getActiveClass(name) ? : null - } -
; - } - } -
:
No directories available
- }} -
:
- } - -
+
+
+
-
-
- -
- { - isWritable && - - } -
- { - rightData && !isLoading ? - - {({ height, width }) => { - const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1); - - return !!filtered.length ? - { - ({ index, style }) => { - const { id, type, name, size } = filtered[index]; - - return
-   - {getItemIcon(type, null)}
- {name} -
- - {formatFileSize(size)} - -
- } - } -
:
This collection is empty
- }} -
:
- } -
+ { isWritable && + } +
{ rightData && !isLoading + ? {({ height, width }) => { + const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1); + return !!filtered.length + ? { ({ index, style }) => { + const { id, type, name, size } = filtered[index]; + + return
+   + {getItemIcon(type, null)} +
+ {name} +
+ + { formatFileSize(size) } + +
+ } }
+ :
This collection is empty
+ }}
+ :
+ +
}
- ); -})); +
})); diff --git a/src/store/collection-panel/collection-panel-action.ts b/src/store/collection-panel/collection-panel-action.ts index c50ff6a8..7bab8632 100644 --- a/src/store/collection-panel/collection-panel-action.ts +++ b/src/store/collection-panel/collection-panel-action.ts @@ -3,9 +3,6 @@ // SPDX-License-Identifier: AGPL-3.0 import { Dispatch } from "redux"; -import { - COLLECTION_PANEL_LOAD_FILES_THRESHOLD -} from "./collection-panel-files/collection-panel-files-actions"; import { CollectionResource } from 'models/collection'; import { RootState } from "store/store"; import { ServiceRepository } from "services/services"; @@ -18,8 +15,6 @@ import { loadDetailsPanel } from 'store/details-panel/details-panel-action'; export const collectionPanelActions = unionize({ SET_COLLECTION: ofType(), - LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(), - LOAD_BIG_COLLECTIONS: ofType(), }); export type CollectionPanelAction = UnionOf; @@ -27,15 +22,15 @@ export type CollectionPanelAction = UnionOf; export const loadCollectionPanel = (uuid: string, forceReload = false) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const { collectionPanel: { item } } = getState(); - const collection = (item && item.uuid === uuid && !forceReload) - ? item - : await services.collectionService.get(uuid); - dispatch(loadDetailsPanel(collection.uuid)); - dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: collection })); - dispatch(resourcesActions.SET_RESOURCES([collection])); - if (collection.fileCount <= COLLECTION_PANEL_LOAD_FILES_THRESHOLD && - !getState().collectionPanel.loadBigCollections) { + let collection: CollectionResource | null = null; + if (!item || item.uuid !== uuid || forceReload) { + collection = await services.collectionService.get(uuid); + dispatch(collectionPanelActions.SET_COLLECTION(collection)); + dispatch(resourcesActions.SET_RESOURCES([collection])); + } else { + collection = item; } + dispatch(loadDetailsPanel(collection.uuid)); return collection; }; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts index 71e1f6e8..8c5e5b5a 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts @@ -15,8 +15,6 @@ import { filterCollectionFilesBySelection } from './collection-panel-files-state import { startSubmit, stopSubmit, initialize, FormErrors } from 'redux-form'; import { getDialog } from "store/dialog/dialog-reducer"; import { getFileFullPath, sortFilesTree } from "services/collection-service/collection-service-files-response"; -import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; -import { loadCollectionPanel } from "../collection-panel-action"; export const collectionPanelFilesAction = unionize({ SET_COLLECTION_FILES: ofType(), @@ -30,7 +28,6 @@ export const collectionPanelFilesAction = unionize({ export type CollectionPanelFilesAction = UnionOf; export const COLLECTION_PANEL_LOAD_FILES = 'collectionPanelLoadFiles'; -export const COLLECTION_PANEL_LOAD_FILES_THRESHOLD = 40000; export const setCollectionFiles = (files, joinParents = true) => (dispatch: any) => { const tree = createCollectionFilesTree(files, joinParents); @@ -39,33 +36,11 @@ export const setCollectionFiles = (files, joinParents = true) => (dispatch: any) dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(mapped)); }; -export const loadCollectionFiles = (uuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PANEL_LOAD_FILES)); - services.collectionService.files(uuid).then(files => { - // Given the array of directories and files, create the appropriate tree nodes, - // sort them, and add the complete url to each. - const tree = createCollectionFilesTree(files); - const sorted = sortFilesTree(tree); - const mapped = mapTreeValues(services.collectionService.extendFileURL)(sorted); - dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(mapped)); - dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PANEL_LOAD_FILES)); - }).catch(() => { - dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_PANEL_LOAD_FILES)); - dispatch(snackbarActions.OPEN_SNACKBAR({ - message: `Error getting file list`, - hideDuration: 2000, - kind: SnackbarKind.ERROR - })); - }); - }; - export const removeCollectionFiles = (filePaths: string[]) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const currentCollection = getState().collectionPanel.item; if (currentCollection) { services.collectionService.deleteFiles(currentCollection.uuid, filePaths).then(() => { - dispatch(loadCollectionPanel(currentCollection.uuid, true)); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, @@ -155,7 +130,6 @@ export const renameFile = (newFullPath: string) => const oldPath = getFileFullPath(file); const newPath = newFullPath; services.collectionService.moveFile(currentCollection.uuid, oldPath, newPath).then(() => { - dispatch(loadCollectionPanel(currentCollection.uuid, true)); dispatch(dialogActions.CLOSE_DIALOG({ id: RENAME_FILE_DIALOG })); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File name changed.', hideDuration: 2000 })); }).catch(e => { diff --git a/src/store/collection-panel/collection-panel-reducer.ts b/src/store/collection-panel/collection-panel-reducer.ts index a6aa87bd..6afba66c 100644 --- a/src/store/collection-panel/collection-panel-reducer.ts +++ b/src/store/collection-panel/collection-panel-reducer.ts @@ -7,12 +7,10 @@ import { CollectionResource } from "models/collection"; export interface CollectionPanelState { item: CollectionResource | null; - loadBigCollections: boolean; } const initialState = { item: null, - loadBigCollections: false, }; export const collectionPanelReducer = (state: CollectionPanelState = initialState, action: CollectionPanelAction) => @@ -21,8 +19,5 @@ export const collectionPanelReducer = (state: CollectionPanelState = initialStat SET_COLLECTION: (item) => ({ ...state, item, - loadBigCollections: false, }), - LOAD_COLLECTION_SUCCESS: ({ item }) => ({ ...state, item }), - LOAD_BIG_COLLECTIONS: (loadBigCollections) => ({ ...state, loadBigCollections}), }); diff --git a/src/store/collections/collection-update-actions.ts b/src/store/collections/collection-update-actions.ts index 82418d27..bf9c6449 100644 --- a/src/store/collections/collection-update-actions.ts +++ b/src/store/collections/collection-update-actions.ts @@ -55,7 +55,7 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) => properties: collection.properties } ).then(updatedCollection => { updatedCollection = {...cachedCollection, ...updatedCollection}; - dispatch(collectionPanelActions.LOAD_COLLECTION_SUCCESS({ item: updatedCollection as CollectionResource })); + dispatch(collectionPanelActions.SET_COLLECTION(updatedCollection)); dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_UPDATE_FORM_NAME })); dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_UPDATE_FORM_NAME)); dispatch(snackbarActions.OPEN_SNACKBAR({ diff --git a/src/store/collections/collection-upload-actions.ts b/src/store/collections/collection-upload-actions.ts index 135538b0..e9c5cc35 100644 --- a/src/store/collections/collection-upload-actions.ts +++ b/src/store/collections/collection-upload-actions.ts @@ -6,14 +6,10 @@ import { Dispatch } from 'redux'; import { RootState } from 'store/store'; import { ServiceRepository } from 'services/services'; import { dialogActions } from 'store/dialog/dialog-actions'; -import { loadCollectionFiles } from '../collection-panel/collection-panel-files/collection-panel-files-actions'; import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions'; import { fileUploaderActions } from 'store/file-uploader/file-uploader-actions'; import { reset, startSubmit, stopSubmit } from 'redux-form'; import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions"; -import { collectionPanelFilesAction } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions'; -import { createTree } from 'models/tree'; -import { loadCollectionPanel } from '../collection-panel/collection-panel-action'; import * as WorkbenchActions from 'store/workbench/workbench-actions'; export const uploadCollectionFiles = (collectionUuid: string, targetLocation?: string) => @@ -40,10 +36,7 @@ export const submitCollectionFiles = (targetLocation?: string) => try { dispatch(progressIndicatorActions.START_WORKING(COLLECTION_UPLOAD_FILES_DIALOG)); dispatch(startSubmit(COLLECTION_UPLOAD_FILES_DIALOG)); - await dispatch(uploadCollectionFiles(currentCollection.uuid, targetLocation)) - .then(() => dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() }))); - dispatch(loadCollectionFiles(currentCollection.uuid)); - dispatch(loadCollectionPanel(currentCollection.uuid)); + await dispatch(uploadCollectionFiles(currentCollection.uuid, targetLocation)); dispatch(closeUploadCollectionFilesDialog()); dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Data has been uploaded.', diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index d2ff84b3..0a348431 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -100,8 +100,6 @@ import { subprocessPanelActions } from 'store/subprocess-panel/subprocess-panel- import { subprocessPanelColumns } from 'views/subprocess-panel/subprocess-panel-root'; import { loadAllProcessesPanel, allProcessesPanelActions } from '../all-processes-panel/all-processes-panel-action'; import { allProcessesPanelColumns } from 'views/all-processes-panel/all-processes-panel'; -import { collectionPanelFilesAction } from '../collection-panel/collection-panel-files/collection-panel-files-actions'; -import { createTree } from 'models/tree'; import { AdminMenuIcon } from 'components/icon/icon'; import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root'; @@ -295,11 +293,9 @@ export const loadCollection = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const userUuid = getUserUuid(getState()); if (userUuid) { - // Clear collection files panel - dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() })); const match = await loadGroupContentsResource({ uuid, userUuid, services }); match({ - OWNED: async collection => { + OWNED: collection => { dispatch(collectionPanelActions.SET_COLLECTION(collection as CollectionResource)); dispatch(updateResources([collection])); dispatch(activateSidePanelTreeItem(collection.ownerUuid)); diff --git a/src/store/workflow-panel/workflow-panel-actions.ts b/src/store/workflow-panel/workflow-panel-actions.ts index 7c90fa6b..912f7630 100644 --- a/src/store/workflow-panel/workflow-panel-actions.ts +++ b/src/store/workflow-panel/workflow-panel-actions.ts @@ -9,13 +9,18 @@ import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-actio import { propertiesActions } from 'store/properties/properties-actions'; import { getProperty } from 'store/properties/properties'; import { navigateToRunProcess } from 'store/navigation/navigation-action'; -import { goToStep, runProcessPanelActions, loadPresets, getWorkflowRunnerSettings } from 'store/run-process-panel/run-process-panel-actions'; +import { + goToStep, + runProcessPanelActions, + loadPresets, + getWorkflowRunnerSettings +} from 'store/run-process-panel/run-process-panel-actions'; import { snackbarActions } from 'store/snackbar/snackbar-actions'; import { initialize } from 'redux-form'; import { RUN_PROCESS_BASIC_FORM } from 'views/run-process-panel/run-process-basic-form'; import { RUN_PROCESS_INPUTS_FORM } from 'views/run-process-panel/run-process-inputs-form'; import { RUN_PROCESS_ADVANCED_FORM } from 'views/run-process-panel/run-process-advanced-form'; -import { getResource, ResourcesState } from 'store/resources/resources'; +import { getResource } from 'store/resources/resources'; import { ProjectResource } from 'models/project'; import { UserResource } from 'models/user'; import { getUserUuid } from "common/getuser"; diff --git a/src/views-components/collection-panel-files/collection-panel-files.ts b/src/views-components/collection-panel-files/collection-panel-files.ts index 216ec669..a26b9fe3 100644 --- a/src/views-components/collection-panel-files/collection-panel-files.ts +++ b/src/views-components/collection-panel-files/collection-panel-files.ts @@ -8,41 +8,17 @@ import { CollectionPanelFilesProps } from "components/collection-panel-files/collection-panel-files"; import { RootState } from "store/store"; -import { TreeItemStatus } from "components/tree/tree"; -import { VirtualTreeItem as TreeItem } from "components/tree/virtual-tree"; -import { - CollectionPanelDirectory, - CollectionPanelFile, - CollectionPanelFilesState -} from "store/collection-panel/collection-panel-files/collection-panel-files-state"; -import { FileTreeData } from "components/file-tree/file-tree-data"; import { Dispatch } from "redux"; import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions"; import { ContextMenuKind } from "../context-menu/context-menu"; -import { getNode, getNodeChildrenIds, Tree, TreeNode, initTreeNode } from "models/tree"; -import { CollectionFileType, createCollectionDirectory } from "models/collection-file"; import { openContextMenu, openCollectionFilesContextMenu } from 'store/context-menu/context-menu-actions'; import { openUploadCollectionFilesDialog } from 'store/collections/collection-upload-actions'; import { ResourceKind } from "models/resource"; import { openDetailsPanel } from 'store/details-panel/details-panel-action'; -const memoizedMapStateToProps = () => { - let prevState: CollectionPanelFilesState; - let prevTree: Array>; - - return (state: RootState): Pick => { - if (prevState !== state.collectionPanelFiles) { - prevState = state.collectionPanelFiles; - prevTree = [].concat.apply( - [], getNodeChildrenIds('')(state.collectionPanelFiles) - .map(collectionItemToList(0)(state.collectionPanelFiles))); - } - return { - items: prevTree, - currentItemUuid: state.detailsPanel.resourceUuid - }; - }; -}; +const mapStateToProps = (state: RootState): Pick => ({ + currentItemUuid: state.detailsPanel.resourceUuid +}); const mapDispatchToProps = (dispatch: Dispatch): Pick => ({ onUploadDataClick: (targetLocation?: string) => { @@ -84,43 +60,4 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick (tree: Tree) => - (id: string): TreeItem[] => { - const node: TreeNode = getNode(id)(tree) || initTreeNode({ - id: '', - parent: '', - value: { - ...createCollectionDirectory({ name: 'Invalid file' }), - selected: false, - collapsed: true - } - }); - - const treeItem = { - active: false, - data: { - name: node.value.name, - size: node.value.type === CollectionFileType.FILE ? node.value.size : undefined, - type: node.value.type, - url: node.value.url, - }, - id: node.id, - items: [], // Not used in this case as we're converting a tree to a list. - itemCount: node.children.length, - open: node.value.type === CollectionFileType.DIRECTORY ? !node.value.collapsed : false, - selected: node.value.selected, - status: TreeItemStatus.LOADED, - level, - }; - - const treeItemChilds = treeItem.open - ? [].concat.apply([], node.children.map(collectionItemToList(level+1)(tree))) - : []; - - return [ - treeItem, - ...treeItemChilds, - ]; - }; +export const CollectionPanelFiles = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/src/views-components/details-panel/workflow-details.tsx b/src/views-components/details-panel/workflow-details.tsx index 4c4bd2de..98978dd2 100644 --- a/src/views-components/details-panel/workflow-details.tsx +++ b/src/views-components/details-panel/workflow-details.tsx @@ -3,10 +3,9 @@ // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; -import { DefaultIcon, WorkflowIcon } from 'components/icon/icon'; +import { WorkflowIcon } from 'components/icon/icon'; import { WorkflowResource } from 'models/workflow'; import { DetailsData } from "./details-data"; -import { DefaultView } from 'components/default-view/default-view'; import { DetailsAttribute } from 'components/details-attribute/details-attribute'; import { ResourceWithName } from 'views-components/data-explorer/renderers'; import { formatDate } from "common/formatters"; diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index 17d35aea..9d127a60 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -21,7 +21,7 @@ import { MoreOptionsIcon, CollectionIcon, ReadOnlyIcon, CollectionOldVersionIcon import { DetailsAttribute } from 'components/details-attribute/details-attribute'; import { CollectionResource, getCollectionUrl } from 'models/collection'; import { CollectionPanelFiles } from 'views-components/collection-panel-files/collection-panel-files'; -import { navigateToProcess, collectionPanelActions } from 'store/collection-panel/collection-panel-action'; +import { navigateToProcess } from 'store/collection-panel/collection-panel-action'; import { getResource } from 'store/resources/resources'; import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions'; import { formatDate, formatFileSize } from "common/formatters"; @@ -32,8 +32,6 @@ import { IllegalNamingWarning } from 'components/warning/warning'; import { GroupResource } from 'models/group'; import { UserResource } from 'models/user'; import { getUserUuid } from 'common/getuser'; -import { getProgressIndicator } from 'store/progress-indicator/progress-indicator-reducer'; -import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD_FILES_THRESHOLD } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions'; import { Link } from 'react-router-dom'; import { Link as ButtonLink } from '@material-ui/core'; import { ResourceWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers'; @@ -115,14 +113,12 @@ interface CollectionPanelDataProps { isWritable: boolean; isOldVersion: boolean; isLoadingFiles: boolean; - tooManyFiles: boolean; } -type CollectionPanelProps = CollectionPanelDataProps & DispatchProp - & WithStyles & RouteComponentProps<{ id: string }>; +type CollectionPanelProps = CollectionPanelDataProps & DispatchProp & WithStyles -export const CollectionPanel = withStyles(styles)( - connect((state: RootState, props: RouteComponentProps<{ id: string }>) => { +export const CollectionPanel = withStyles(styles)(connect( + (state: RootState, props: RouteComponentProps<{ id: string }>) => { const currentUserUUID = getUserUuid(state); const item = getResource(props.match.params.id)(state.resources); let isWritable = false; @@ -137,14 +133,11 @@ export const CollectionPanel = withStyles(styles)( } } } - const loadingFilesIndicator = getProgressIndicator(COLLECTION_PANEL_LOAD_FILES)(state.progressIndicator); - const isLoadingFiles = (loadingFilesIndicator && loadingFilesIndicator!.working) || false; - const tooManyFiles = (!state.collectionPanel.loadBigCollections && item && item.fileCount > COLLECTION_PANEL_LOAD_FILES_THRESHOLD) || false; - return { item, isWritable, isOldVersion, isLoadingFiles, tooManyFiles }; + return { item, isWritable, isOldVersion }; })( class extends React.Component { render() { - const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props; + const { classes, item, dispatch, isWritable, isOldVersion } = this.props; const panelsData: MPVPanelState[] = [ { name: "Details" }, { name: "Files" }, @@ -203,15 +196,7 @@ export const CollectionPanel = withStyles(styles)( - { - dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true)); - dispatch(loadCollectionFiles(this.props.item.uuid)); - } - } /> +