X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/ed5eba6574a98f8acfe4f1fc0c063b9347b656c1..2a7fd99c212c33a1ec9911f8529fa5afc59a7bb2:/src/components/collection-panel-files/collection-panel-files.tsx diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx index 74bfca65b6..fb36ebce54 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -8,24 +8,39 @@ import { connect } from 'react-redux'; import { FixedSizeList } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; import servicesProvider from 'common/service-provider'; -import { CustomizeTableIcon, DownloadIcon } from 'components/icon/icon'; +import { CustomizeTableIcon, DownloadIcon, MoreOptionsIcon } from 'components/icon/icon'; import { SearchInput } from 'components/search-input/search-input'; -import { ListItemIcon, StyleRulesCallback, Theme, WithStyles, withStyles, Tooltip, IconButton, Checkbox, CircularProgress, Button, Typography } 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'; -import _ from 'lodash'; export interface CollectionPanelFilesProps { - items: any; isWritable: boolean; onUploadDataClick: (targetLocation?: string) => void; onSearchChange: (searchValue: string) => void; @@ -40,13 +55,37 @@ export interface CollectionPanelFilesProps { 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" + | "moreOptionsButton" + | "moreOptions" + | "loader" + | "wrapper" + | "dataWrapper" + | "row" + | "rowEmpty" + | "leftPanel" + | "rightPanel" + | "pathPanel" + | "pathPanelItem" + | "rowName" + | "listItemIcon" + | "rowActive" + | "pathPanelMenu" + | "rowSelection" + | "leftPanelHidden" + | "leftPanelVisible" + | "searchWrapper" + | "searchWrapperHidden"; const styles: StyleRulesCallback = (theme: Theme) => ({ wrapper: { display: 'flex', minHeight: '600px', - color: 'rgba(0, 0, 0, 0.87)', + color: 'rgba(0,0,0,0.87)', fontSize: '0.875rem', fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', fontWeight: 400, @@ -115,8 +154,8 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ marginTop: '-15px', }, pathPanel: { - padding: '1rem', - marginBottom: '1rem', + padding: '0.5rem', + marginBottom: '0.5rem', backgroundColor: '#fff', boxShadow: '0px 1px 3px 0px rgb(0 0 0 / 20%), 0px 1px 1px 0px rgb(0 0 0 / 14%), 0px 2px 1px -1px rgb(0 0 0 / 12%)', }, @@ -125,7 +164,7 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ }, leftPanel: { flex: 0, - padding: '1rem', + padding: '0 1rem 1rem', marginRight: '1rem', whiteSpace: 'nowrap', position: 'relative', @@ -156,8 +195,8 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ rightPanel: { flex: '50%', padding: '1rem', - paddingTop: '2rem', - marginTop: '-1rem', + paddingTop: '0.5rem', + marginTop: '-0.5rem', position: 'relative', backgroundColor: '#fff', boxShadow: '0px 3px 3px 0px rgb(0 0 0 / 20%), 0px 3px 1px 0px rgb(0 0 0 / 14%), 0px 3px 1px -1px rgb(0 0 0 / 12%)', @@ -170,38 +209,36 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ }, uploadButton: { float: 'right', - } + }, + moreOptionsButton: { + width: theme.spacing.unit * 3, + height: theme.spacing.unit * 3, + marginRight: theme.spacing.unit, + marginTop: 'auto', + marginBottom: 'auto', + justifyContent: 'center', + }, + moreOptions: { + position: 'absolute' + }, }); const pathPromise = {}; -let prevState = {}; -function difference(object, base) { - function changes(object, base) { - return _.transform(object, function(result, value, key) { - if (!_.isEqual(value, base[key])) { - result[key] = (_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value; - } - }); - } - return changes(object, base); -} export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState) => ({ auth: state.auth, collectionPanel: state.collectionPanel, collectionPanelFiles: state.collectionPanelFiles, }))((props: CollectionPanelFilesProps & WithStyles & { auth: AuthState }) => { - const diff = difference(props, prevState); - prevState = props; - console.log('---> render CollectionPanel <------', diff); const { classes, onItemMenuOpen, onUploadDataClick, isWritable, dispatch, collectionPanelFiles, collectionPanel } = props; const { apiToken, config } = props.auth; - const webdavClient = new WebDAV(); - webdavClient.defaults.baseURL = config.keepWebServiceUrl; - webdavClient.defaults.headers = { - Authorization: `Bearer ${apiToken}` - }; + const webdavClient = new WebDAV({ + baseURL: config.keepWebServiceUrl, + headers: { + Authorization: `Bearer ${apiToken}` + }, + }); const webDAVRequestConfig: WebDAVRequestConfig = { headers: { @@ -210,8 +247,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(''); @@ -224,14 +261,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState React.useEffect(() => { if (props.currentItemUuid) { - console.log(' --> useEffect current UUID: ', props.currentItemUuid); setPathData({}); setPath([props.currentItemUuid]); } }, [props.currentItemUuid]); const fetchData = (keys, ignoreCache = false) => { - console.log('---> fetchData', keys); const keyArray = Array.isArray(keys) ? keys : [keys]; Promise.all(keyArray.filter(key => !!key) @@ -239,14 +274,13 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const dataExists = !!pathData[key]; const runningRequest = pathPromise[key]; - if ((!dataExists || ignoreCache) && (!runningRequest || ignoreCache)) { + if (ignoreCache || (!dataExists && !runningRequest)) { if (!isLoading) { setIsLoading(true); } pathPromise[key] = true; - console.log('>>> fetching data for key', key); return webdavClient.propfind(`c=${key}`, webDAVRequestConfig); } @@ -257,7 +291,6 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState .then((requests) => { const newState = requests.map((request, index) => { if (request && request.responseXML != null) { - console.log(">>> got data for key", keyArray[index]); const key = keyArray[index]; const result: any = extractFilesData(request.responseXML); const sortedResult = sortBy(result, (n) => n.name).sort((n1, n2) => { @@ -276,8 +309,7 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState }).reduce((prev, next) => { return { ...next, ...prev }; }, {}); - - setPathData({ ...pathData, ...newState }); + setPathData((state) => ({ ...state, ...newState })); }) .finally(() => { setIsLoading(false); @@ -287,7 +319,6 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState React.useEffect(() => { if (rightKey) { - console.log('---> useEffect rightKey:', rightKey); fetchData(rightKey); setLeftSearch(''); setRightSearch(''); @@ -297,19 +328,12 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const currentPDH = (collectionPanel.item || {}).portableDataHash; React.useEffect(() => { if (currentPDH) { - console.log('---> useEffect PDH change:', currentPDH); - // Avoid fetching the same content level twice - if (leftKey !== rightKey) { - fetchData([leftKey, rightKey], true); - } else { - fetchData(rightKey, true); - } + fetchData([leftKey, rightKey], true); } }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps React.useEffect(() => { if (rightData) { - console.log('---> useEffect rightData:', rightData, 'search:', rightSearch); const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1); setCollectionFiles(filtered, false)(dispatch); } @@ -339,14 +363,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) { - console.log('---> useEffect parentRef:', parentRef); + if (parentRef?.current) { node = parentRef.current; (node as any).addEventListener('contextmenu', handleRightClick); } @@ -361,22 +383,28 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const handleClick = React.useCallback( (event: any) => { let isCheckbox = false; + let isMoreButton = false; let elem = event.target; if (elem.type === 'checkbox') { isCheckbox = true; } + // The "More options" button click event could be triggered on its + // internal graphic element. + else if ((elem.dataset && elem.dataset.id === 'moreOptions') || (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === 'moreOptions')) { + isMoreButton = true; + } while (elem && elem.dataset && !elem.dataset.item) { elem = elem.parentNode; } - if (elem && elem.dataset && !isCheckbox) { + if (elem && elem.dataset && !isCheckbox && !isMoreButton) { const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset; if (breadcrumbPath) { const index = path.indexOf(breadcrumbPath); - setPath([...path.slice(0, index + 1)]); + setPath((state) => ([...state.slice(0, index + 1)])); } if (parentPath && type === 'directory') { @@ -384,11 +412,11 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState path.pop() } - setPath([...path, parentPath]); + setPath((state) => ([...state, parentPath])); } if (subfolderPath && type === 'directory') { - setPath([...path, subfolderPath]); + setPath((state) => ([...state, subfolderPath])); } if (elem.dataset.id && type === 'file') { @@ -404,6 +432,14 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState const item = collectionPanelFiles[id]; props.onSelectionToggle(event, item); } + if (isMoreButton) { + const { id } = elem.dataset; + const item: any = { + id, + data: rightData.find((elem) => elem.id === id), + }; + onItemMenuOpen(event, item, isWritable); + } }, [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps ); @@ -447,18 +483,15 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState return
- { path.map((p: string, index: number) => + { path.map( (p: string, index: number) => + {index === 0 ? 'Home' : p} /  - ) } + ) + }
- { onOptionsMenuOpen(ev, isWritable); }}> @@ -466,61 +499,45 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
-
-
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 }) => { - console.log("Left Data ROW: ", filtered[index]); - 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((state) => ([...state.slice(0, state.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
+ }} +
+ :
}
-
+
+
@@ -536,12 +553,9 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState
{ rightData && !isLoading ? {({ height, width }) => { const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1); - console.log("Right Data: ", filtered); - return !!filtered.length ? { ({ index, style }) => { - console.log("Right Data ROW: ", filtered[index]); const { id, type, name, size } = filtered[index]; return
{ formatFileSize(size) } + + + + +
} }
:
This collection is empty