From: Daniel Kutyła Date: Wed, 8 Dec 2021 22:01:38 +0000 (+0100) Subject: Merge remote-tracking branch 'origin/main' into 17579-Clear-table-filter-when-changin... X-Git-Tag: 2.4.0~23^2~4 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/43551086cc04bb37a2b1dc6c8ec24af44f2acf8d?hp=be9b5b23f584d08adcee0d3ca4a31558c1aa938d Merge remote-tracking branch 'origin/main' into 17579-Clear-table-filter-when-changing-the-project Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- diff --git a/src/components/collection-panel-files/collection-panel-files.tsx b/src/components/collection-panel-files/collection-panel-files.tsx index 97ec3bf9..1ef6b5c9 100644 --- a/src/components/collection-panel-files/collection-panel-files.tsx +++ b/src/components/collection-panel-files/collection-panel-files.tsx @@ -48,7 +48,6 @@ const styles: StyleRulesCallback = (theme: Theme) => ({ wrapper: { display: 'flex', minHeight: '600px', - marginBottom: '1rem', color: 'rgba(0, 0, 0, 0.87)', fontSize: '0.875rem', fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', @@ -490,7 +489,7 @@ export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState data-parent-path={name} className={classNames(classes.row, getActiveClass(name))} key={id}> - {getItemIcon(type, getActiveClass(name))} + {getItemIcon(type, getActiveClass(name))}
{name}
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index 78aae350..05125f12 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -11,17 +11,19 @@ import { SearchInput } from 'components/search-input/search-input'; import { ArvadosTheme } from "common/custom-theme"; import { createTree } from 'models/tree'; import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree'; -import { MoreOptionsIcon } from 'components/icon/icon'; +import { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon'; import { PaperProps } from '@material-ui/core/Paper'; +import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view'; -type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title'; +type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title' | 'dataTable' | 'container'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ searchBox: { paddingBottom: theme.spacing.unit * 2 }, toolbar: { - paddingTop: theme.spacing.unit * 2 + paddingTop: theme.spacing.unit, + paddingRight: theme.spacing.unit * 2, }, toolbarUnderTitle: { paddingTop: 0 @@ -30,7 +32,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ overflow: 'auto' }, root: { - height: '100%' + height: '100%', }, moreOptionsButton: { padding: 0 @@ -39,7 +41,14 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ paddingLeft: theme.spacing.unit * 3, paddingTop: theme.spacing.unit * 3, fontSize: '18px' - } + }, + dataTable: { + height: '100%', + overflow: 'auto', + }, + container: { + height: '100%', + }, }); interface DataExplorerDataProps { @@ -79,7 +88,8 @@ interface DataExplorerActionProps { extractKey?: (item: T) => React.Key; } -type DataExplorerProps = DataExplorerDataProps & DataExplorerActionProps & WithStyles; +type DataExplorerProps = DataExplorerDataProps & + DataExplorerActionProps & WithStyles & MPVPanelProps; export const DataExplorer = withStyles(styles)( class DataExplorerGeneric extends React.Component> { @@ -96,12 +106,14 @@ export const DataExplorer = withStyles(styles)( rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch, items, itemsAvailable, onRowClick, onRowDoubleClick, classes, dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput, - paperKey, fetchMode, currentItemUuid, title + paperKey, fetchMode, currentItemUuid, title, + doHidePanel, doMaximizePanel, panelName, panelMaximized } = this.props; return - {title &&
{title}
} - {(!hideColumnSelector || !hideSearchInput) && + + {title && {title}} + {(!hideColumnSelector || !hideSearchInput) &&
{!hideSearchInput && } - } - + + } + { doHidePanel && + + + } + } + onRowClick(item)} @@ -128,8 +148,8 @@ export const DataExplorer = withStyles(styles)( working={working} defaultView={dataTableDefaultView} currentItemUuid={currentItemUuid} - currentRoute={paperKey} /> - + currentRoute={paperKey} /> + {fetchMode === DataTableFetchMode.PAGINATED ? Load more} - + + ; } diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx index 0c84f642..de52d365 100644 --- a/src/components/data-table/data-table.tsx +++ b/src/components/data-table/data-table.tsx @@ -39,13 +39,11 @@ type CssRules = "tableBody" | "root" | "content" | "noItemsInfo" | 'tableCell' | const styles: StyleRulesCallback = (theme: Theme) => ({ root: { - overflowX: 'auto', - overflowY: 'auto', - height: 'calc(100vh - 280px)', + width: '100%', }, content: { display: 'inline-block', - width: '100%' + width: '100%', }, tableBody: { background: theme.palette.background.paper diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index 26ce4fea..523eefbd 100644 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -59,12 +59,15 @@ import SettingsEthernet from '@material-ui/icons/SettingsEthernet'; import Star from '@material-ui/icons/Star'; import StarBorder from '@material-ui/icons/StarBorder'; import Warning from '@material-ui/icons/Warning'; +import Visibility from '@material-ui/icons/Visibility'; +import VisibilityOff from '@material-ui/icons/VisibilityOff'; import VpnKey from '@material-ui/icons/VpnKey'; import LinkOutlined from '@material-ui/icons/LinkOutlined'; // Import FontAwesome icons import { library } from '@fortawesome/fontawesome-svg-core'; import { faPencilAlt, faSlash } from '@fortawesome/free-solid-svg-icons'; +import { CropFreeSharp } from '@material-ui/icons'; library.add( faPencilAlt, faSlash, @@ -112,10 +115,12 @@ export const FileIcon: IconType = (props) => ; export const HelpIcon: IconType = (props) => ; export const HelpOutlineIcon: IconType = (props) => ; export const ImportContactsIcon: IconType = (props) => ; +export const InfoIcon: IconType = (props) => ; export const InputIcon: IconType = (props) => ; export const KeyIcon: IconType = (props) => ; export const LogIcon: IconType = (props) => ; export const MailIcon: IconType = (props) => ; +export const MaximizeIcon: IconType = (props) => ; export const MoreOptionsIcon: IconType = (props) => ; export const MoveToIcon: IconType = (props) => ; export const NewProjectIcon: IconType = (props) => ; @@ -144,6 +149,8 @@ export const SidePanelRightArrowIcon: IconType = (props) => ; export const UserPanelIcon: IconType = (props) => ; export const UsedByIcon: IconType = (props) => ; +export const VisibleIcon: IconType = (props) => ; +export const InvisibleIcon: IconType = (props) => ; export const WorkflowIcon: IconType = (props) => ; export const WarningIcon: IconType = (props) => ; export const Link: IconType = (props) => ; diff --git a/src/components/multi-panel-view/multi-panel-view.test.tsx b/src/components/multi-panel-view/multi-panel-view.test.tsx new file mode 100644 index 00000000..6cf13d78 --- /dev/null +++ b/src/components/multi-panel-view/multi-panel-view.test.tsx @@ -0,0 +1,87 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from "react"; +import { configure, mount } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import { MPVContainer } from './multi-panel-view'; +import { Button } from "@material-ui/core"; + +configure({ adapter: new Adapter() }); + +const PanelMock = ({panelName, panelMaximized, doHidePanel, doMaximizePanel, children, ...rest}) => +
{children}
; + +describe('', () => { + let props; + + beforeEach(() => { + props = { + classes: {}, + }; + }); + + it('should show default panel buttons for every child', () => { + const childs = [ + This is one panel, + This is another panel, + ]; + const wrapper = mount({[...childs]}); + expect(wrapper.find(Button).first().html()).toContain('Panel 1'); + expect(wrapper.html()).toContain('This is one panel'); + expect(wrapper.find(Button).last().html()).toContain('Panel 2'); + expect(wrapper.html()).toContain('This is another panel'); + }); + + it('should show panel when clicking on its button', () => { + const childs = [ + This is one panel, + ]; + props.panelStates = [ + {name: 'Initially invisible Panel', visible: false}, + ] + + const wrapper = mount({[...childs]}); + + // Initial state: panel not visible + expect(wrapper.html()).not.toContain('This is one panel'); + expect(wrapper.html()).toContain('All panels are hidden'); + + // Panel visible when clicking on its button + wrapper.find(Button).simulate('click'); + expect(wrapper.html()).toContain('This is one panel'); + expect(wrapper.html()).not.toContain('All panels are hidden'); + }); + + it('should show custom panel buttons when config provided', () => { + const childs = [ + This is one panel, + This is another panel, + ]; + props.panelStates = [ + {name: 'First Panel'}, + ] + const wrapper = mount({[...childs]}); + expect(wrapper.find(Button).first().html()).toContain('First Panel'); + expect(wrapper.html()).toContain('This is one panel'); + // Second panel received the default button naming and hidden status by default + expect(wrapper.find(Button).last().html()).toContain('Panel 2'); + expect(wrapper.html()).not.toContain('This is another panel'); + wrapper.find(Button).last().simulate('click'); + expect(wrapper.html()).toContain('This is another panel'); + }); + + it('should set panel hidden when requested', () => { + const childs = [ + This is one panel, + ]; + props.panelStates = [ + {name: 'First Panel', visible: false}, + ] + const wrapper = mount({[...childs]}); + expect(wrapper.find(Button).html()).toContain('First Panel'); + expect(wrapper.html()).not.toContain('This is one panel'); + expect(wrapper.html()).toContain('All panels are hidden'); + }); +}); \ No newline at end of file diff --git a/src/components/multi-panel-view/multi-panel-view.tsx b/src/components/multi-panel-view/multi-panel-view.tsx new file mode 100644 index 00000000..dbb37921 --- /dev/null +++ b/src/components/multi-panel-view/multi-panel-view.tsx @@ -0,0 +1,198 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React, { MutableRefObject, ReactElement, ReactNode, useEffect, useRef, useState } from 'react'; +import { + Button, + Grid, + Paper, + StyleRulesCallback, + Tooltip, + withStyles, + WithStyles +} from "@material-ui/core"; +import { GridProps } from '@material-ui/core/Grid'; +import { isArray } from 'lodash'; +import { DefaultView } from 'components/default-view/default-view'; +import { InfoIcon, InvisibleIcon, VisibleIcon } from 'components/icon/icon'; +import { ReactNodeArray } from 'prop-types'; +import classNames from 'classnames'; + +type CssRules = 'button' | 'buttonIcon' | 'content'; + +const styles: StyleRulesCallback = theme => ({ + button: { + padding: '2px 5px', + marginRight: '5px', + }, + buttonIcon: { + boxShadow: 'none', + padding: '2px 0px 2px 5px', + fontSize: '1rem' + }, + content: { + overflow: 'auto', + }, +}); + +interface MPVHideablePanelDataProps { + name: string; + visible: boolean; + maximized: boolean; + illuminated: boolean; + children: ReactNode; + panelRef?: MutableRefObject; +} + +interface MPVHideablePanelActionProps { + doHidePanel: () => void; + doMaximizePanel: () => void; +} + +type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps; + +const MPVHideablePanel = ({doHidePanel, doMaximizePanel, name, visible, maximized, illuminated, ...props}: MPVHideablePanelProps) => + visible + ? <> + {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel, panelName: name, panelMaximized: maximized, panelIlluminated: illuminated, panelRef: props.panelRef })} + + : null; + +interface MPVPanelDataProps { + panelName?: string; + panelMaximized?: boolean; + panelIlluminated?: boolean; + panelRef?: MutableRefObject; +} + +interface MPVPanelActionProps { + doHidePanel?: () => void; + doMaximizePanel?: () => void; +} + +// Props received by panel implementors +export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps; + +type MPVPanelContentProps = {children: ReactElement} & MPVPanelProps & GridProps; + +// Grid item compatible component for layout and MPV props passing +export const MPVPanelContent = ({doHidePanel, doMaximizePanel, panelName, panelMaximized, panelIlluminated, panelRef, ...props}: MPVPanelContentProps) => { + useEffect(() => { + if (panelRef && panelRef.current) { + panelRef.current.scrollIntoView({behavior: 'smooth'}); + } + }, [panelRef]); + + return + {/* Element to scroll to when the panel is selected */} + + {React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })} + + ; +} + +export interface MPVPanelState { + name: string; + visible?: boolean; +} +interface MPVContainerDataProps { + panelStates?: MPVPanelState[]; +} +type MPVContainerProps = MPVContainerDataProps & GridProps; + +// Grid container compatible component that also handles panel toggling. +const MPVContainerComponent = ({children, panelStates, classes, ...props}: MPVContainerProps & WithStyles) => { + if (children === undefined || children === null || children === {}) { + children = []; + } else if (!isArray(children)) { + children = [children]; + } + const visibility = (children as ReactNodeArray).map((_, idx) => + !!!panelStates || // if panelStates wasn't passed, default to all visible panels + (panelStates[idx] && + (panelStates[idx].visible || panelStates[idx].visible === undefined))); + const [panelVisibility, setPanelVisibility] = useState(visibility); + const [brightenedPanel, setBrightenedPanel] = useState(-1); + const panelRef = useRef(null); + + let panels: JSX.Element[] = []; + let toggles: JSX.Element[] = []; + + if (isArray(children)) { + for (let idx = 0; idx < children.length; idx++) { + const showFn = (idx: number) => () => { + setPanelVisibility([ + ...panelVisibility.slice(0, idx), + true, + ...panelVisibility.slice(idx+1) + ]); + }; + const hideFn = (idx: number) => () => { + setPanelVisibility([ + ...panelVisibility.slice(0, idx), + false, + ...panelVisibility.slice(idx+1) + ]) + }; + const maximizeFn = (idx: number) => () => { + // Maximize X == hide all but X + setPanelVisibility([ + ...panelVisibility.slice(0, idx).map(() => false), + true, + ...panelVisibility.slice(idx+1).map(() => false), + ]) + }; + const toggleIcon = panelVisibility[idx] + ? + : + const panelName = panelStates === undefined + ? `Panel ${idx+1}` + : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx+1}`; + const toggleVariant = "outlined"; + const toggleTooltip = panelVisibility[idx] + ? '' + :`Show ${panelName} panel`; + const panelIsMaximized = panelVisibility[idx] && + panelVisibility.filter(e => e).length === 1; + + toggles = [ + ...toggles, + + + + ]; + + const aPanel = + + {children[idx]} + ; + panels = [...panels, aPanel]; + }; + }; + + return + + { toggles.map((tgl, idx) => {tgl}) } + + + { panelVisibility.includes(true) + ? panels + : + + } + + ; +}; + +export const MPVContainer = withStyles(styles)(MPVContainerComponent); \ No newline at end of file diff --git a/src/components/panel-default-view/panel-default-view.tsx b/src/components/panel-default-view/panel-default-view.tsx deleted file mode 100644 index c364bb75..00000000 --- a/src/components/panel-default-view/panel-default-view.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import React from 'react'; -import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles'; -import { DefaultViewDataProps, DefaultView } from 'components/default-view/default-view'; - -type CssRules = 'classRoot' | 'classIcon' | 'classMessage'; - -const styles: StyleRulesCallback = () => ({ - classRoot: { - position: 'absolute', - width: '80%', - left: '50%', - top: '50%', - transform: 'translate(-50%, -50%)' - }, - classMessage: { - fontSize: '1.75rem', - }, - classIcon: { - fontSize: '6rem' - } -}); - -type PanelDefaultViewProps = Pick & WithStyles; - -export const PanelDefaultView = withStyles(styles)( - ({ classes, ...props }: PanelDefaultViewProps) => - ); diff --git a/src/store/process-logs-panel/process-logs-panel.ts b/src/store/process-logs-panel/process-logs-panel.ts index deaaab6a..87b50bd2 100644 --- a/src/store/process-logs-panel/process-logs-panel.ts +++ b/src/store/process-logs-panel/process-logs-panel.ts @@ -1,9 +1,10 @@ -import { RootState } from '../store'; -import { matchProcessLogRoute } from 'routes/routes'; // Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 +import { RootState } from '../store'; +import { matchProcessLogRoute, matchProcessRoute } from 'routes/routes'; + export interface ProcessLogsPanel { filters: string[]; selectedFilter: string; @@ -20,6 +21,6 @@ export const getProcessPanelLogs = ({ selectedFilter, logs }: ProcessLogsPanel) export const getProcessLogsPanelCurrentUuid = ({ router }: RootState) => { const pathname = router.location ? router.location.pathname : ''; - const match = matchProcessLogRoute(pathname); + const match = matchProcessLogRoute(pathname) || matchProcessRoute(pathname); return match ? match.params.id : undefined; }; diff --git a/src/views-components/details-panel/process-details.tsx b/src/views-components/details-panel/process-details.tsx index c4b374b9..d9c991f5 100644 --- a/src/views-components/details-panel/process-details.tsx +++ b/src/views-components/details-panel/process-details.tsx @@ -5,12 +5,8 @@ import React from 'react'; import { ProcessIcon } from 'components/icon/icon'; import { ProcessResource } from 'models/process'; -import { formatDate } from 'common/formatters'; -import { ResourceKind } from 'models/resource'; -import { resourceLabel } from 'common/labels'; import { DetailsData } from "./details-data"; -import { DetailsAttribute } from "components/details-attribute/details-attribute"; -import { ResourceOwnerWithName } from '../data-explorer/renderers'; +import { ProcessDetailsAttributes } from 'views/process-panel/process-details-attributes'; export class ProcessDetails extends DetailsData { @@ -19,25 +15,6 @@ export class ProcessDetails extends DetailsData { } getDetails() { - return
- - } /> - - - - - - - - - - - - - - - -
; + return ; } } diff --git a/src/views/all-processes-panel/all-processes-panel.tsx b/src/views/all-processes-panel/all-processes-panel.tsx index f9fab44d..928b4fff 100644 --- a/src/views/all-processes-panel/all-processes-panel.tsx +++ b/src/views/all-processes-panel/all-processes-panel.tsx @@ -33,7 +33,7 @@ import { getInitialProcessStatusFilters, getInitialProcessTypeFilters } from 'st import { getProcess } from 'store/processes/process'; import { ResourcesState } from 'store/resources/resources'; -type CssRules = "toolbar" | "button"; +type CssRules = "toolbar" | "button" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { @@ -43,6 +43,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ button: { marginLeft: theme.spacing.unit }, + root: { + width: '100%', + } }); export enum AllProcessesPanelColumnNames { @@ -142,18 +145,17 @@ export const AllProcessesPanel = withStyles(styles)( } render() { - return - } />; + dataTableDefaultView={ } /> +
} } ) diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx index 703bbec5..8f87cb26 100644 --- a/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx +++ b/src/views/api-client-authorization-panel/api-client-authorization-panel-root.tsx @@ -4,10 +4,10 @@ import React from 'react'; import { - StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Grid, Tooltip, IconButton + StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; import { ArvadosTheme } from 'common/custom-theme'; -import { HelpIcon, ShareMeIcon } from 'components/icon/icon'; +import { ShareMeIcon } from 'components/icon/icon'; import { createTree } from 'models/tree'; import { DataColumns } from 'components/data-table/data-table'; import { SortDirection } from 'components/data-table/data-column'; @@ -20,21 +20,11 @@ import { TokenLastUsedAt, TokenLastUsedByIpAddress, TokenScopes, TokenUserId } from 'views-components/data-explorer/renderers'; -type CssRules = 'card' | 'cardContent' | 'helpIconGrid'; +type CssRules = 'root'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ - card: { + root: { width: '100%', - overflow: 'auto' - }, - cardContent: { - padding: 0, - '&:last-child': { - paddingBottom: 0 - } - }, - helpIconGrid: { - textAlign: 'right' } }); @@ -132,7 +122,6 @@ export interface ApiClientAuthorizationPanelRootActionProps { onItemClick: (item: string) => void; onContextMenu: (event: React.MouseEvent, item: string) => void; onItemDoubleClick: (item: string) => void; - openHelpDialog: () => void; } export interface ApiClientAuthorizationPanelRootDataProps { @@ -143,33 +132,18 @@ type ApiClientAuthorizationPanelRootProps = ApiClientAuthorizationPanelRootActio & ApiClientAuthorizationPanelRootDataProps & WithStyles; export const ApiClientAuthorizationPanelRoot = withStyles(styles)( - ({ classes, onItemDoubleClick, onItemClick, onContextMenu, openHelpDialog }: ApiClientAuthorizationPanelRootProps) => - - - - - - - - - - - - - } /> - - - - + ({ classes, onItemDoubleClick, onItemClick, onContextMenu }: ApiClientAuthorizationPanelRootProps) => +
+ } />
); \ No newline at end of file diff --git a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx index 89254dcc..9604bf50 100644 --- a/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx +++ b/src/views/api-client-authorization-panel/api-client-authorization-panel.tsx @@ -11,7 +11,6 @@ import { ApiClientAuthorizationPanelRootActionProps } from 'views/api-client-authorization-panel/api-client-authorization-panel-root'; import { openApiClientAuthorizationContextMenu } from 'store/context-menu/context-menu-actions'; -import { openApiClientAuthorizationsHelpDialog } from 'store/api-client-authorizations/api-client-authorizations-actions'; const mapStateToProps = (state: RootState): ApiClientAuthorizationPanelRootDataProps => { return { @@ -25,9 +24,6 @@ const mapDispatchToProps = (dispatch: Dispatch): ApiClientAuthorizationPanelRoot }, onItemClick: (resourceUuid: string) => { return; }, onItemDoubleClick: uuid => { return; }, - openHelpDialog: () => { - dispatch(openApiClientAuthorizationsHelpDialog()); - } }); export const ApiClientAuthorizationPanel = connect(mapStateToProps, mapDispatchToProps)(ApiClientAuthorizationPanelRoot); \ No newline at end of file diff --git a/src/views/collection-content-address-panel/collection-content-address-panel.tsx b/src/views/collection-content-address-panel/collection-content-address-panel.tsx index 88638085..f1278049 100644 --- a/src/views/collection-content-address-panel/collection-content-address-panel.tsx +++ b/src/views/collection-content-address-panel/collection-content-address-panel.tsx @@ -7,7 +7,6 @@ import { StyleRulesCallback, WithStyles, withStyles, - Grid, Button } from '@material-ui/core'; import { CollectionIcon } from 'components/icon/icon'; @@ -38,7 +37,7 @@ import { getResource, ResourcesState } from 'store/resources/resources'; import { RootState } from 'store/store'; import { CollectionResource } from 'models/collection'; -type CssRules = 'backLink' | 'backIcon' | 'card' | 'title' | 'iconHeader' | 'link'; +type CssRules = 'backLink' | 'backIcon' | 'root' | 'content'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ backLink: { @@ -53,24 +52,13 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ backIcon: { marginRight: theme.spacing.unit }, - card: { - width: '100%' + root: { + width: '100%', }, - title: { - color: theme.palette.grey["700"] + content: { + // reserve space for the content address bar + height: `calc(100% - ${theme.spacing.unit * 7}px)`, }, - iconHeader: { - fontSize: '1.875rem', - color: theme.customs.colors.green700 - }, - link: { - fontSize: '0.875rem', - color: theme.palette.primary.main, - textAlign: 'right', - '&:hover': { - cursor: 'pointer' - } - } }); enum CollectionContentAddressPanelColumnNames { @@ -162,14 +150,14 @@ export const CollectionsContentAddressPanel = withStyles(styles)( connect(mapStateToProps, mapDispatchToProps)( class extends React.Component> { render() { - return + return
- - } />; - ; + } />
+ ; } } ) diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index e78b1f3d..794e093f 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -4,15 +4,20 @@ import React from 'react'; import { - StyleRulesCallback, WithStyles, withStyles, - IconButton, Grid, Tooltip, Typography, ExpansionPanel, - ExpansionPanelSummary, ExpansionPanelDetails + StyleRulesCallback, + WithStyles, + withStyles, + IconButton, + Grid, + Tooltip, + Typography, + Card, CardHeader, CardContent, } from '@material-ui/core'; import { connect, DispatchProp } from "react-redux"; import { RouteComponentProps } from 'react-router'; import { ArvadosTheme } from 'common/custom-theme'; import { RootState } from 'store/store'; -import { MoreOptionsIcon, CollectionIcon, ReadOnlyIcon, ExpandIcon, CollectionOldVersionIcon } from 'components/icon/icon'; +import { MoreOptionsIcon, CollectionIcon, ReadOnlyIcon, CollectionOldVersionIcon } from 'components/icon/icon'; 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'; @@ -33,9 +38,12 @@ import { COLLECTION_PANEL_LOAD_FILES, loadCollectionFiles, COLLECTION_PANEL_LOAD import { Link } from 'react-router-dom'; import { Link as ButtonLink } from '@material-ui/core'; import { ResourceOwnerWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers'; +import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view'; type CssRules = 'root' | 'button' + | 'infoCard' + | 'propertiesCard' | 'filesCard' | 'iconHeader' | 'tag' @@ -49,16 +57,21 @@ type CssRules = 'root' const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { - display: 'flex', - flexFlow: 'column', - height: 'calc(100vh - 130px)', // (100% viewport height) - (top bar + breadcrumbs) + width: '100%', }, button: { cursor: 'pointer' }, + infoCard: { + paddingLeft: theme.spacing.unit * 2, + paddingRight: theme.spacing.unit * 2, + paddingBottom: theme.spacing.unit * 2, + }, + propertiesCard: { + padding: 0, + }, filesCard: { - marginBottom: theme.spacing.unit * 2, - flex: 1, + padding: 0, }, iconHeader: { fontSize: '1.875rem', @@ -133,10 +146,15 @@ export const CollectionPanel = withStyles(styles)( class extends React.Component { render() { const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props; + const panelsData: MPVPanelState[] = [ + {name: "Details"}, + {name: "Properties"}, + {name: "Files"}, + ]; return item - ?
- - }> + ? + + @@ -165,8 +183,6 @@ export const CollectionPanel = withStyles(styles)( - - @@ -185,15 +201,12 @@ export const CollectionPanel = withStyles(styles)( } - - - - - }> - {"Properties"} - - - + + + + + + {isWritable && } @@ -218,21 +231,23 @@ export const CollectionPanel = withStyles(styles)( :
No properties set on this collection.
}
-
-
-
-
- { - dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true)); - dispatch(loadCollectionFiles(this.props.item.uuid)); - } - } /> -
-
+
+ + + + + { + dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true)); + dispatch(loadCollectionFiles(this.props.item.uuid)); + } + } /> + + + : null; } diff --git a/src/views/favorite-panel/favorite-panel.tsx b/src/views/favorite-panel/favorite-panel.tsx index 404baeb9..0b6532c1 100644 --- a/src/views/favorite-panel/favorite-panel.tsx +++ b/src/views/favorite-panel/favorite-panel.tsx @@ -41,7 +41,7 @@ import { getProperty } from 'store/properties/properties'; import { PROJECT_PANEL_CURRENT_UUID } from 'store/project-panel/project-panel-action'; import { CollectionResource } from 'models/collection'; -type CssRules = "toolbar" | "button"; +type CssRules = "toolbar" | "button" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { @@ -51,6 +51,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ button: { marginLeft: theme.spacing.unit }, + root: { + width: '100%', + }, }); export enum FavoritePanelColumnNames { @@ -176,7 +179,7 @@ export const FavoritePanel = withStyles(styles)( } render() { - return - } />; + } />; } } ) diff --git a/src/views/groups-panel/groups-panel.tsx b/src/views/groups-panel/groups-panel.tsx index 4d15118c..faefab10 100644 --- a/src/views/groups-panel/groups-panel.tsx +++ b/src/views/groups-panel/groups-panel.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Grid, Button, Typography } from "@material-ui/core"; +import { Grid, Button, Typography, StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core"; import { DataExplorer } from "views-components/data-explorer/data-explorer"; import { DataColumns } from 'components/data-table/data-table'; import { SortDirection } from 'components/data-table/data-column'; @@ -22,6 +22,15 @@ import { openContextMenu } from 'store/context-menu/context-menu-actions'; import { ResourceKind } from 'models/resource'; import { LinkClass, LinkResource } from 'models/link'; import { navigateToGroupDetails } from 'store/navigation/navigation-action'; +import { ArvadosTheme } from 'common/custom-theme'; + +type CssRules = "root"; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + root: { + width: '100%', + } +}); export enum GroupsPanelColumnNames { GROUP = "Name", @@ -74,14 +83,14 @@ export interface GroupsPanelProps { resources: ResourcesState; } -export const GroupsPanel = connect( +export const GroupsPanel = withStyles(styles)(connect( mapStateToProps, mapDispatchToProps )( - class GroupsPanel extends React.Component { + class GroupsPanel extends React.Component> { render() { return ( - New group
- } /> + } /> ); } @@ -113,7 +122,7 @@ export const GroupsPanel = connect( }); } } - }); + })); const GroupMembersCount = connect( diff --git a/src/views/link-panel/link-panel-root.tsx b/src/views/link-panel/link-panel-root.tsx index 7a5f0503..b32208cd 100644 --- a/src/views/link-panel/link-panel-root.tsx +++ b/src/views/link-panel/link-panel-root.tsx @@ -11,10 +11,20 @@ import { DataTableDefaultView } from 'components/data-table-default-view/data-ta import { ResourcesState } from 'store/resources/resources'; import { ShareMeIcon } from 'components/icon/icon'; import { createTree } from 'models/tree'; -import { - ResourceLinkUuid, ResourceLinkHead, ResourceLinkTail, - ResourceLinkClass, ResourceLinkName } +import { + ResourceLinkUuid, ResourceLinkHead, ResourceLinkTail, + ResourceLinkClass, ResourceLinkName } from 'views-components/data-explorer/renderers'; +import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; +import { ArvadosTheme } from 'common/custom-theme'; + +type CssRules = "root"; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + root: { + width: '100%', + } +}); export enum LinkPanelColumnNames { NAME = "Name", @@ -73,20 +83,20 @@ export interface LinkPanelRootActionProps { onItemDoubleClick: (item: string) => void; } -export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps; +export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps & WithStyles; -export const LinkPanelRoot = (props: LinkPanelRootProps) => { - return { + return
- }/>; -}; \ No newline at end of file + }/>
; +}); \ No newline at end of file diff --git a/src/views/process-panel/process-details-attributes.tsx b/src/views/process-panel/process-details-attributes.tsx new file mode 100644 index 00000000..4f26a71f --- /dev/null +++ b/src/views/process-panel/process-details-attributes.tsx @@ -0,0 +1,65 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from "react"; +import { Grid } from "@material-ui/core"; +import { formatDate } from "common/formatters"; +import { resourceLabel } from "common/labels"; +import { DetailsAttribute } from "components/details-attribute/details-attribute"; +import { ProcessResource } from "models/process"; +import { ResourceKind } from "models/resource"; +import { ResourceOwnerWithName } from "views-components/data-explorer/renderers"; + +type CssRules = 'label' | 'value'; + +export const ProcessDetailsAttributes = (props: { item: ProcessResource, twoCol?: boolean, classes?: Record }) => { + const item = props.item; + const classes = props.classes || { label: '', value: '', button: '' }; + const mdSize = props.twoCol ? 6 : 12; + return + + + + + } /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ; +}; diff --git a/src/views/process-panel/process-details-card.tsx b/src/views/process-panel/process-details-card.tsx new file mode 100644 index 00000000..18610781 --- /dev/null +++ b/src/views/process-panel/process-details-card.tsx @@ -0,0 +1,63 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import React from 'react'; +import { + StyleRulesCallback, + WithStyles, + withStyles, + Card, + CardHeader, + IconButton, + CardContent, + Tooltip, +} from '@material-ui/core'; +import { ArvadosTheme } from 'common/custom-theme'; +import { CloseIcon } from 'components/icon/icon'; +import { Process } from 'store/processes/process'; +import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view'; +import { ProcessDetailsAttributes } from './process-details-attributes'; + +type CssRules = 'card' | 'content' | 'title'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + card: { + height: '100%' + }, + content: { + '&:last-child': { + paddingBottom: theme.spacing.unit * 2, + } + }, + title: { + overflow: 'hidden', + paddingTop: theme.spacing.unit * 0.5 + }, +}); + +export interface ProcessDetailsCardDataProps { + process: Process; +} + +type ProcessDetailsCardProps = ProcessDetailsCardDataProps & WithStyles & MPVPanelProps; + +export const ProcessDetailsCard = withStyles(styles)( + ({ classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => { + return + + + } /> + + + + ; + } +); + diff --git a/src/views/process-panel/process-information-card.tsx b/src/views/process-panel/process-information-card.tsx index e70a0478..4c938017 100644 --- a/src/views/process-panel/process-information-card.tsx +++ b/src/views/process-panel/process-information-card.tsx @@ -8,13 +8,14 @@ import { CardHeader, IconButton, CardContent, Grid, Chip, Typography, Tooltip } from '@material-ui/core'; import { ArvadosTheme } from 'common/custom-theme'; -import { MoreOptionsIcon, ProcessIcon } from 'components/icon/icon'; +import { CloseIcon, MoreOptionsIcon, ProcessIcon } from 'components/icon/icon'; import { DetailsAttribute } from 'components/details-attribute/details-attribute'; import { Process } from 'store/processes/process'; import { getProcessStatus, getProcessStatusColor } from 'store/processes/process'; import { formatDate } from 'common/formatters'; import classNames from 'classnames'; import { ContainerState } from 'models/container'; +import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view'; type CssRules = 'card' | 'iconHeader' | 'label' | 'value' | 'chip' | 'link' | 'content' | 'title' | 'avatar' | 'cancelButton'; @@ -83,10 +84,10 @@ export interface ProcessInformationCardDataProps { cancelProcess: (uuid: string) => void; } -type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles; +type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles & MPVPanelProps; export const ProcessInformationCard = withStyles(styles, { withTheme: true })( - ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess }: ProcessInformationCardProps) => { + ({ classes, process, onContextMenu, theme, openProcessInputDialog, navigateToOutput, openWorkflow, cancelProcess, doHidePanel, panelName }: ProcessInformationCardProps) => { const { container } = process; const startedAt = container ? formatDate(container.startedAt) : 'N/A'; const finishedAt = container ? formatDate(container.finishedAt) : 'N/A'; @@ -111,6 +112,10 @@ export const ProcessInformationCard = withStyles(styles, { withTheme: true })( + { doHidePanel && + + + } } title={ diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx index e7f66573..deb5f1b0 100644 --- a/src/views/process-panel/process-panel-root.tsx +++ b/src/views/process-panel/process-panel-root.tsx @@ -3,13 +3,24 @@ // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core'; import { ProcessInformationCard } from './process-information-card'; import { DefaultView } from 'components/default-view/default-view'; import { ProcessIcon } from 'components/icon/icon'; import { Process } from 'store/processes/process'; import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel'; import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter'; +import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view'; +import { ArvadosTheme } from 'common/custom-theme'; +import { ProcessDetailsCard } from './process-details-card'; + +type CssRules = 'root'; + +const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ + root: { + width: '100%', + }, +}); export interface ProcessPanelRootDataProps { process?: Process; @@ -26,12 +37,18 @@ export interface ProcessPanelRootActionProps { cancelProcess: (uuid: string) => void; } -export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps; +export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles; + +const panelsData: MPVPanelState[] = [ + {name: "Info"}, + {name: "Details", visible: false}, + {name: "Subprocesses"}, +]; -export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) => +export const ProcessPanelRoot = withStyles(styles)(({ process, ...props }: ProcessPanelRootProps) => process - ? - + ? + props.onContextMenu(event, process)} @@ -40,11 +57,14 @@ export const ProcessPanelRoot = ({ process, ...props }: ProcessPanelRootProps) = openWorkflow={props.navigateToWorkflow} cancelProcess={props.cancelProcess} /> - - + + + + + - - + + : - ; +
); diff --git a/src/views/project-panel/project-panel.tsx b/src/views/project-panel/project-panel.tsx index 67264511..97f79517 100644 --- a/src/views/project-panel/project-panel.tsx +++ b/src/views/project-panel/project-panel.tsx @@ -51,9 +51,7 @@ type CssRules = 'root' | "button"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { - position: 'relative', width: '100%', - height: '100%' }, button: { marginLeft: theme.spacing.unit diff --git a/src/views/public-favorites-panel/public-favorites-panel.tsx b/src/views/public-favorites-panel/public-favorites-panel.tsx index ee09654a..b58aa2f0 100644 --- a/src/views/public-favorites-panel/public-favorites-panel.tsx +++ b/src/views/public-favorites-panel/public-favorites-panel.tsx @@ -39,7 +39,7 @@ import { getResource, ResourcesState } from 'store/resources/resources'; import { GroupContentsResource } from 'services/groups-service/groups-service'; import { CollectionResource } from 'models/collection'; -type CssRules = "toolbar" | "button"; +type CssRules = "toolbar" | "button" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { @@ -49,6 +49,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ button: { marginLeft: theme.spacing.unit }, + root: { + width: '100%', + }, }); export enum PublicFavoritePanelColumnNames { @@ -160,7 +163,7 @@ export const PublicFavoritePanel = withStyles(styles)( connect(mapStateToProps, mapDispatchToProps)( class extends React.Component { render() { - return - } />; + } />; } } ) diff --git a/src/views/shared-with-me-panel/shared-with-me-panel.tsx b/src/views/shared-with-me-panel/shared-with-me-panel.tsx index eb3127a7..219410c5 100644 --- a/src/views/shared-with-me-panel/shared-with-me-panel.tsx +++ b/src/views/shared-with-me-panel/shared-with-me-panel.tsx @@ -20,7 +20,7 @@ import { } from 'store/context-menu/context-menu-actions'; import { GroupContentsResource } from 'services/groups-service/groups-service'; -type CssRules = "toolbar" | "button"; +type CssRules = "toolbar" | "button" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { @@ -30,6 +30,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ button: { marginLeft: theme.spacing.unit }, + root: { + width: '100%', + }, }); interface SharedWithMePanelDataProps { @@ -46,13 +49,13 @@ export const SharedWithMePanel = withStyles(styles)( }))( class extends React.Component { render() { - return } />; + dataTableDefaultView={} />; } handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx index b8e1b081..41a8f66b 100644 --- a/src/views/subprocess-panel/subprocess-panel-root.tsx +++ b/src/views/subprocess-panel/subprocess-panel-root.tsx @@ -17,6 +17,7 @@ import { DataTableDefaultView } from 'components/data-table-default-view/data-ta import { createTree } from 'models/tree'; import { getInitialProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters'; import { ResourcesState } from 'store/resources/resources'; +import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view'; export enum SubprocessPanelColumnNames { NAME = "Name", @@ -80,7 +81,7 @@ const DEFAULT_VIEW_MESSAGES = [ 'The current process may not have any or none matches current filtering.' ]; -export const SubprocessPanelRoot = (props: SubprocessPanelProps) => { +export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps) => { return { - } />; + } + doHidePanel={props.doHidePanel} + doMaximizePanel={props.doMaximizePanel} + panelMaximized={props.panelMaximized} + panelName={props.panelName} />; }; \ No newline at end of file diff --git a/src/views/trash-panel/trash-panel.tsx b/src/views/trash-panel/trash-panel.tsx index b67b666c..d303c2f7 100644 --- a/src/views/trash-panel/trash-panel.tsx +++ b/src/views/trash-panel/trash-panel.tsx @@ -36,7 +36,7 @@ import { getTrashPanelTypeFilters } from 'store/resource-type-filters/resource-type-filters'; -type CssRules = "toolbar" | "button"; +type CssRules = "toolbar" | "button" | "root"; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ toolbar: { @@ -46,6 +46,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ button: { marginLeft: theme.spacing.unit }, + root: { + width: '100%', + }, }); export enum TrashPanelColumnNames { @@ -146,7 +149,7 @@ export const TrashPanel = withStyles(styles)( }))( class extends React.Component { render() { - return - } />; + } />; } handleContextMenu = (event: React.MouseEvent, resourceUuid: string) => { diff --git a/src/views/user-panel/user-panel.tsx b/src/views/user-panel/user-panel.tsx index c86ca519..5fb979a2 100644 --- a/src/views/user-panel/user-panel.tsx +++ b/src/views/user-panel/user-panel.tsx @@ -30,7 +30,7 @@ import { ShareMeIcon, AddIcon } from 'components/icon/icon'; import { USERS_PANEL_ID, openUserCreateDialog } from 'store/users/users-actions'; import { noop } from 'lodash'; -type UserPanelRules = "button"; +type UserPanelRules = "button" | 'root' | 'content'; const styles = withStyles(theme => ({ button: { @@ -39,6 +39,13 @@ const styles = withStyles(theme => ({ textAlign: 'right', alignSelf: 'center' }, + root: { + width: '100%', + }, + content: { + // reserve space for the tab bar + height: `calc(100% - ${theme.spacing.unit * 7}px)`, + } })); export enum UserPanelColumnNames { @@ -149,13 +156,13 @@ export const UserPanel = compose( render() { const { value } = this.state; - return + return {value === 0 && - +
} /> - } +
}
; } diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 9ce93bf2..1c6bf03f 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -129,6 +129,9 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ minWidth: 0, paddingLeft: theme.spacing.unit * 3, paddingRight: theme.spacing.unit * 3, + // Reserve vertical space for app bar + MainContentBar + minHeight: `calc(100vh - ${theme.spacing.unit * 16}px)`, + display: 'flex', } });