wrapper: {
display: 'flex',
minHeight: '600px',
- marginBottom: '1rem',
color: 'rgba(0, 0, 0, 0.87)',
fontSize: '0.875rem',
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
data-parent-path={name}
className={classNames(classes.row, getActiveClass(name))}
key={id}>
- {getItemIcon(type, getActiveClass(name))}
+ {getItemIcon(type, getActiveClass(name))}
<div className={classes.rowName}>
{name}
</div>
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<CssRules> = (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
overflow: 'auto'
},
root: {
- height: '100%'
+ height: '100%',
},
moreOptionsButton: {
padding: 0
paddingLeft: theme.spacing.unit * 3,
paddingTop: theme.spacing.unit * 3,
fontSize: '18px'
- }
+ },
+ dataTable: {
+ height: '100%',
+ overflow: 'auto',
+ },
+ container: {
+ height: '100%',
+ },
});
interface DataExplorerDataProps<T> {
extractKey?: (item: T) => React.Key;
}
-type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
+type DataExplorerProps<T> = DataExplorerDataProps<T> &
+ DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
export const DataExplorer = withStyles(styles)(
class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
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 <Paper className={classes.root} {...paperProps} key={paperKey}>
- {title && <div className={classes.title}>{title}</div>}
- {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
+ <Grid container direction="column" wrap="nowrap" className={classes.container}>
+ {title && <Grid item xs className={classes.title}>{title}</Grid>}
+ {(!hideColumnSelector || !hideSearchInput) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
<Grid container justify="space-between" wrap="nowrap" alignItems="center">
<div className={classes.searchBox}>
{!hideSearchInput && <SearchInput
columns={columns}
onColumnToggle={onColumnToggle} />}
</Grid>
- </Toolbar>}
- <DataTable
+ { doMaximizePanel && !panelMaximized &&
+ <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
+ <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
+ </Tooltip> }
+ { doHidePanel &&
+ <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+ <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+ </Tooltip> }
+ </Toolbar></Grid>}
+ <Grid item xs="auto" className={classes.dataTable}><DataTable
columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
items={items}
onRowClick={(_, item: T) => onRowClick(item)}
working={working}
defaultView={dataTableDefaultView}
currentItemUuid={currentItemUuid}
- currentRoute={paperKey} />
- <Toolbar className={classes.footer}>
+ currentRoute={paperKey} /></Grid>
+ <Grid item xs><Toolbar className={classes.footer}>
<Grid container justify="flex-end">
{fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
count={itemsAvailable}
onClick={this.loadMore}
>Load more</Button>}
</Grid>
- </Toolbar>
+ </Toolbar></Grid>
+ </Grid>
</Paper>;
}
const styles: StyleRulesCallback<CssRules> = (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
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,
export const HelpIcon: IconType = (props) => <Help {...props} />;
export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
+export const InfoIcon: IconType = (props) => <Info {...props} />;
export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
export const MailIcon: IconType = (props) => <Mail {...props} />;
+export const MaximizeIcon: IconType = (props) => <CropFreeSharp {...props} />;
export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
export const MoveToIcon: IconType = (props) => <Input {...props} />;
export const NewProjectIcon: IconType = (props) => <CreateNewFolder {...props} />;
export const TrashIcon: IconType = (props) => <Delete {...props} />;
export const UserPanelIcon: IconType = (props) => <Person {...props} />;
export const UsedByIcon: IconType = (props) => <Folder {...props} />;
+export const VisibleIcon: IconType = (props) => <Visibility {...props} />;
+export const InvisibleIcon: IconType = (props) => <VisibilityOff {...props} />;
export const WorkflowIcon: IconType = (props) => <Code {...props} />;
export const WarningIcon: IconType = (props) => <Warning style={{ color: '#fbc02d', height: '30px', width: '30px' }} {...props} />;
export const Link: IconType = (props) => <LinkOutlined {...props} />;
--- /dev/null
+// 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}) =>
+ <div {...rest}>{children}</div>;
+
+describe('<MPVContainer />', () => {
+ let props;
+
+ beforeEach(() => {
+ props = {
+ classes: {},
+ };
+ });
+
+ it('should show default panel buttons for every child', () => {
+ const childs = [
+ <PanelMock key={1}>This is one panel</PanelMock>,
+ <PanelMock key={2}>This is another panel</PanelMock>,
+ ];
+ const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+ 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 = [
+ <PanelMock key={1}>This is one panel</PanelMock>,
+ ];
+ props.panelStates = [
+ {name: 'Initially invisible Panel', visible: false},
+ ]
+
+ const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+
+ // 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 = [
+ <PanelMock key={1}>This is one panel</PanelMock>,
+ <PanelMock key={2}>This is another panel</PanelMock>,
+ ];
+ props.panelStates = [
+ {name: 'First Panel'},
+ ]
+ const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+ 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 = [
+ <PanelMock key={1}>This is one panel</PanelMock>,
+ ];
+ props.panelStates = [
+ {name: 'First Panel', visible: false},
+ ]
+ const wrapper = mount(<MPVContainer {...props}>{[...childs]}</MPVContainer>);
+ 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
--- /dev/null
+// 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<CssRules> = 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<any>;
+}
+
+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<any>;
+}
+
+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 <Grid item {...props}>
+ <span ref={panelRef} /> {/* Element to scroll to when the panel is selected */}
+ <Paper style={{height: '100%'}} elevation={panelIlluminated ? 8 : 0}>
+ {React.cloneElement(props.children, { doHidePanel, doMaximizePanel, panelName, panelMaximized })}
+ </Paper>
+ </Grid>;
+}
+
+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<CssRules>) => {
+ 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<boolean[]>(visibility);
+ const [brightenedPanel, setBrightenedPanel] = useState<number>(-1);
+ const panelRef = useRef<any>(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]
+ ? <VisibleIcon className={classNames(classes.buttonIcon)} />
+ : <InvisibleIcon className={classNames(classes.buttonIcon)}/>
+ 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,
+ <Tooltip title={toggleTooltip} disableFocusListener>
+ <Button variant={toggleVariant} size="small" color="primary"
+ className={classNames(classes.button)}
+ onMouseEnter={() => setBrightenedPanel(idx)}
+ onMouseLeave={() => setBrightenedPanel(-1)}
+ onClick={showFn(idx)}>
+ {panelName}
+ {toggleIcon}
+ </Button>
+ </Tooltip>
+ ];
+
+ const aPanel =
+ <MPVHideablePanel key={idx} visible={panelVisibility[idx]} name={panelName}
+ panelRef={(idx === brightenedPanel) ? panelRef : undefined}
+ maximized={panelIsMaximized} illuminated={idx === brightenedPanel}
+ doHidePanel={hideFn(idx)} doMaximizePanel={maximizeFn(idx)}>
+ {children[idx]}
+ </MPVHideablePanel>;
+ panels = [...panels, aPanel];
+ };
+ };
+
+ return <Grid container {...props}>
+ <Grid container item direction="row">
+ { toggles.map((tgl, idx) => <Grid item key={idx}>{tgl}</Grid>) }
+ </Grid>
+ <Grid container item {...props} xs className={classes.content}>
+ { panelVisibility.includes(true)
+ ? panels
+ : <Grid container item alignItems='center' justify='center'>
+ <DefaultView messages={["All panels are hidden.", "Click on the buttons above to show them."]} icon={InfoIcon} />
+ </Grid> }
+ </Grid>
+ </Grid>;
+};
+
+export const MPVContainer = withStyles(styles)(MPVContainerComponent);
\ No newline at end of file
+++ /dev/null
-// 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<CssRules> = () => ({
- classRoot: {
- position: 'absolute',
- width: '80%',
- left: '50%',
- top: '50%',
- transform: 'translate(-50%, -50%)'
- },
- classMessage: {
- fontSize: '1.75rem',
- },
- classIcon: {
- fontSize: '6rem'
- }
-});
-
-type PanelDefaultViewProps = Pick<DefaultViewDataProps, 'icon' | 'messages'> & WithStyles<CssRules>;
-
-export const PanelDefaultView = withStyles(styles)(
- ({ classes, ...props }: PanelDefaultViewProps) =>
- <DefaultView {...classes} {...props} />);
-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;
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;
};
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<ProcessResource> {
}
getDetails() {
- return <div>
- <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
- <DetailsAttribute label='Owner' linkToUuid={this.item.ownerUuid} value={this.item.ownerUuid}
- uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
-
- <DetailsAttribute label='Status' value={this.item.state} />
- <DetailsAttribute label='Last modified' value={formatDate(this.item.modifiedAt)} />
-
- <DetailsAttribute label='Started at' value={formatDate(this.item.createdAt)} />
- <DetailsAttribute label='Finished at' value={formatDate(this.item.expiresAt)} />
-
- <DetailsAttribute label='Outputs' value={this.item.outputPath} />
- <DetailsAttribute label='UUID' linkToUuid={this.item.uuid} value={this.item.uuid} />
- <DetailsAttribute label='Container UUID' value={this.item.containerUuid} />
-
- <DetailsAttribute label='Priority' value={this.item.priority} />
- <DetailsAttribute label='Runtime Constraints' value={JSON.stringify(this.item.runtimeConstraints)} />
-
- <DetailsAttribute label='Docker Image locator' linkToUuid={this.item.containerImage} value={this.item.containerImage} />
- </div>;
+ return <ProcessDetailsAttributes item={this.item} />;
}
}
import { getProcess } from 'store/processes/process';
import { ResourcesState } from 'store/resources/resources';
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
toolbar: {
button: {
marginLeft: theme.spacing.unit
},
+ root: {
+ width: '100%',
+ }
});
export enum AllProcessesPanelColumnNames {
}
render() {
- return <DataExplorer
+ return <div className={this.props.classes.root}><DataExplorer
id={ALL_PROCESSES_PANEL_ID}
onRowClick={this.handleRowClick}
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={true}
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ProcessIcon}
- messages={['Processes list empty.']}
- />
- } />;
+ dataTableDefaultView={ <DataTableDefaultView
+ icon={ProcessIcon}
+ messages={['Processes list empty.']}
+ /> } />
+ </div>
}
}
)
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';
TokenLastUsedAt, TokenLastUsedByIpAddress, TokenScopes, TokenUserId
} from 'views-components/data-explorer/renderers';
-type CssRules = 'card' | 'cardContent' | 'helpIconGrid';
+type CssRules = 'root';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
- card: {
+ root: {
width: '100%',
- overflow: 'auto'
- },
- cardContent: {
- padding: 0,
- '&:last-child': {
- paddingBottom: 0
- }
- },
- helpIconGrid: {
- textAlign: 'right'
}
});
onItemClick: (item: string) => void;
onContextMenu: (event: React.MouseEvent<HTMLElement>, item: string) => void;
onItemDoubleClick: (item: string) => void;
- openHelpDialog: () => void;
}
export interface ApiClientAuthorizationPanelRootDataProps {
& ApiClientAuthorizationPanelRootDataProps & WithStyles<CssRules>;
export const ApiClientAuthorizationPanelRoot = withStyles(styles)(
- ({ classes, onItemDoubleClick, onItemClick, onContextMenu, openHelpDialog }: ApiClientAuthorizationPanelRootProps) =>
- <Card className={classes.card}>
- <CardContent className={classes.cardContent}>
- <Grid container direction="row" justify="flex-end">
- <Grid item xs={12} className={classes.helpIconGrid}>
- <Tooltip title="Api token - help">
- <IconButton onClick={openHelpDialog}>
- <HelpIcon />
- </IconButton>
- </Tooltip>
- </Grid>
- <Grid item xs={12}>
- <DataExplorer
- id={API_CLIENT_AUTHORIZATION_PANEL_ID}
- onRowClick={onItemClick}
- onRowDoubleClick={onItemDoubleClick}
- onContextMenu={onContextMenu}
- contextMenuColumn={true}
- hideColumnSelector
- hideSearchInput
- dataTableDefaultView={
- <DataTableDefaultView
- icon={ShareMeIcon}
- messages={[DEFAULT_MESSAGE]} />
- } />
- </Grid>
- </Grid>
- </CardContent>
- </Card>
+ ({ classes, onItemDoubleClick, onItemClick, onContextMenu }: ApiClientAuthorizationPanelRootProps) =>
+ <div className={classes.root}><DataExplorer
+ id={API_CLIENT_AUTHORIZATION_PANEL_ID}
+ onRowClick={onItemClick}
+ onRowDoubleClick={onItemDoubleClick}
+ onContextMenu={onContextMenu}
+ contextMenuColumn={true}
+ hideColumnSelector
+ hideSearchInput
+ dataTableDefaultView={
+ <DataTableDefaultView
+ icon={ShareMeIcon}
+ messages={[DEFAULT_MESSAGE]} />
+ } /></div>
);
\ No newline at end of file
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 {
},
onItemClick: (resourceUuid: string) => { return; },
onItemDoubleClick: uuid => { return; },
- openHelpDialog: () => {
- dispatch<any>(openApiClientAuthorizationsHelpDialog());
- }
});
export const ApiClientAuthorizationPanel = connect(mapStateToProps, mapDispatchToProps)(ApiClientAuthorizationPanelRoot);
\ No newline at end of file
StyleRulesCallback,
WithStyles,
withStyles,
- Grid,
Button
} from '@material-ui/core';
import { CollectionIcon } from 'components/icon/icon';
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<CssRules> = (theme: ArvadosTheme) => ({
backLink: {
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 {
connect(mapStateToProps, mapDispatchToProps)(
class extends React.Component<CollectionContentAddressPanelActionProps & CollectionContentAddressPanelDataProps & CollectionContentAddressDataProps & WithStyles<CssRules>> {
render() {
- return <Grid item xs={12}>
+ return <div className={this.props.classes.root}>
<Button
onClick={() => window.history.back()}
className={this.props.classes.backLink}>
<BackIcon className={this.props.classes.backIcon} />
Back
</Button>
- <DataExplorer
+ <div className={this.props.classes.content}><DataExplorer
id={COLLECTIONS_CONTENT_ADDRESS_PANEL_ID}
hideSearchInput
onRowClick={this.props.onItemClick}
<DataTableDefaultView
icon={CollectionIcon}
messages={['Collections with this content address not found.']} />
- } />;
- </Grid >;
+ } /></div>
+ </div>;
}
}
)
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';
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'
const styles: StyleRulesCallback<CssRules> = (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',
class extends React.Component<CollectionPanelProps> {
render() {
const { classes, item, dispatch, isWritable, isOldVersion, isLoadingFiles, tooManyFiles } = this.props;
+ const panelsData: MPVPanelState[] = [
+ {name: "Details"},
+ {name: "Properties"},
+ {name: "Files"},
+ ];
return item
- ? <div className={classes.root}>
- <ExpansionPanel data-cy='collection-info-panel' defaultExpanded>
- <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
+ ? <MPVContainer className={classes.root} spacing={8} direction="column" justify-content="flex-start" wrap="nowrap" panelStates={panelsData}>
+ <MPVPanelContent xs="auto" data-cy='collection-info-panel'>
+ <Card className={classes.infoCard}>
<Grid container justify="space-between">
<Grid item xs={11}><span>
<IconButton onClick={this.openCollectionDetails}>
</Tooltip>
</Grid>
</Grid>
- </ExpansionPanelSummary>
- <ExpansionPanelDetails>
<Grid container justify="space-between">
<Grid item xs={12}>
<Typography variant="caption">
}
</Grid>
</Grid>
- </ExpansionPanelDetails>
- </ExpansionPanel>
-
- <ExpansionPanel data-cy='collection-properties-panel' defaultExpanded>
- <ExpansionPanelSummary expandIcon={<ExpandIcon />}>
- {"Properties"}
- </ExpansionPanelSummary>
- <ExpansionPanelDetails>
- <Grid container>
+ </Card>
+ </MPVPanelContent>
+ <MPVPanelContent xs="auto" data-cy='collection-properties-panel'>
+ <Card className={classes.propertiesCard}>
+ <CardHeader title="Properties" />
+ <CardContent><Grid container>
{isWritable && <Grid item xs={12}>
<CollectionTagForm />
</Grid>}
: <div className={classes.centeredLabel}>No properties set on this collection.</div>
}
</Grid>
- </Grid>
- </ExpansionPanelDetails>
- </ExpansionPanel>
- <div className={classes.filesCard}>
- <CollectionPanelFiles
- isWritable={isWritable}
- isLoading={isLoadingFiles}
- tooManyFiles={tooManyFiles}
- loadFilesFunc={() => {
- dispatch(collectionPanelActions.LOAD_BIG_COLLECTIONS(true));
- dispatch<any>(loadCollectionFiles(this.props.item.uuid));
- }
- } />
- </div>
- </div>
+ </Grid></CardContent>
+ </Card>
+ </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));
+ }
+ } />
+ </Card>
+ </MPVPanelContent>
+ </MPVContainer>
: null;
}
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<CssRules> = (theme: ArvadosTheme) => ({
toolbar: {
button: {
marginLeft: theme.spacing.unit
},
+ root: {
+ width: '100%',
+ },
});
export enum FavoritePanelColumnNames {
}
render() {
- return <DataExplorer
+ return <div className={this.props.classes.root}><DataExplorer
id={FAVORITE_PANEL_ID}
onRowClick={this.handleRowClick}
onRowDoubleClick={this.handleRowDoubleClick}
icon={FavoriteIcon}
messages={['Your favorites list is empty.']}
/>
- } />;
+ } /></div>;
}
}
)
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';
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<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ }
+});
export enum GroupsPanelColumnNames {
GROUP = "Name",
resources: ResourcesState;
}
-export const GroupsPanel = connect(
+export const GroupsPanel = withStyles(styles)(connect(
mapStateToProps, mapDispatchToProps
)(
- class GroupsPanel extends React.Component<GroupsPanelProps> {
+ class GroupsPanel extends React.Component<GroupsPanelProps & WithStyles<CssRules>> {
render() {
return (
- <DataExplorer
+ <div className={this.props.classes.root}><DataExplorer
id={GROUPS_PANEL_ID}
onRowClick={noop}
onRowDoubleClick={this.props.onRowDoubleClick}
<AddIcon /> New group
</Button>
</Grid>
- } />
+ } /></div>
);
}
});
}
}
- });
+ }));
const GroupMembersCount = connect(
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<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ }
+});
export enum LinkPanelColumnNames {
NAME = "Name",
onItemDoubleClick: (item: string) => void;
}
-export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps;
+export type LinkPanelRootProps = LinkPanelRootDataProps & LinkPanelRootActionProps & WithStyles<CssRules>;
-export const LinkPanelRoot = (props: LinkPanelRootProps) => {
- return <DataExplorer
+export const LinkPanelRoot = withStyles(styles)((props: LinkPanelRootProps) => {
+ return <div className={props.classes.root}><DataExplorer
id={LINK_PANEL_ID}
onRowClick={props.onItemClick}
onRowDoubleClick={props.onItemDoubleClick}
onContextMenu={props.onContextMenu}
- contextMenuColumn={true}
+ contextMenuColumn={true}
hideColumnSelector
hideSearchInput
dataTableDefaultView={
<DataTableDefaultView
icon={ShareMeIcon}
messages={['Your link list is empty.']} />
- }/>;
-};
\ No newline at end of file
+ }/></div>;
+});
\ No newline at end of file
--- /dev/null
+// 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<CssRules, string> }) => {
+ const item = props.item;
+ const classes = props.classes || { label: '', value: '', button: '' };
+ const mdSize = props.twoCol ? 6 : 12;
+ return <Grid container>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Type' value={resourceLabel(ResourceKind.PROCESS)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute classLabel={classes.label} classValue={classes.value}
+ label='Owner' linkToUuid={item.ownerUuid}
+ uuidEnhancer={(uuid: string) => <ResourceOwnerWithName uuid={uuid} />} />
+ </Grid>
+ <Grid item xs={12} md={12}>
+ <DetailsAttribute label='Status' value={item.state} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Last modified' value={formatDate(item.modifiedAt)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Started at' value={formatDate(item.createdAt)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Created at' value={formatDate(item.createdAt)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Finished at' value={formatDate(item.expiresAt)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Outputs' value={item.outputPath} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='UUID' linkToUuid={item.uuid} value={item.uuid} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Container UUID' value={item.containerUuid} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Priority' value={item.priority} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Runtime Constraints'
+ value={JSON.stringify(item.runtimeConstraints)} />
+ </Grid>
+ <Grid item xs={12} md={mdSize}>
+ <DetailsAttribute label='Docker Image locator'
+ linkToUuid={item.containerImage} value={item.containerImage} />
+ </Grid>
+ </Grid>;
+};
--- /dev/null
+// 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<CssRules> = (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<CssRules> & MPVPanelProps;
+
+export const ProcessDetailsCard = withStyles(styles)(
+ ({ classes, process, doHidePanel, panelName }: ProcessDetailsCardProps) => {
+ return <Card className={classes.card}>
+ <CardHeader
+ classes={{
+ content: classes.title,
+ }}
+ title='Details'
+ action={ doHidePanel &&
+ <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+ <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+ </Tooltip> } />
+ <CardContent className={classes.content}>
+ <ProcessDetailsAttributes item={process.containerRequest} twoCol />
+ </CardContent>
+ </Card>;
+ }
+);
+
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';
cancelProcess: (uuid: string) => void;
}
-type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true>;
+type ProcessInformationCardProps = ProcessInformationCardDataProps & WithStyles<CssRules, true> & 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';
<MoreOptionsIcon />
</IconButton>
</Tooltip>
+ { doHidePanel &&
+ <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+ <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
+ </Tooltip> }
</div>
}
title={
// 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<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ },
+});
export interface ProcessPanelRootDataProps {
process?: Process;
cancelProcess: (uuid: string) => void;
}
-export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
+export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
+
+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
- ? <Grid container spacing={16} alignItems="stretch">
- <Grid item sm={12} md={12}>
+ ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData} justify-content="flex-start" direction="column" wrap="nowrap">
+ <MPVPanelContent xs="auto">
<ProcessInformationCard
process={process}
onContextMenu={event => props.onContextMenu(event, process)}
openWorkflow={props.navigateToWorkflow}
cancelProcess={props.cancelProcess}
/>
- </Grid>
- <Grid item sm={12} md={12}>
+ </MPVPanelContent>
+ <MPVPanelContent xs="auto">
+ <ProcessDetailsCard process={process} />
+ </MPVPanelContent>
+ <MPVPanelContent xs>
<SubprocessPanel />
- </Grid>
- </Grid>
+ </MPVPanelContent>
+ </MPVContainer>
: <Grid container
alignItems='center'
justify='center'
<DefaultView
icon={ProcessIcon}
messages={['Process not found']} />
- </Grid>;
+ </Grid>);
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
- position: 'relative',
width: '100%',
- height: '100%'
},
button: {
marginLeft: theme.spacing.unit
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<CssRules> = (theme: ArvadosTheme) => ({
toolbar: {
button: {
marginLeft: theme.spacing.unit
},
+ root: {
+ width: '100%',
+ },
});
export enum PublicFavoritePanelColumnNames {
connect(mapStateToProps, mapDispatchToProps)(
class extends React.Component<FavoritePanelProps> {
render() {
- return <DataExplorer
+ return <div className={this.props.classes.root}><DataExplorer
id={PUBLIC_FAVORITE_PANEL_ID}
onRowClick={this.props.onItemClick}
onRowDoubleClick={this.props.onItemDoubleClick}
<DataTableDefaultView
icon={PublicFavoriteIcon}
messages={['Public favorites list is empty.']} />
- } />;
+ } /></div>;
}
}
)
} 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<CssRules> = (theme: ArvadosTheme) => ({
toolbar: {
button: {
marginLeft: theme.spacing.unit
},
+ root: {
+ width: '100%',
+ },
});
interface SharedWithMePanelDataProps {
}))(
class extends React.Component<SharedWithMePanelProps> {
render() {
- return <DataExplorer
+ return <div className={this.props.classes.root}><DataExplorer
id={SHARED_WITH_ME_PANEL_ID}
onRowClick={this.handleRowClick}
onRowDoubleClick={this.handleRowDoubleClick}
onContextMenu={this.handleContextMenu}
contextMenuColumn={false}
- dataTableDefaultView={<DataTableDefaultView icon={ShareMeIcon} />} />;
+ dataTableDefaultView={<DataTableDefaultView icon={ShareMeIcon} />} /></div>;
}
handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
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",
'The current process may not have any or none matches current filtering.'
];
-export const SubprocessPanelRoot = (props: SubprocessPanelProps) => {
+export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps) => {
return <DataExplorer
id={SUBPROCESS_PANEL_ID}
onRowClick={props.onItemClick}
<DataTableDefaultView
icon={ProcessIcon}
messages={DEFAULT_VIEW_MESSAGES} />
- } />;
+ }
+ doHidePanel={props.doHidePanel}
+ doMaximizePanel={props.doMaximizePanel}
+ panelMaximized={props.panelMaximized}
+ panelName={props.panelName} />;
};
\ No newline at end of file
getTrashPanelTypeFilters
} from 'store/resource-type-filters/resource-type-filters';
-type CssRules = "toolbar" | "button";
+type CssRules = "toolbar" | "button" | "root";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
toolbar: {
button: {
marginLeft: theme.spacing.unit
},
+ root: {
+ width: '100%',
+ },
});
export enum TrashPanelColumnNames {
}))(
class extends React.Component<TrashPanelProps> {
render() {
- return <DataExplorer
+ return <div className={this.props.classes.root}><DataExplorer
id={TRASH_PANEL_ID}
onRowClick={this.handleRowClick}
onRowDoubleClick={this.handleRowDoubleClick}
<DataTableDefaultView
icon={TrashIcon}
messages={['Your trash list is empty.']}/>
- } />;
+ } /></div>;
}
handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
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<UserPanelRules>(theme => ({
button: {
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 {
render() {
const { value } = this.state;
- return <Paper>
+ return <Paper className={this.props.classes.root}>
<Tabs value={value} onChange={this.handleChange} fullWidth>
<Tab label="USERS" />
<Tab label="ACTIVITY" disabled />
</Tabs>
{value === 0 &&
- <span>
+ <div className={this.props.classes.content}>
<DataExplorer
id={USERS_PANEL_ID}
onRowClick={noop}
icon={ShareMeIcon}
messages={['Your user list is empty.']} />
} />
- </span>}
+ </div>}
</Paper>;
}
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',
}
});