});
// 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')
.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')
['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();
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();
});
cy.get('[data-cy=form-submit-btn]').click();
- cy.wait(1000);
cy.get('[data-cy=collection-files-panel]')
.contains('Home')
.click();
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 => {
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');
});
});
});
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();
cy.get('[data-cy=element-path]').should('contain', `/ Projects / ${colName}`);
});
});
+
+ it('can display owner of the item', function() {
+ const colName = `Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
+
+ cy.createCollection(adminUser.token, {
+ name: colName,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+ }).then(function() {
+ cy.loginAs(activeUser);
+
+ cy.doSearch(colName);
+
+ cy.get('[data-cy=search-results]').should('contain', colName);
+
+ cy.get('[data-cy=search-results]').contains(colName).closest('tr')
+ .within(() => {
+ cy.get('p').contains(activeUser.user.uuid).should('contain', activeUser.user.full_name);
+ });
+ });
+ });
});
\ No newline at end of file
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<HTMLElement>, item: TreeItem<FileTreeData>, isWritable: boolean) => void;
onSelectionToggle: (event: React.MouseEvent<HTMLElement>, item: TreeItem<FileTreeData>) => 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<CssRules> = (theme: Theme) => ({
wrapper: {
};
const parentRef = React.useRef(null);
- const [path, setPath]: any = React.useState([]);
- const [pathData, setPathData]: any = React.useState({});
+ const [path, setPath] = React.useState<string[]>([]);
+ const [pathData, setPathData] = React.useState({});
const [isLoading, setIsLoading] = React.useState(false);
const [leftSearch, setLeftSearch] = React.useState('');
const [rightSearch, setRightSearch] = React.useState('');
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);
}
})
.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(() => {
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
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);
}
[props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
);
- return (
- <div data-cy="collection-files-panel" onClick={handleClick} ref={parentRef}>
- <div className={classes.pathPanel}>
- <div className={classes.pathPanelPathWrapper}>
- {
- path
- .map((p: string, index: number) => <span
- key={`${index}-${p}`}
- data-item="true"
- className={classes.pathPanelItem}
- data-breadcrumb-path={p}
- >
- <span className={classes.rowActive}>{index === 0 ? 'Home' : p}</span> <b>/</b>
- </span>)
- }
- </div>
- <Tooltip className={classes.pathPanelMenu} title="More options" disableFocusListener>
- <IconButton
- data-cy='collection-files-panel-options-btn'
- onClick={(ev) => {
- onOptionsMenuOpen(ev, isWritable);
- }}>
- <CustomizeTableIcon />
+ return <div data-cy="collection-files-panel" onClick={handleClick} ref={parentRef}>
+ <div className={classes.pathPanel}>
+ <div className={classes.pathPanelPathWrapper}>
+ { path.map( (p: string, index: number) =>
+ <span key={`${index}-${p}`} data-item="true"
+ className={classes.pathPanelItem} data-breadcrumb-path={p}>
+ <span className={classes.rowActive}>{index === 0 ? 'Home' : p}</span> <b>/</b>
+ </span>)
+ }
+ </div>
+ <Tooltip className={classes.pathPanelMenu} title="More options" disableFocusListener>
+ <IconButton data-cy='collection-files-panel-options-btn'
+ onClick={(ev) => {
+ onOptionsMenuOpen(ev, isWritable);
+ }}>
+ <CustomizeTableIcon />
+ </IconButton>
+ </Tooltip>
+ </div>
+ <div className={classes.wrapper}>
+ <div className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel">
+ <Tooltip title="Go back" className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
+ <IconButton onClick={() => setPath([...path.slice(0, path.length -1)])}>
+ <BackIcon />
</IconButton>
</Tooltip>
+ <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
+ <SearchInput selfClearProp={leftKey} label="Search" value={leftSearch} onSearch={setLeftSearch} />
+ </div>
+ <div className={classes.dataWrapper}>{ leftData
+ ? <AutoSizer defaultWidth={0}>{({ height, width }) => {
+ const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
+ return !!filtered.length
+ ? <FixedSizeList height={height} itemCount={filtered.length}
+ itemSize={35} width={width}>{ ({ index, style }) => {
+ const { id, type, name } = filtered[index];
+ return <div data-id={id} style={style} data-item="true"
+ data-type={type} data-parent-path={name}
+ className={classNames(classes.row, getActiveClass(name))}
+ key={id}>
+ { getItemIcon(type, getActiveClass(name)) }
+ <div className={classes.rowName}>
+ {name}
+ </div>
+ { getActiveClass(name)
+ ? <SidePanelRightArrowIcon
+ style={{ display: 'inline', marginTop: '5px', marginLeft: '5px' }} />
+ : null
+ }
+ </div>;
+ }}</FixedSizeList>
+ : <div className={classes.rowEmpty}>No directories available</div>
+ }}
+ </AutoSizer>
+ : <div className={classes.row}><CircularProgress className={classes.loader} size={30} /></div> }
+ </div>
</div>
- <div className={classes.wrapper}>
- <div className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel">
- <Tooltip title="Go back" className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
- <IconButton onClick={() => setPath([...path.slice(0, path.length -1)])}>
- <BackIcon />
- </IconButton>
- </Tooltip>
- <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
- <SearchInput selfClearProp={leftKey} label="Search" value={leftSearch} onSearch={setLeftSearch} />
- </div>
- <div className={classes.dataWrapper}>
- {
- leftData ?
- <AutoSizer defaultWidth={0}>
- {({ height, width }) => {
- const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
-
- return !!filtered.length ? <FixedSizeList
- height={height}
- itemCount={filtered.length}
- itemSize={35}
- width={width}
- >
- {
- ({ index, style }) => {
- const { id, type, name } = filtered[index];
-
- return <div
- data-id={id}
- style={style}
- data-item="true"
- data-type={type}
- data-parent-path={name}
- className={classNames(classes.row, getActiveClass(name))}
- key={id}>
- {getItemIcon(type, getActiveClass(name))}
- <div className={classes.rowName}>
- {name}
- </div>
- {
- getActiveClass(name) ? <SidePanelRightArrowIcon
- style={{ display: 'inline', marginTop: '5px', marginLeft: '5px' }} /> : null
- }
- </div>;
- }
- }
- </FixedSizeList> : <div className={classes.rowEmpty}>No directories available</div>
- }}
- </AutoSizer> : <div className={classes.row}><CircularProgress className={classes.loader} size={30} /></div>
- }
-
- </div>
+ <div className={classes.rightPanel} data-cy="collection-files-right-panel">
+ <div className={classes.searchWrapper}>
+ <SearchInput selfClearProp={rightKey} label="Search" value={rightSearch} onSearch={setRightSearch} />
</div>
- <div className={classes.rightPanel} data-cy="collection-files-right-panel">
- <div className={classes.searchWrapper}>
- <SearchInput selfClearProp={rightKey} label="Search" value={rightSearch} onSearch={setRightSearch} />
- </div>
- {
- isWritable &&
- <Button
- className={classes.uploadButton}
- data-cy='upload-button'
- onClick={() => {
- onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
- }}
- variant='contained'
- color='primary'
- size='small'>
- <DownloadIcon className={classes.uploadIcon} />
- Upload data
- </Button>
- }
- <div className={classes.dataWrapper}>
- {
- rightData && !isLoading ?
- <AutoSizer defaultHeight={500}>
- {({ height, width }) => {
- const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
-
- return !!filtered.length ? <FixedSizeList
- height={height}
- itemCount={filtered.length}
- itemSize={35}
- width={width}
- >
- {
- ({ index, style }) => {
- const { id, type, name, size } = filtered[index];
-
- return <div
- style={style}
- data-id={id}
- data-item="true"
- data-type={type}
- data-subfolder-path={name}
- className={classes.row} key={id}>
- <Checkbox
- color="primary"
- className={classes.rowSelection}
- checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
- />
- {getItemIcon(type, null)} <div className={classes.rowName}>
- {name}
- </div>
- <span className={classes.rowName} style={{ marginLeft: 'auto', marginRight: '1rem' }}>
- {formatFileSize(size)}
- </span>
- </div>
- }
- }
- </FixedSizeList> : <div className={classes.rowEmpty}>This collection is empty</div>
- }}
- </AutoSizer> : <div className={classes.row}><CircularProgress className={classes.loader} size={30} /></div>
- }
- </div>
+ { isWritable &&
+ <Button className={classes.uploadButton} data-cy='upload-button'
+ onClick={() => {
+ onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
+ }}
+ variant='contained' color='primary' size='small'>
+ <DownloadIcon className={classes.uploadIcon} />
+ Upload data
+ </Button> }
+ <div className={classes.dataWrapper}>{ rightData && !isLoading
+ ? <AutoSizer defaultHeight={500}>{({ height, width }) => {
+ const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
+ return !!filtered.length
+ ? <FixedSizeList height={height} itemCount={filtered.length}
+ itemSize={35} width={width}>{ ({ index, style }) => {
+ const { id, type, name, size } = filtered[index];
+
+ return <div style={style} data-id={id} data-item="true"
+ data-type={type} data-subfolder-path={name}
+ className={classes.row} key={id}>
+ <Checkbox color="primary"
+ className={classes.rowSelection}
+ checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
+ />
+ {getItemIcon(type, null)}
+ <div className={classes.rowName}>
+ {name}
+ </div>
+ <span className={classes.rowName} style={{
+ marginLeft: 'auto', marginRight: '1rem' }}>
+ { formatFileSize(size) }
+ </span>
+ </div>
+ } }</FixedSizeList>
+ : <div className={classes.rowEmpty}>This collection is empty</div>
+ }}</AutoSizer>
+ : <div className={classes.row}>
+ <CircularProgress className={classes.loader} size={30} />
+ </div> }
</div>
</div>
</div>
- );
-}));
+ </div>}));
// 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";
export const collectionPanelActions = unionize({
SET_COLLECTION: ofType<CollectionResource>(),
- LOAD_COLLECTION_SUCCESS: ofType<{ item: CollectionResource }>(),
- LOAD_BIG_COLLECTIONS: ofType<boolean>(),
});
export type CollectionPanelAction = UnionOf<typeof collectionPanelActions>;
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<any>(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<any>(loadDetailsPanel(collection.uuid));
return collection;
};
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<CollectionFilesTree>(),
export type CollectionPanelFilesAction = UnionOf<typeof collectionPanelFilesAction>;
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);
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<any>(loadCollectionPanel(currentCollection.uuid, true));
dispatch(snackbarActions.OPEN_SNACKBAR({
message: 'Removed.',
hideDuration: 2000,
const oldPath = getFileFullPath(file);
const newPath = newFullPath;
services.collectionService.moveFile(currentCollection.uuid, oldPath, newPath).then(() => {
- dispatch<any>(loadCollectionPanel(currentCollection.uuid, true));
dispatch(dialogActions.CLOSE_DIALOG({ id: RENAME_FILE_DIALOG }));
dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'File name changed.', hideDuration: 2000 }));
}).catch(e => {
export interface CollectionPanelState {
item: CollectionResource | null;
- loadBigCollections: boolean;
}
const initialState = {
item: null,
- loadBigCollections: false,
};
export const collectionPanelReducer = (state: CollectionPanelState = initialState, action: CollectionPanelAction) =>
SET_COLLECTION: (item) => ({
...state,
item,
- loadBigCollections: false,
}),
- LOAD_COLLECTION_SUCCESS: ({ item }) => ({ ...state, item }),
- LOAD_BIG_COLLECTIONS: (loadBigCollections) => ({ ...state, loadBigCollections}),
});
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({
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) =>
try {
dispatch(progressIndicatorActions.START_WORKING(COLLECTION_UPLOAD_FILES_DIALOG));
dispatch(startSubmit(COLLECTION_UPLOAD_FILES_DIALOG));
- await dispatch<any>(uploadCollectionFiles(currentCollection.uuid, targetLocation))
- .then(() => dispatch<any>(collectionPanelFilesAction.SET_COLLECTION_FILES({ files: createTree() })));
- dispatch<any>(loadCollectionFiles(currentCollection.uuid));
- dispatch<any>(loadCollectionPanel(currentCollection.uuid));
+ await dispatch<any>(uploadCollectionFiles(currentCollection.uuid, targetLocation));
dispatch(closeUploadCollectionFilesDialog());
dispatch(snackbarActions.OPEN_SNACKBAR({
message: 'Data has been uploaded.',
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';
async (dispatch: Dispatch<any>, 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));
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";
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<TreeItem<FileTreeData>>;
-
- return (state: RootState): Pick<CollectionPanelFilesProps, "items" | "currentItemUuid"> => {
- 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<CollectionPanelFilesProps, "currentItemUuid"> => ({
+ currentItemUuid: state.detailsPanel.resourceUuid
+});
const mapDispatchToProps = (dispatch: Dispatch): Pick<CollectionPanelFilesProps, 'onSearchChange' | 'onFileClick' | 'onUploadDataClick' | 'onCollapseToggle' | 'onSelectionToggle' | 'onItemMenuOpen' | 'onOptionsMenuOpen'> => ({
onUploadDataClick: (targetLocation?: string) => {
},
});
-export const CollectionPanelFiles = connect(memoizedMapStateToProps(), mapDispatchToProps)(Component);
-
-const collectionItemToList = (level: number) => (tree: Tree<CollectionPanelDirectory | CollectionPanelFile>) =>
- (id: string): TreeItem<FileTreeData>[] => {
- const node: TreeNode<CollectionPanelDirectory | CollectionPanelFile> = 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);
return resource || { lastName: '' };
})(renderLastName);
-const renderFullName = (dispatch: Dispatch ,item: { uuid: string, firstName: string, lastName: string }, link?: boolean) => {
+const renderFullName = (dispatch: Dispatch, item: { uuid: string, firstName: string, lastName: string }, link?: boolean) => {
const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
return link ? <Typography noWrap
color="primary"
style={{ 'cursor': 'pointer' }}
onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
- {displayName}
+ {displayName}
</Typography> :
- <Typography noWrap>{displayName}</Typography>;
+ <Typography noWrap>{displayName}</Typography>;
}
export const UserResourceFullName = connect(
(state: RootState, props: { uuid: string, link?: boolean }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
- return {item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link};
- })((props: {item: {uuid: string, firstName: string, lastName: string}, link?: boolean} & DispatchProp<any>) => renderFullName(props.dispatch, props.item, props.link));
+ return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
+ })((props: { item: { uuid: string, firstName: string, lastName: string }, link?: boolean } & DispatchProp<any>) => renderFullName(props.dispatch, props.item, props.link));
const renderUuid = (item: { uuid: string }) =>
<Typography data-cy="uuid" noWrap>
</Typography>;
export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => (
- getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' }
- ))(renderUuid);
+ getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' }
+))(renderUuid);
const renderEmail = (item: { email: string }) =>
<Typography noWrap>{item.email}</Typography>;
UNKNOWN = ''
}
-const renderAccountStatus = (props: {status: UserAccountStatus}) =>
+const renderAccountStatus = (props: { status: UserAccountStatus }) =>
<Grid container alignItems="center" wrap="nowrap" spacing={8} data-cy="account-status">
<Grid item>
{(() => {
- switch(props.status) {
+ switch (props.status) {
case UserAccountStatus.ACTIVE:
- return <ActiveIcon style={{color: '#4caf50', verticalAlign: "middle"}} />;
+ return <ActiveIcon style={{ color: '#4caf50', verticalAlign: "middle" }} />;
case UserAccountStatus.SETUP:
- return <SetupIcon style={{color: '#2196f3', verticalAlign: "middle"}} />;
+ return <SetupIcon style={{ color: '#2196f3', verticalAlign: "middle" }} />;
case UserAccountStatus.INACTIVE:
- return <InactiveIcon style={{color: '#9e9e9e', verticalAlign: "middle"}} />;
+ return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: "middle" }} />;
default:
return <></>;
}
)(state.resources);
if (user) {
- return user.isActive ? {status: UserAccountStatus.ACTIVE} : permissions.length > 0 ? {status: UserAccountStatus.SETUP} : {status: UserAccountStatus.INACTIVE};
+ return user.isActive ? { status: UserAccountStatus.ACTIVE } : permissions.length > 0 ? { status: UserAccountStatus.SETUP } : { status: UserAccountStatus.INACTIVE };
} else {
- return {status: UserAccountStatus.UNKNOWN};
+ return { status: UserAccountStatus.UNKNOWN };
}
}
export const ResourceLinkTailAccountStatus = connect(
(state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
- return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, {uuid: link.tailUuid}) : {status: UserAccountStatus.UNKNOWN};
+ return link && link.tailKind === ResourceKind.USER ? getUserAccountStatus(state, { uuid: link.tailUuid }) : { status: UserAccountStatus.UNKNOWN };
})(renderAccountStatus);
export const UserResourceAccountStatus = connect(getUserAccountStatus)(renderAccountStatus);
const renderIsHidden = (props: {
- memberLinkUuid: string,
- permissionLinkUuid: string,
- visible: boolean,
- canManage: boolean,
- setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void
- }) => {
+ memberLinkUuid: string,
+ permissionLinkUuid: string,
+ visible: boolean,
+ canManage: boolean,
+ setMemberIsHidden: (memberLinkUuid: string, permissionLinkUuid: string, hide: boolean) => void
+}) => {
if (props.memberLinkUuid) {
return <Checkbox
- data-cy="user-visible-checkbox"
- color="primary"
- checked={props.visible}
- disabled={!props.canManage}
- onClick={(e) => {
- e.stopPropagation();
- props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
- }} />;
+ data-cy="user-visible-checkbox"
+ color="primary"
+ checked={props.visible}
+ disabled={!props.canManage}
+ onClick={(e) => {
+ e.stopPropagation();
+ props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
+ }} />;
} else {
return <Typography />;
}
return resource || { hostname: '' };
})(renderHostname);
-const renderVirtualMachineLogin = (login: {user: string}) =>
+const renderVirtualMachineLogin = (login: { user: string }) =>
<Typography noWrap>{login.user}</Typography>
export const VirtualMachineLogin = connect(
const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
- return {user: user?.username || permission?.tailUuid || ''};
+ return { user: user?.username || permission?.tailUuid || '' };
})(renderVirtualMachineLogin);
// Common methods
const getResourceDisplayName = (resource: Resource): string => {
if ((resource as UserResource).kind === ResourceKind.USER
- && typeof (resource as UserResource).firstName !== 'undefined') {
+ && typeof (resource as UserResource).firstName !== 'undefined') {
// We can be sure the resource is UserResource
return getUserDisplayName(resource as UserResource);
} else {
</IconButton>
</Typography>;
} else {
- return <Typography noWrap></Typography>;
+ return <Typography noWrap></Typography>;
}
}
canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
};
})((props: { item: LinkResource, canManage: boolean } & DispatchProp<any>) =>
- renderLinkDelete(props.dispatch, props.item, props.canManage));
+ renderLinkDelete(props.dispatch, props.item, props.canManage));
export const ResourceLinkTailEmail = connect(
(state: RootState, props: { uuid: string }) => {
return { uuid: props.uuid, userFullname };
});
-export const ResourceOwnerWithName =
+const ownerFromResourceId =
compose(
- userFromID,
- withStyles({}, { withTheme: true }))
+ connect((state: RootState, props: { uuid: string }) => {
+ const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
+ return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
+ }),
+ userFromID
+ );
+
+const _resourceWithName =
+ withStyles({}, { withTheme: true })
((props: { uuid: string, userFullname: string, dispatch: Dispatch, theme: ArvadosTheme }) => {
const { uuid, userFullname, dispatch, theme } = props;
</Typography>;
});
+export const ResourceOwnerWithName = ownerFromResourceId(_resourceWithName);
+
+export const ResourceWithName = userFromID(_resourceWithName);
+
export const UserNameFromID =
compose(userFromID)(
(props: { uuid: string, userFullname: string, dispatch: Dispatch }) => {
return <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
{responsiblePersonName} ({uuid})
- </Typography>;
+ </Typography>;
});
const renderType = (type: string, subtype: string) =>
withStyles({}, { withTheme: true }))
((props: { process?: Process, theme: ArvadosTheme }) =>
props.process
- ? <Chip label={getProcessStatus(props.process)}
- style={{
- height: props.theme.spacing.unit * 3,
- width: props.theme.spacing.unit * 12,
- backgroundColor: getProcessStatusColor(
- getProcessStatus(props.process), props.theme),
- color: props.theme.palette.common.white,
- fontSize: '0.875rem',
- borderRadius: props.theme.spacing.unit * 0.625,
- }}
- />
- : <Typography>-</Typography>
+ ? <Chip label={getProcessStatus(props.process)}
+ style={{
+ height: props.theme.spacing.unit * 3,
+ width: props.theme.spacing.unit * 12,
+ backgroundColor: getProcessStatusColor(
+ getProcessStatus(props.process), props.theme),
+ color: props.theme.palette.common.white,
+ fontSize: '0.875rem',
+ borderRadius: props.theme.spacing.unit * 0.625,
+ }}
+ />
+ : <Typography>-</Typography>
);
export const ProcessStartDate = connect(
import { ArvadosTheme } from 'common/custom-theme';
import { Dispatch } from 'redux';
import { getPropertyChip } from '../resource-properties-form/property-chip';
-import { ResourceOwnerWithName } from '../data-explorer/renderers';
+import { ResourceWithName } from '../data-explorer/renderers';
import { GroupClass } from "models/group";
import { openProjectUpdateDialog, ProjectUpdateFormDialogData } from 'store/projects/project-update-actions';
marginBottom: theme.spacing.unit / 2,
},
editIcon: {
- paddingRight: theme.spacing.unit/2,
+ paddingRight: theme.spacing.unit / 2,
fontSize: '1.125rem',
},
editButton: {
withStyles(styles)(
({ classes, project, onClick }: ProjectDetailsComponentProps) => <div>
{project.groupClass !== GroupClass.FILTER ?
- <Button onClick={onClick({
- uuid: project.uuid,
- name: project.name,
- description: project.description,
- properties: project.properties,
- })}
- className={classes.editButton} variant='contained'
- data-cy='details-panel-edit-btn' color='primary' size='small'>
- <RenameIcon className={classes.editIcon} /> Edit
- </Button>
- : ''
- }
+ <Button onClick={onClick({
+ uuid: project.uuid,
+ name: project.name,
+ description: project.description,
+ properties: project.properties,
+ })}
+ className={classes.editButton} variant='contained'
+ data-cy='details-panel-edit-btn' color='primary' size='small'>
+ <RenameIcon className={classes.editIcon} /> Edit
+ </Button>
+ : ''
+ }
<DetailsAttribute label='Type' value={project.groupClass === GroupClass.FILTER ? 'Filter group' : resourceLabel(ResourceKind.PROJECT)} />
<DetailsAttribute label='Owner' linkToUuid={project.ownerUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
<DetailsAttribute label='Last modified' value={formatDate(project.modifiedAt)} />
<DetailsAttribute label='Created at' value={formatDate(project.createdAt)} />
<DetailsAttribute label='UUID' linkToUuid={project.uuid} value={project.uuid} />
{
Object.keys(project.properties).map(k =>
Array.isArray(project.properties[k])
- ? project.properties[k].map((v: string) =>
- getPropertyChip(k, v, undefined, classes.tag))
- : getPropertyChip(k, project.properties[k], undefined, classes.tag)
+ ? project.properties[k].map((v: string) =>
+ getPropertyChip(k, v, undefined, classes.tag))
+ : getPropertyChip(k, project.properties[k], undefined, classes.tag)
)
}
</div>
// 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 { ResourceOwnerWithName } from 'views-components/data-explorer/renderers';
+import { ResourceWithName } from 'views-components/data-explorer/renderers';
import { formatDate } from "common/formatters";
import { Grid } from '@material-ui/core';
import { withStyles, StyleRulesCallback, WithStyles, Button } from '@material-ui/core';
<Grid item xs={12} >
<DetailsAttribute
label='Owner' linkToUuid={workflow?.ownerUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
</Grid>
<Grid item xs={12}>
<DetailsAttribute label='Created at' value={formatDate(workflow?.createdAt)} />
<Grid item xs={12} >
<DetailsAttribute
label='Last modified by user' linkToUuid={workflow?.modifiedByUserUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
</Grid>
</Grid >;
}));
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";
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 { ResourceOwnerWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
+import { ResourceWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
type CssRules = 'root'
isWritable: boolean;
isOldVersion: boolean;
isLoadingFiles: boolean;
- tooManyFiles: boolean;
}
-type CollectionPanelProps = CollectionPanelDataProps & DispatchProp
- & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
+type CollectionPanelProps = CollectionPanelDataProps & DispatchProp & WithStyles<CssRules>
-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<CollectionResource>(props.match.params.id)(state.resources);
let isWritable = false;
}
}
}
- 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<CollectionPanelProps> {
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"},
+ { name: "Details" },
+ { name: "Files" },
];
return item
? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
{isOldVersion &&
<Typography className={classes.warningLabel} variant="caption">
This is an old version. Make a copy to make changes. Go to the <Link to={getCollectionUrl(item.currentVersionUuid)}>head version</Link> for sharing options.
- </Typography>
+ </Typography>
}
</Grid>
</Grid>
</MPVPanelContent>
<MPVPanelContent xs>
<Card className={classes.filesCard}>
- <CollectionPanelFiles
- isWritable={isWritable}
- isLoading={isLoadingFiles}
- tooManyFiles={tooManyFiles}
- loadFilesFunc={() => {
- dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true));
- dispatch<any>(loadCollectionFiles(this.props.item.uuid));
- }
- } />
+ <CollectionPanelFiles isWritable={isWritable} />
</Card>
</MPVPanelContent>
</MPVContainer>
<Grid item xs={12} md={mdSize}>
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
label='Owner' linkToUuid={item.ownerUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
</Grid>
<div data-cy="responsible-person-wrapper" ref={responsiblePersonRef}>
<Grid item xs={12} md={12}>
<Grid item xs={12} md={12}>
<DetailsAttribute classLabel={classes.label} classValue={classes.value}
label='Properties' />
- { Object.keys(item.properties).length > 0
+ {Object.keys(item.properties).length > 0
? Object.keys(item.properties).map(k =>
- Array.isArray(item.properties[k])
+ Array.isArray(item.properties[k])
? item.properties[k].map((v: string) =>
getPropertyChip(k, v, undefined, classes.tag))
: getPropertyChip(k, item.properties[k], undefined, classes.tag))
- : <div className={classes.value}>No properties</div> }
+ : <div className={classes.value}>No properties</div>}
</Grid>
</Grid>;
};
import { resourceLabel } from "common/labels";
import { DetailsAttribute } from "components/details-attribute/details-attribute";
import { ResourceKind } from "models/resource";
-import { ContainerRunTime, ResourceOwnerWithName } from "views-components/data-explorer/renderers";
+import { ContainerRunTime, ResourceWithName } from "views-components/data-explorer/renderers";
import { getProcess, getProcessStatus } from "store/processes/process";
import { RootState } from "store/store";
import { connect } from "react-redux";
<Grid item xs={12} md={mdSize}>
<DetailsAttribute
label='Owner' linkToUuid={containerRequest.ownerUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ uuidEnhancer={(uuid: string) => <ResourceWithName uuid={uuid} />} />
</Grid>
<Grid item xs={12} md={mdSize}>
<DetailsAttribute label='Container UUID' value={containerRequest.containerUuid} />
name: resource.name,
uuid: resource.uuid,
ownerUuid: resource.ownerUuid,
- isTrashed: ('isTrashed' in resource) ? resource.isTrashed: false,
+ isTrashed: ('isTrashed' in resource) ? resource.isTrashed : false,
kind: resource.kind,
menuKind,
description: resource.description,