"react-router-dom": "4.3.1",
"react-router-redux": "5.0.0-alpha.9",
"react-scripts-ts": "2.17.0",
+ "react-transition-group": "2.4.0",
"redux": "4.0.0",
"redux-thunk": "2.3.0",
"unionize": "2.1.2"
import * as React from 'react';
import { StyleRulesCallback, WithStyles, Typography, withStyles, Theme } from '@material-ui/core';
import { ArvadosTheme } from '~/common/custom-theme';
+import * as classNames from 'classnames';
type CssRules = 'root';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
boxSizing: 'border-box',
- width: '100%',
- height: 'auto',
- maxHeight: '550px',
overflow: 'auto',
padding: theme.spacing.unit
}
export interface CodeSnippetDataProps {
lines: string[];
+ className?: string;
}
type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
export const CodeSnippet = withStyles(styles)(
- ({ classes, lines }: CodeSnippetProps) =>
- <Typography component="div" className={classes.root}>
+ ({ classes, lines, className }: CodeSnippetProps) =>
+ <Typography
+ component="div"
+ className={classNames(classes.root, className)}>
{
lines.map((line: string, index: number) => {
return <Typography key={index} component="pre">{line}</Typography>;
}
});
-type DefaultCodeSnippet = CodeSnippetDataProps;
-
-export const DefaultCodeSnippet = (props: DefaultCodeSnippet) =>
+export const DefaultCodeSnippet = (props: CodeSnippetDataProps) =>
<MuiThemeProvider theme={theme}>
- <CodeSnippet lines={props.lines} />
+ <CodeSnippet {...props} />
</MuiThemeProvider>;
\ No newline at end of file
import { Config } from '~/common/config';
import { addRouteChangeHandlers } from './routes/route-change-handlers';
import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
+import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set';
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet);
addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSet);
addMenuActionSet(ContextMenuKind.PROCESS, processActionSet);
+addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
fetchConfig()
return getProjectUrl(uuid);
case ResourceKind.COLLECTION:
return getCollectionUrl(uuid);
+ case ResourceKind.PROCESS:
+ return getProcessUrl(uuid);
default:
return undefined;
}
export enum CommonResourceServiceError {
UNIQUE_VIOLATION = 'UniqueViolation',
OWNERSHIP_CYCLE = 'OwnershipCycle',
+ MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingContainerRequestFinalState',
UNKNOWN = 'Unknown',
NONE = 'None'
}
return CommonResourceServiceError.UNIQUE_VIOLATION;
case /ownership cycle/.test(error):
return CommonResourceServiceError.OWNERSHIP_CYCLE;
+ case /Mounts cannot be modified in state 'Final'/.test(error):
+ return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE;
default:
return CommonResourceServiceError.UNKNOWN;
}
import { RootState } from '~/store/store';
import { ServiceRepository } from '~/services/services';
import { getCommonResourceServiceError, CommonResourceServiceError } from '~/services/common-service/common-resource-service';
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
export const COLLECTION_COPY_FORM_NAME = 'collectionCopyFormName';
-export interface CollectionCopyFormDialogData {
- name: string;
- ownerUuid: string;
- uuid: string;
-}
-
export const openCollectionCopyDialog = (resource: { name: string, uuid: string }) =>
(dispatch: Dispatch) => {
dispatch<any>(resetPickerProjectTree());
- const initialData: CollectionCopyFormDialogData = { name: `Copy of: ${resource.name}`, ownerUuid: '', uuid: resource.uuid };
+ const initialData: CopyFormDialogData = { name: `Copy of: ${resource.name}`, ownerUuid: '', uuid: resource.uuid };
dispatch<any>(initialize(COLLECTION_COPY_FORM_NAME, initialData));
dispatch(dialogActions.OPEN_DIALOG({ id: COLLECTION_COPY_FORM_NAME, data: {} }));
};
-export const copyCollection = (resource: CollectionCopyFormDialogData) =>
+export const copyCollection = (resource: CopyFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(startSubmit(COLLECTION_COPY_FORM_NAME));
try {
import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
import { extractUuidKind, ResourceKind } from '~/models/resource';
import { matchProcessRoute } from '~/routes/routes';
+import { Process } from '~/store/processes/process';
export const contextMenuActions = unionize({
OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
}
};
-export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>) =>
+export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>, process: Process) =>
(dispatch: Dispatch, getState: () => RootState) => {
- const { location } = getState().router;
- const pathname = location ? location.pathname : '';
- const match = matchProcessRoute(pathname);
- const uuid = match ? match.params.id : '';
const resource = {
- uuid,
+ uuid: process.containerRequest.uuid,
ownerUuid: '',
kind: ResourceKind.PROCESS,
name: '',
return ContextMenuKind.PROJECT;
case ResourceKind.COLLECTION:
return ContextMenuKind.COLLECTION_RESOURCE;
+ case ResourceKind.PROCESS:
+ return ContextMenuKind.PROCESS_RESOURCE;
case ResourceKind.USER:
return ContextMenuKind.ROOT_PROJECT;
default:
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface CopyFormDialogData {
+ name: string;
+ uuid: string;
+ ownerUuid: string;
+}
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { RootState } from '../store';
+import { Dispatch } from 'redux';
+import { getProcess } from '~/store/processes/process';
+
+export const PROCESS_COMMAND_DIALOG_NAME = 'processCommandDialog';
+
+export interface ProcessCommandDialogData {
+ command: string;
+ processName: string;
+}
+
+export const openProcessCommandDialog = (processUuid: string) =>
+ (dispatch: Dispatch<any>, getState: () => RootState) => {
+ const process = getProcess(processUuid)(getState().resources);
+ if (process) {
+ const data: ProcessCommandDialogData = {
+ command: process.containerRequest.command.join(' '),
+ processName: process.containerRequest.name,
+ };
+ dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_COMMAND_DIALOG_NAME, data }));
+ }
+ };
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { initialize, startSubmit } from 'redux-form';
+import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+import { getProcess, ProcessStatus, getProcessStatus } from '~/store/processes/process';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+
+export const PROCESS_COPY_FORM_NAME = 'processCopyFormName';
+
+export const openCopyProcessDialog = (resource: { name: string, uuid: string }) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const process = getProcess(resource.uuid)(getState().resources);
+ if (process) {
+ const processStatus = getProcessStatus(process);
+ if (processStatus === ProcessStatus.DRAFT) {
+ dispatch<any>(resetPickerProjectTree());
+ const initialData: CopyFormDialogData = { name: `Copy of: ${resource.name}`, uuid: resource.uuid, ownerUuid: '' };
+ dispatch<any>(initialize(PROCESS_COPY_FORM_NAME, initialData));
+ dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_COPY_FORM_NAME, data: {} }));
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'You can copy only draft processes.', hideDuration: 2000 }));
+ }
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process not found', hideDuration: 2000 }));
+ }
+ };
+
+export const copyProcess = (resource: CopyFormDialogData) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(startSubmit(PROCESS_COPY_FORM_NAME));
+ try {
+ const process = await services.containerRequestService.get(resource.uuid);
+ const uuidKey = 'uuid';
+ delete process[uuidKey];
+ await services.containerRequestService.create({ ...process, ownerUuid: resource.ownerUuid, name: resource.name });
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_COPY_FORM_NAME }));
+ return process;
+ } catch (e) {
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_COPY_FORM_NAME }));
+ throw new Error('Could not copy the process.');
+ }
+ };
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { startSubmit, stopSubmit, initialize } from 'redux-form';
+import { ServiceRepository } from '~/services/services';
+import { RootState } from '~/store/store';
+import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
+import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions';
+import { projectPanelActions } from '~/store/project-panel/project-panel-action';
+import { getProcess, getProcessStatus, ProcessStatus } from '~/store/processes/process';
+
+export const PROCESS_MOVE_FORM_NAME = 'processMoveFormName';
+
+export const openMoveProcessDialog = (resource: { name: string, uuid: string }) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const process = getProcess(resource.uuid)(getState().resources);
+ if (process) {
+ const processStatus = getProcessStatus(process);
+ if (processStatus === ProcessStatus.DRAFT) {
+ dispatch<any>(resetPickerProjectTree());
+ dispatch(initialize(PROCESS_MOVE_FORM_NAME, resource));
+ dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_MOVE_FORM_NAME, data: {} }));
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'You can move only draft processes.', hideDuration: 2000 }));
+ }
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process not found', hideDuration: 2000 }));
+ }
+ };
+
+export const moveProcess = (resource: MoveToFormDialogData) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(startSubmit(PROCESS_MOVE_FORM_NAME));
+ try {
+ const process = await services.containerRequestService.get(resource.uuid);
+ await services.containerRequestService.update(resource.uuid, { ...process, ownerUuid: resource.ownerUuid });
+ dispatch(projectPanelActions.REQUEST_ITEMS());
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_MOVE_FORM_NAME }));
+ return process;
+ } catch (e) {
+ const error = getCommonResourceServiceError(e);
+ if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
+ dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'A process with the same name already exists in the target project.' }));
+ } else if (error === CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE) {
+ dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'You can move only draft processes.' }));
+ } else {
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_MOVE_FORM_NAME }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the process.', hideDuration: 2000 }));
+ }
+ return;
+ }
+ };
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from "redux";
+import { initialize, startSubmit, stopSubmit } from 'redux-form';
+import { RootState } from "~/store/store";
+import { dialogActions } from "~/store/dialog/dialog-actions";
+import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service";
+import { ServiceRepository } from "~/services/services";
+import { getProcess } from '~/store/processes/process';
+import { projectPanelActions } from '~/store/project-panel/project-panel-action';
+import { snackbarActions } from '~/store/snackbar/snackbar-actions';
+
+export interface ProcessUpdateFormDialogData {
+ uuid: string;
+ name: string;
+}
+
+export const PROCESS_UPDATE_FORM_NAME = 'processUpdateFormName';
+
+export const openProcessUpdateDialog = (resource: ProcessUpdateFormDialogData) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const process = getProcess(resource.uuid)(getState().resources);
+ if(process) {
+ dispatch(initialize(PROCESS_UPDATE_FORM_NAME, { ...resource, name: process.containerRequest.name }));
+ dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_UPDATE_FORM_NAME, data: {} }));
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process not found', hideDuration: 2000 }));
+ }
+ };
+
+export const updateProcess = (resource: ProcessUpdateFormDialogData) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(startSubmit(PROCESS_UPDATE_FORM_NAME));
+ try {
+ const process = await services.containerRequestService.get(resource.uuid);
+ const updatedProcess = await services.containerRequestService.update(resource.uuid, { ...process, name: resource.name });
+ dispatch(projectPanelActions.REQUEST_ITEMS());
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_UPDATE_FORM_NAME }));
+ return updatedProcess;
+ } catch (e) {
+ const error = getCommonResourceServiceError(e);
+ if (error === CommonResourceServiceError.UNIQUE_VIOLATION) {
+ dispatch(stopSubmit(PROCESS_UPDATE_FORM_NAME, { name: 'Process with the same name already exists.' }));
+ } else if (error === CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE) {
+ dispatch(stopSubmit(PROCESS_UPDATE_FORM_NAME, { name: 'You cannot modified in "Final" state.' }));
+ } else {
+ dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_UPDATE_FORM_NAME }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not update the process.', hideDuration: 2000 }));
+ }
+ return;
+ }
+ };
\ No newline at end of file
import * as collectionUpdateActions from '~/store/collections/collection-update-actions';
import * as collectionMoveActions from '~/store/collections/collection-move-actions';
import * as processesActions from '../processes/processes-actions';
+import * as processMoveActions from '~/store/processes/process-move-actions';
+import * as processUpdateActions from '~/store/processes/process-update-actions';
+import * as processCopyActions from '~/store/processes/process-copy-actions';
import { trashPanelColumns } from "~/views/trash-panel/trash-panel";
import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action";
import { initProcessLogsPanel } from '../process-logs-panel/process-logs-panel-actions';
import { sharedWithMePanelActions } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
import { loadSharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel-actions';
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
export const loadWorkbench = () =>
async (dispatch: Dispatch, getState: () => RootState) => {
}
};
-export const copyCollection = (data: collectionCopyActions.CollectionCopyFormDialogData) =>
+export const copyCollection = (data: CopyFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
try {
const collection = await dispatch<any>(collectionCopyActions.copyCollection(data));
};
+export const updateProcess = (data: processUpdateActions.ProcessUpdateFormDialogData) =>
+ async (dispatch: Dispatch) => {
+ try {
+ const process = await dispatch<any>(processUpdateActions.updateProcess(data));
+ if (process) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Process has been successfully updated.",
+ hideDuration: 2000
+ }));
+ dispatch<any>(updateResources([process]));
+ dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+ }
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000 }));
+ }
+ };
+
+export const moveProcess = (data: MoveToFormDialogData) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const process = await dispatch<any>(processMoveActions.moveProcess(data));
+ dispatch<any>(updateResources([process]));
+ dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been moved.', hideDuration: 2000 }));
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000 }));
+ }
+ };
+
+export const copyProcess = (data: CopyFormDialogData) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const process = await dispatch<any>(processCopyActions.copyProcess(data));
+ dispatch<any>(updateResources([process]));
+ dispatch<any>(reloadProjectMatchingUuid([process.ownerUuid]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been copied.', hideDuration: 2000 }));
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000 }));
+ }
+ };
+
export const loadProcessLog = (uuid: string) =>
async (dispatch: Dispatch) => {
const process = await dispatch<any>(processesActions.loadProcess(uuid));
export const COPY_FILE_VALIDATION = [require];
export const MOVE_TO_VALIDATION = [require];
+
+export const PROCESS_NAME_VALIDATION = [require, maxLength(255)];
\ No newline at end of file
} from "~/components/icon/icon";
import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
import { navigateToProcessLogs } from '~/store/navigation/navigation-action';
+import { openMoveProcessDialog } from '~/store/processes/process-move-actions';
+import { openProcessUpdateDialog } from "~/store/processes/process-update-actions";
+import { openCopyProcessDialog } from '~/store/processes/process-copy-actions';
+import { openProcessCommandDialog } from '../../../store/processes/process-command-actions';
export const processActionSet: ContextMenuActionSet = [[
{
icon: RenameIcon,
name: "Edit process",
- execute: (dispatch, resource) => {
- // add code
- }
+ execute: (dispatch, resource) => dispatch<any>(openProcessUpdateDialog(resource))
},
{
icon: ShareIcon,
{
icon: MoveToIcon,
name: "Move to",
- execute: (dispatch, resource) => {
- // add code
- }
+ execute: (dispatch, resource) => dispatch<any>(openMoveProcessDialog(resource))
},
{
component: ToggleFavoriteAction,
{
icon: CopyIcon,
name: "Copy to project",
- execute: (dispatch, resource) => {
- // add code
- }
+ execute: (dispatch, resource) => dispatch<any>(openCopyProcessDialog(resource))
},
{
icon: ReRunProcessIcon,
icon: CommandIcon,
name: "Command",
execute: (dispatch, resource) => {
- // add code
+ dispatch<any>(openProcessCommandDialog(resource.uuid));
}
},
{
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ToggleFavoriteAction } from "../actions/favorite-action";
+import { toggleFavorite } from "~/store/favorites/favorites-actions";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon";
+import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
+import { openMoveProcessDialog } from '~/store/processes/process-move-actions';
+import { openProcessUpdateDialog } from "~/store/processes/process-update-actions";
+import { openCopyProcessDialog } from '~/store/processes/process-copy-actions';
+
+export const processResourceActionSet: ContextMenuActionSet = [[
+ {
+ icon: RenameIcon,
+ name: "Edit process",
+ execute: (dispatch, resource) => dispatch<any>(openProcessUpdateDialog(resource))
+ },
+ {
+ icon: ShareIcon,
+ name: "Share",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: MoveToIcon,
+ name: "Move to",
+ execute: (dispatch, resource) => dispatch<any>(openMoveProcessDialog(resource))
+ },
+ {
+ component: ToggleFavoriteAction,
+ execute: (dispatch, resource) => {
+ dispatch<any>(toggleFavorite(resource)).then(() => {
+ dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
+ });
+ }
+ },
+ {
+ icon: CopyIcon,
+ name: "Copy to project",
+ execute: (dispatch, resource) => dispatch<any>(openCopyProcessDialog(resource))
+ },
+ {
+ icon: DetailsIcon,
+ name: "View details",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ },
+ {
+ icon: RemoveIcon,
+ name: "Remove",
+ execute: (dispatch, resource) => {
+ // add code
+ }
+ }
+]];
COLLECTION = 'Collection',
COLLECTION_RESOURCE = 'CollectionResource',
PROCESS = "Process",
+ PROCESS_RESOURCE = 'ProcessResource',
PROCESS_LOGS = "ProcessLogs"
}
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Drawer, IconButton, Tabs, Tab, Typography, Grid } from '@material-ui/core';
+import { IconButton, Tabs, Tab, Typography, Grid, Tooltip } from '@material-ui/core';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { Transition } from 'react-transition-group';
import { ArvadosTheme } from '~/common/custom-theme';
import * as classnames from "classnames";
import { connect } from 'react-redux';
import { EmptyDetails } from "./empty-details";
import { DetailsData } from "./details-data";
import { DetailsResource } from "~/models/details";
-import { getResource } from '../../store/resources/resources';
+import { getResource } from '~/store/resources/resources';
-type CssRules = 'root' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'headerTitle' | 'tabContainer';
+type CssRules = 'root' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'tabContainer';
-const drawerWidth = 320;
+const DRAWER_WIDTH = 320;
+const SLIDE_TIMEOUT = 500;
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
- width: 0,
- overflowX: 'hidden',
- transition: 'width 0.5s ease',
background: theme.palette.background.paper,
borderLeft: `1px solid ${theme.palette.divider}`,
height: '100%',
+ overflow: 'hidden',
+ transition: `width ${SLIDE_TIMEOUT}ms ease`,
+ width: 0,
},
opened: {
- width: drawerWidth,
+ width: DRAWER_WIDTH,
},
container: {
- width: drawerWidth,
- },
- drawerPaper: {
- position: 'relative',
- width: drawerWidth
+ maxWidth: 'none',
+ width: DRAWER_WIDTH,
},
headerContainer: {
color: theme.palette.grey["600"],
margin: `${theme.spacing.unit}px 0`,
- textAlign: 'center'
+ textAlign: 'center',
},
headerIcon: {
- fontSize: '2.125rem'
- },
- headerTitle: {
- overflowWrap: 'break-word',
- wordWrap: 'break-word'
+ fontSize: '2.125rem',
},
tabContainer: {
- padding: theme.spacing.unit * 3
- }
+ overflow: 'auto',
+ padding: theme.spacing.unit * 3,
+ },
});
const getItem = (resource: DetailsResource): DetailsData => {
this.setState({ tabsValue: value });
}
- renderTabContainer = (children: React.ReactElement<any>) =>
- <Typography className={this.props.classes.tabContainer} component="div">
- {children}
- </Typography>
-
render() {
- const { classes, onCloseDrawer, isOpened, item } = this.props;
- const { tabsValue } = this.state;
+ const { classes, isOpened } = this.props;
return (
- <div className={classnames([classes.root, { [classes.opened]: isOpened }])}>
- <div className={classes.container}>
- <div className={classes.headerContainer}>
- <Grid container alignItems='center' justify='space-around'>
- <Grid item xs={2}>
- {item.getIcon(classes.headerIcon)}
- </Grid>
- <Grid item xs={8}>
- <Typography variant="title" className={classes.headerTitle}>
- {item.getTitle()}
- </Typography>
- </Grid>
- <Grid item>
- <IconButton color="inherit" onClick={onCloseDrawer}>
- {<CloseIcon />}
- </IconButton>
- </Grid>
- </Grid>
- </div>
- <Tabs value={tabsValue} onChange={this.handleChange}>
- <Tab disableRipple label="Details" />
- <Tab disableRipple label="Activity" disabled />
- </Tabs>
- {tabsValue === 0 && this.renderTabContainer(
- <Grid container direction="column">
- {item.getDetails()}
- </Grid>
- )}
- {tabsValue === 1 && this.renderTabContainer(
- <Grid container direction="column" />
- )}
- </div>
- </div>
+ <Grid
+ container
+ direction="column"
+ className={classnames([classes.root, { [classes.opened]: isOpened }])}>
+ <Transition
+ in={isOpened}
+ timeout={SLIDE_TIMEOUT}
+ unmountOnExit>
+ {this.renderContent()}
+ </Transition>
+ </Grid>
);
}
+
+ renderContent() {
+ const { classes, onCloseDrawer, item } = this.props;
+ const { tabsValue } = this.state;
+ return <Grid
+ container
+ direction="column"
+ item
+ xs
+ className={classes.container} >
+ <Grid
+ item
+ className={classes.headerContainer}
+ container
+ alignItems='center'
+ justify='space-around'
+ wrap="nowrap">
+ <Grid item xs={2}>
+ {item.getIcon(classes.headerIcon)}
+ </Grid>
+ <Grid item xs={8}>
+ <Tooltip title={item.getTitle()}>
+ <Typography variant="title" noWrap>
+ {item.getTitle()}
+ </Typography>
+ </Tooltip>
+ </Grid>
+ <Grid item>
+ <IconButton color="inherit" onClick={onCloseDrawer}>
+ <CloseIcon />
+ </IconButton>
+ </Grid>
+ </Grid>
+ <Grid item>
+ <Tabs value={tabsValue} onChange={this.handleChange}>
+ <Tab disableRipple label="Details" />
+ <Tab disableRipple label="Activity" disabled />
+ </Tabs>
+ </Grid>
+ <Grid item xs className={this.props.classes.tabContainer} >
+ {tabsValue === 0
+ ? item.getDetails()
+ : null}
+ </Grid>
+ </Grid >;
+ }
}
)
);
{...props}
/>;
-export const CollectionPartialCopyFields = () => <div style={{ display: 'flex' }}>
- <div>
- <CollectionNameField />
- <CollectionDescriptionField />
- </div>
+export const CollectionPartialCopyFields = () => <div>
+ <CollectionNameField />
+ <CollectionDescriptionField />
<CollectionProjectPickerField />
</div>;
import { ProjectTreePickerField } from '~/views-components/project-tree-picker/project-tree-picker';
import { COPY_NAME_VALIDATION, COPY_FILE_VALIDATION } from '~/validators/validators';
import { TextField } from "~/components/text-field/text-field";
-import { CollectionCopyFormDialogData } from "~/store/collections/collection-copy-actions";
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
-type CopyFormDialogProps = WithDialogProps<string> & InjectedFormProps<CollectionCopyFormDialogData>;
+type CopyFormDialogProps = WithDialogProps<string> & InjectedFormProps<CopyFormDialogData>;
-export const DialogCollectionCopy = (props: CopyFormDialogProps) =>
+export const DialogCopy = (props: CopyFormDialogProps) =>
<FormDialog
dialogTitle='Make a copy'
- formFields={CollectionCopyFields}
+ formFields={CopyDialogFields}
submitLabel='Copy'
{...props}
/>;
-const CollectionCopyFields = () => <span>
+const CopyDialogFields = () => <span>
<Field
name='name'
component={TextField}
import { compose } from "redux";
import { withDialog } from "~/store/dialog/with-dialog";
import { reduxForm } from 'redux-form';
-import { COLLECTION_COPY_FORM_NAME, CollectionCopyFormDialogData } from '~/store/collections/collection-copy-actions';
-import { DialogCollectionCopy } from "~/views-components/dialog-copy/dialog-collection-copy";
+import { COLLECTION_COPY_FORM_NAME } from '~/store/collections/collection-copy-actions';
+import { DialogCopy } from "~/views-components/dialog-copy/dialog-copy";
import { copyCollection } from '~/store/workbench/workbench-actions';
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
export const CopyCollectionDialog = compose(
withDialog(COLLECTION_COPY_FORM_NAME),
- reduxForm<CollectionCopyFormDialogData>({
+ reduxForm<CopyFormDialogData>({
form: COLLECTION_COPY_FORM_NAME,
onSubmit: (data, dispatch) => {
dispatch(copyCollection(data));
}
})
-)(DialogCollectionCopy);
\ No newline at end of file
+)(DialogCopy);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { compose } from "redux";
+import { withDialog } from "~/store/dialog/with-dialog";
+import { reduxForm } from 'redux-form';
+import { PROCESS_COPY_FORM_NAME } from '~/store/processes/process-copy-actions';
+import { DialogCopy } from "~/views-components/dialog-copy/dialog-copy";
+import { copyProcess } from '~/store/workbench/workbench-actions';
+import { CopyFormDialogData } from '~/store/copy-dialog/copy-dialog';
+
+export const CopyProcessDialog = compose(
+ withDialog(PROCESS_COPY_FORM_NAME),
+ reduxForm<CopyFormDialogData>({
+ form: PROCESS_COPY_FORM_NAME,
+ onSubmit: (data, dispatch) => {
+ dispatch(copyProcess(data));
+ }
+ })
+)(DialogCopy);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { compose } from 'redux';
+import { withDialog } from "~/store/dialog/with-dialog";
+import { reduxForm } from 'redux-form';
+import { PROCESS_MOVE_FORM_NAME } from '~/store/processes/process-move-actions';
+import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
+import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to';
+import { moveProcess } from '~/store/workbench/workbench-actions';
+
+export const MoveProcessDialog = compose(
+ withDialog(PROCESS_MOVE_FORM_NAME),
+ reduxForm<MoveToFormDialogData>({
+ form: PROCESS_MOVE_FORM_NAME,
+ onSubmit: (data, dispatch) => {
+ dispatch(moveProcess(data));
+ }
+ })
+)(DialogMoveTo);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { compose } from "redux";
+import { reduxForm } from 'redux-form';
+import { withDialog } from "~/store/dialog/with-dialog";
+import { DialogProcessUpdate } from '~/views-components/dialog-update/dialog-process-update';
+import { PROCESS_UPDATE_FORM_NAME, ProcessUpdateFormDialogData } from '~/store/processes/process-update-actions';
+import { updateProcess } from "~/store/workbench/workbench-actions";
+
+export const UpdateProcessDialog = compose(
+ withDialog(PROCESS_UPDATE_FORM_NAME),
+ reduxForm<ProcessUpdateFormDialogData>({
+ form: PROCESS_UPDATE_FORM_NAME,
+ onSubmit: (data, dispatch) => {
+ dispatch(updateProcess(data));
+ }
+ })
+)(DialogProcessUpdate);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { InjectedFormProps } from 'redux-form';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { ProcessUpdateFormDialogData } from '~/store/processes/process-update-actions';
+import { FormDialog } from '~/components/form-dialog/form-dialog';
+import { ProcessNameField } from '~/views-components/form-fields/process-form-fields';
+
+type DialogProcessProps = WithDialogProps<{}> & InjectedFormProps<ProcessUpdateFormDialogData>;
+
+export const DialogProcessUpdate = (props: DialogProcessProps) =>
+ <FormDialog
+ dialogTitle='Edit Process'
+ formFields={ProcessEditFields}
+ submitLabel='Save'
+ {...props}
+ />;
+
+const ProcessEditFields = () => <span>
+ <ProcessNameField />
+</span>;
validate={COLLECTION_PROJECT_VALIDATION} />;
const ProjectPicker = (props: WrappedFieldProps) =>
- <div style={{ width: '400px', height: '144px', display: 'flex', flexDirection: 'column' }}>
+ <div style={{ height: '144px', display: 'flex', flexDirection: 'column' }}>
<ProjectTreePicker onChange={projectUuid => props.input.onChange(projectUuid)} />
</div>;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Field, WrappedFieldProps } from "redux-form";
+import { TextField } from "~/components/text-field/text-field";
+import { PROCESS_NAME_VALIDATION } from "~/validators/validators";
+
+export const ProcessNameField = () =>
+ <Field
+ name='name'
+ component={TextField}
+ validate={PROCESS_NAME_VALIDATION}
+ label="Process Name" />;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dialog, DialogTitle, DialogActions, Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
+import { withDialog } from "~/store/dialog/with-dialog";
+import { PROCESS_COMMAND_DIALOG_NAME } from '~/store/processes/process-command-actions';
+import { WithDialogProps } from '~/store/dialog/with-dialog';
+import { ProcessCommandDialogData } from '~/store/processes/process-command-actions';
+import { DefaultCodeSnippet } from "~/components/default-code-snippet/default-code-snippet";
+import { compose } from 'redux';
+
+type CssRules = 'codeSnippet';
+
+const styles: StyleRulesCallback<CssRules> = theme => ({
+ codeSnippet: {
+ marginLeft: theme.spacing.unit * 3,
+ marginRight: theme.spacing.unit * 3,
+ }
+});
+
+export const ProcessCommandDialog = compose(
+ withDialog(PROCESS_COMMAND_DIALOG_NAME),
+ withStyles(styles),
+)(
+ (props: WithDialogProps<ProcessCommandDialogData> & WithStyles<CssRules>) =>
+ <Dialog
+ open={props.open}
+ maxWidth="md"
+ onClose={props.closeDialog}
+ style={{ alignSelf: 'stretch' }}>
+ <DialogTitle>{`Command - ${props.data.processName}`}</DialogTitle>
+ <DefaultCodeSnippet
+ className={props.classes.codeSnippet}
+ lines={[props.data.command]} />
+ <DialogActions>
+ <Button
+ variant='flat'
+ color='primary'
+ onClick={props.closeDialog}>
+ Close
+ </Button>
+ </DialogActions>
+ </Dialog>
+);
\ No newline at end of file
// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
-import { CodeSnippet, CodeSnippetDataProps } from '~/components/code-snippet/code-snippet';
+import { MuiThemeProvider, createMuiTheme, StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
+import { CodeSnippet } from '~/components/code-snippet/code-snippet';
import grey from '@material-ui/core/colors/grey';
+type CssRules = 'codeSnippet';
+
+const styles: StyleRulesCallback<CssRules> = () => ({
+ codeSnippet: {
+ maxHeight: '550px',
+ }
+});
+
const theme = createMuiTheme({
overrides: {
MuiTypography: {
}
});
-type ProcessLogCodeSnippet = CodeSnippetDataProps;
+interface ProcessLogCodeSnippetProps {
+ lines: string[];
+}
-export const ProcessLogCodeSnippet = (props: ProcessLogCodeSnippet) =>
- <MuiThemeProvider theme={theme}>
- <CodeSnippet lines={props.lines} />
- </MuiThemeProvider>;
\ No newline at end of file
+export const ProcessLogCodeSnippet = withStyles(styles)(
+ (props: ProcessLogCodeSnippetProps & WithStyles<CssRules>) =>
+ <MuiThemeProvider theme={theme}>
+ <CodeSnippet lines={props.lines} className={props.classes.codeSnippet} />
+ </MuiThemeProvider>);
\ No newline at end of file
process: Process;
}
-export type ProcessLogMainCardProps = ProcessLogMainCardDataProps & CodeSnippetDataProps & ProcessLogFormDataProps & ProcessLogFormActionProps;
+export interface ProcessLogMainCardActionProps {
+ onContextMenu: (event: React.MouseEvent<any>, process: Process) => void;
+}
+
+export type ProcessLogMainCardProps = ProcessLogMainCardDataProps
+ & ProcessLogMainCardActionProps
+ & CodeSnippetDataProps
+ & ProcessLogFormDataProps
+ & ProcessLogFormActionProps;
export const ProcessLogMainCard = withStyles(styles)(
- ({ classes, process, selectedFilter, filters, onChange, lines }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
+ ({ classes, process, selectedFilter, filters, onChange, lines, onContextMenu }: ProcessLogMainCardProps & WithStyles<CssRules>) =>
<Grid item xs={12}>
<Link to={`/processes/${process.containerRequest.uuid}`} className={classes.backLink}>
<BackIcon className={classes.backIcon} /> Back
<CardHeader
avatar={<ProcessIcon className={classes.iconHeader} />}
action={
- <div>
- <IconButton aria-label="More options">
- <Tooltip title="More options">
- <MoreOptionsIcon />
- </Tooltip>
- </IconButton>
- </div>
- }
+ <IconButton onClick={event => onContextMenu(event, process)} aria-label="More options">
+ <Tooltip title="More options">
+ <MoreOptionsIcon />
+ </Tooltip>
+ </IconButton>}
title={
<Tooltip title={process.containerRequest.name} placement="bottom-start">
<Typography noWrap variant="title" className={classes.title}>
{process.containerRequest.name}
</Typography>
- </Tooltip>
- }
+ </Tooltip>}
subheader={process.containerRequest.description} />
<CardContent>
{lines.length > 0
- ? < Grid container spacing={24} alignItems='center'>
- <Grid item xs={6}>
- <ProcessLogForm selectedFilter={selectedFilter} filters={filters} onChange={onChange} />
- </Grid>
- <Grid item xs={6} className={classes.link}>
- <Typography component='div'>
- Go to Log collection
+ ? < Grid
+ container
+ spacing={24}
+ direction='column'>
+ <Grid container item>
+ <Grid item xs={6}>
+ <ProcessLogForm selectedFilter={selectedFilter} filters={filters} onChange={onChange} />
+ </Grid>
+ <Grid item xs={6} className={classes.link}>
+ <Typography component='div'>
+ Go to Log collection
</Typography>
+ </Grid>
</Grid>
- <Grid item xs={12}>
+ <Grid item xs>
<ProcessLogCodeSnippet lines={lines} />
</Grid>
</Grid>
import { DefaultView } from '~/components/default-view/default-view';
import { ProcessIcon } from '~/components/icon/icon';
import { CodeSnippetDataProps } from '~/components/code-snippet/code-snippet';
+import { ProcessLogMainCardActionProps } from './process-log-main-card';
export type ProcessLogPanelRootDataProps = {
process?: Process;
} & ProcessLogFormDataProps & CodeSnippetDataProps;
-export type ProcessLogPanelRootActionProps = {
- onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
-} & ProcessLogFormActionProps;
+export type ProcessLogPanelRootActionProps = ProcessLogMainCardActionProps & ProcessLogFormActionProps;
export type ProcessLogPanelRootProps = ProcessLogPanelRootDataProps & ProcessLogPanelRootActionProps;
//
// SPDX-License-Identifier: AGPL-3.0
-import * as React from 'react';
import { RootState } from '~/store/store';
import { connect } from 'react-redux';
import { getProcess } from '~/store/processes/process';
import { Dispatch } from 'redux';
import { openProcessContextMenu } from '~/store/context-menu/context-menu-actions';
-import { matchProcessLogRoute } from '~/routes/routes';
import { ProcessLogPanelRootDataProps, ProcessLogPanelRootActionProps, ProcessLogPanelRoot } from './process-log-panel-root';
import { getProcessPanelLogs } from '~/store/process-logs-panel/process-logs-panel';
import { setProcessLogsPanelFilter } from '~/store/process-logs-panel/process-logs-panel-actions';
};
const mapDispatchToProps = (dispatch: Dispatch): ProcessLogPanelRootActionProps => ({
- onContextMenu: (event: React.MouseEvent<HTMLElement>) => {
- dispatch<any>(openProcessContextMenu(event));
+ onContextMenu: (event, process) => {
+ dispatch<any>(openProcessContextMenu(event, process));
},
- onChange: (filter: FilterOption) => {
+ onChange: filter => {
dispatch(setProcessLogsPanelFilter(filter.value));
}
});
}
export interface ProcessPanelRootActionProps {
- onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
onToggle: (status: string) => void;
}
export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps;
-export const ProcessPanelRoot = (props: ProcessPanelRootProps) =>
- props.process
+export const ProcessPanelRoot = ({process, ...props}: ProcessPanelRootProps) =>
+ process
? <Grid container spacing={16} alignItems="stretch">
<Grid item sm={12} md={7}>
<ProcessInformationCard
- process={props.process}
- onContextMenu={props.onContextMenu} />
+ process={process}
+ onContextMenu={event => props.onContextMenu(event, process)} />
</Grid>
<Grid item sm={12} md={5}>
<SubprocessesCard
};
const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
- onContextMenu: event => {
- dispatch<any>(openProcessContextMenu(event));
+ onContextMenu: (event, process) => {
+ dispatch<any>(openProcessContextMenu(event, process));
},
onToggle: status => {
dispatch<any>(toggleProcessPanelFilter(status));
export interface ProcessSubprocessesDataProps {
subprocesses: Array<Process>;
- onContextMenu: (event: React.MouseEvent<HTMLElement>) => void;
+ onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
}
export const ProcessSubprocesses = ({ onContextMenu, subprocesses }: ProcessSubprocessesDataProps) => {
return <Grid container spacing={16}>
{subprocesses.map(subprocess =>
<Grid item xs={12} sm={6} md={4} lg={2} key={subprocess.containerRequest.uuid}>
- <ProcessSubprocessesCard onContextMenu={onContextMenu} subprocess={subprocess} />
+ <ProcessSubprocessesCard
+ onContextMenu={event => onContextMenu(event, subprocess)}
+ subprocess={subprocess} />
</Grid>
)}
</Grid>;
import { CreateProjectDialog } from '~/views-components/dialog-forms/create-project-dialog';
import { CreateCollectionDialog } from '~/views-components/dialog-forms/create-collection-dialog';
import { CopyCollectionDialog } from '~/views-components/dialog-forms/copy-collection-dialog';
+import { CopyProcessDialog } from '~/views-components/dialog-forms/copy-process-dialog';
import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-collection-dialog';
+import { UpdateProcessDialog } from '~/views-components/dialog-forms/update-process-dialog';
import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog';
+import { MoveProcessDialog } from '~/views-components/dialog-forms/move-process-dialog';
import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-dialog';
import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog';
import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
-
import { TrashPanel } from "~/views/trash-panel/trash-panel";
-import { MainContentBar } from '../../views-components/main-content-bar/main-content-bar';
+import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
import { Grid } from '@material-ui/core';
import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
+import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
type CssRules = 'root' | 'contentWrapper' | 'content' | 'appBar';
</Grid>}
</Grid>
<ContextMenu />
- <Snackbar />
- <CreateProjectDialog />
+ <CopyCollectionDialog />
+ <CopyProcessDialog />
<CreateCollectionDialog />
- <RenameFileDialog />
- <PartialCopyCollectionDialog />
+ <CreateProjectDialog />
+ <CurrentTokenDialog />
<FileRemoveDialog />
- <CopyCollectionDialog />
<FileRemoveDialog />
- <MultipleFilesRemoveDialog />
- <UpdateCollectionDialog />
<FilesUploadCollectionDialog />
- <UpdateProjectDialog />
<MoveCollectionDialog />
+ <MoveProcessDialog />
<MoveProjectDialog />
- <CurrentTokenDialog />
+ <MultipleFilesRemoveDialog />
+ <PartialCopyCollectionDialog />
+ <ProcessCommandDialog />
+ <RenameFileDialog />
+ <Snackbar />
+ <UpdateCollectionDialog />
+ <UpdateProcessDialog />
+ <UpdateProjectDialog />
</>;
}
prop-types "^15.6.0"
react-is "^16.4.1"
-react-transition-group@^2.2.1:
+react-transition-group@2.4.0, react-transition-group@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.4.0.tgz#1d9391fabfd82e016f26fabd1eec329dbd922b5a"
dependencies: