From: Lisa Knox Date: Wed, 17 May 2023 20:53:30 +0000 (-0400) Subject: 15768: removeMany dialog good, button styling Arvados-DCO-1.1-Signed-off-by: Lisa... X-Git-Tag: 2.7.1~13^2~103 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/06e815a5ab6a9a9a118014b41f023c46237156f1 15768: removeMany dialog good, button styling Arvados-DCO-1.1-Signed-off-by: Lisa Knox --- diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx index 3ef2e3f9..508413ff 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -231,7 +231,8 @@ export const DataExplorer = withStyles(styles)( )} - {isMSToolbarVisible && } + {/* {isMSToolbarVisible && } */} + )} diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx index 628405ce..7a9d95b0 100644 --- a/src/components/data-table/data-table.tsx +++ b/src/components/data-table/data-table.tsx @@ -286,17 +286,14 @@ export const DataTable = withStyles(styles)( renderHeadCell = (column: DataColumn, index: number) => { const { name, key, renderHeader, filters, sort } = column; const { onSortToggle, onFiltersChange, classes } = this.props; + const { isSelected, checkedList } = this.state; return column.name === 'checkBoxColumn' ? (
- + - +
) : ( diff --git a/src/components/multiselectToolbar/MultiselectToolbar.tsx b/src/components/multiselectToolbar/MultiselectToolbar.tsx index 9e36289c..0338d610 100644 --- a/src/components/multiselectToolbar/MultiselectToolbar.tsx +++ b/src/components/multiselectToolbar/MultiselectToolbar.tsx @@ -10,44 +10,67 @@ import { RootState } from 'store/store'; import { Dispatch } from 'redux'; import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar'; import { TCheckedList } from 'components/data-table/data-table'; -import { openRemoveProcessDialog } from 'store/processes/processes-actions'; +import { openRemoveProcessDialog, openRemoveManyProcessesDialog } from 'store/processes/processes-actions'; import { processResourceActionSet } from '../../views-components/context-menu/action-sets/process-resource-action-set'; import { ContextMenuResource } from 'store/context-menu/context-menu-actions'; import { toggleTrashed } from 'store/trash/trash-actions'; -type CssRules = 'root' | 'button'; +type CssRules = 'root' | 'expanded' | 'button'; const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { display: 'flex', flexDirection: 'row', + justifyContent: 'start', + width: '0px', + padding: 0, + marginTop: '0.5rem', + marginLeft: '0.5rem', + overflow: 'hidden', + transition: 'width 150ms', + transitionTimingFunction: 'ease', + }, + expanded: { + transition: 'width 150ms', + transitionTimingFunction: 'ease-in', + width: '40%', }, button: { - color: theme.palette.text.primary, - // margin: '0.5rem', + backgroundColor: '#017ead', + color: 'white', + fontSize: '0.75rem', + width: 'fit-content', + margin: '2px', + padding: '1px', }, }); type MultiselectToolbarAction = { name: string; - fn: string; + action: string; }; export const defaultActions: Array = [ - // { - // name: 'copy', - // fn: (name, checkedList) => MSToolbarCopyButton(name, checkedList), - // }, + { + name: 'copy', + action: 'copySelected', + }, + { + name: 'move', + action: 'moveSelected', + }, { name: 'remove', - fn: 'REMOVE', + action: 'removeSelected', }, ]; export type MultiselectToolbarProps = { buttons: Array; + isVisible: boolean; checkedList: TCheckedList; copySelected: () => void; + moveSelected: () => void; removeSelected: (selectedList: TCheckedList) => void; }; @@ -56,17 +79,12 @@ export const MultiselectToolbar = connect( mapDispatchToProps )( withStyles(styles)((props: MultiselectToolbarProps & WithStyles) => { - console.log(props); - const actions = { - COPY: props.copySelected, - REMOVE: props.removeSelected, - }; - - const { classes, buttons, checkedList } = props; + // console.log(props); + const { classes, buttons, isVisible, checkedList } = props; return ( - + {buttons.map((btn) => ( - ))} @@ -86,7 +104,7 @@ function selectedToString(checkedList: TCheckedList) { } function selectedToArray(checkedList: TCheckedList): Array { - const arrayifiedSelectedList: Array = []; + const arrayifiedSelectedList: Array = []; for (const [key, value] of Object.entries(checkedList)) { if (value === true) { arrayifiedSelectedList.push(key); @@ -97,8 +115,10 @@ function selectedToArray(checkedList: TCheckedList): Array { function mapStateToProps(state: RootState) { // console.log(state.resources, state.multiselect.checkedList); + const { isVisible, checkedList } = state.multiselect; return { - checkedList: state.multiselect.checkedList as TCheckedList, + isVisible: isVisible, + checkedList: checkedList as TCheckedList, // selectedList: state.multiselect.checkedList.forEach(processUUID=>containerRequestUUID) }; } @@ -106,10 +126,11 @@ function mapStateToProps(state: RootState) { function mapDispatchToProps(dispatch: Dispatch) { return { copySelected: () => {}, - removeSelected: (selectedList) => removeMany(dispatch, selectedList), + moveSelected: () => {}, + removeSelected: (checkedList: TCheckedList) => removeMany(dispatch, checkedList), }; } function removeMany(dispatch: Dispatch, checkedList: TCheckedList): void { - selectedToArray(checkedList).forEach((uuid: string) => dispatch(openRemoveProcessDialog(uuid))); + dispatch(openRemoveManyProcessesDialog(selectedToArray(checkedList))); } diff --git a/src/store/processes/processes-actions.ts b/src/store/processes/processes-actions.ts index b26c2017..195e9db6 100644 --- a/src/store/processes/processes-actions.ts +++ b/src/store/processes/processes-actions.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { Dispatch } from "redux"; +import { Dispatch } from 'redux'; import { RootState } from 'store/store'; import { ServiceRepository } from 'services/services'; import { updateResources } from 'store/resources/resources-actions'; @@ -13,19 +13,20 @@ import { projectPanelActions } from 'store/project-panel/project-panel-action'; import { navigateToRunProcess } from 'store/navigation/navigation-action'; import { goToStep, runProcessPanelActions } from 'store/run-process-panel/run-process-panel-actions'; import { getResource } from 'store/resources/resources'; -import { initialize } from "redux-form"; -import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "views/run-process-panel/run-process-basic-form"; -import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from "views/run-process-panel/run-process-advanced-form"; +import { initialize } from 'redux-form'; +import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from 'views/run-process-panel/run-process-basic-form'; +import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from 'views/run-process-panel/run-process-advanced-form'; import { MOUNT_PATH_CWL_WORKFLOW, MOUNT_PATH_CWL_INPUT } from 'models/process'; -import { CommandInputParameter, getWorkflow, getWorkflowInputs, getWorkflowOutputs, WorkflowInputsData } from "models/workflow"; -import { ProjectResource } from "models/project"; -import { UserResource } from "models/user"; -import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter"; -import { ContainerResource } from "models/container"; -import { ContainerRequestResource, ContainerRequestState } from "models/container-request"; -import { FilterBuilder } from "services/api/filter-builder"; +import { CommandInputParameter, getWorkflow, getWorkflowInputs, getWorkflowOutputs, WorkflowInputsData } from 'models/workflow'; +import { ProjectResource } from 'models/project'; +import { UserResource } from 'models/user'; +import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter'; +import { ContainerResource } from 'models/container'; +import { ContainerRequestResource, ContainerRequestState } from 'models/container-request'; +import { FilterBuilder } from 'services/api/filter-builder'; -export const loadProcess = (containerRequestUuid: string) => +export const loadProcess = + (containerRequestUuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { let containerRequest: ContainerRequestResource | undefined = undefined; try { @@ -49,7 +50,7 @@ export const loadProcess = (containerRequestUuid: string) => dispatch(updateResources([container])); } catch {} - try{ + try { if (container && container.runtimeUserUuid) { const runtimeUser = await services.userService.get(container.runtimeUserUuid, false); dispatch(updateResources([runtimeUser])); @@ -61,12 +62,13 @@ export const loadProcess = (containerRequestUuid: string) => return { containerRequest }; }; -export const loadContainers = (containerUuids: string[], loadMounts: boolean = true) => +export const loadContainers = + (containerUuids: string[], loadMounts: boolean = true) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { let args: any = { filters: new FilterBuilder().addIn('uuid', containerUuids).getFilters(), limit: containerUuids.length, - }; + }; if (!loadMounts) { args.select = containerFieldsNoMounts; } @@ -77,121 +79,119 @@ export const loadContainers = (containerUuids: string[], loadMounts: boolean = t // Until the api supports unselecting fields, we need a list of all other fields to omit mounts const containerFieldsNoMounts = [ - "auth_uuid", - "command", - "container_image", - "cost", - "created_at", - "cwd", - "environment", - "etag", - "exit_code", - "finished_at", - "gateway_address", - "href", - "interactive_session_started", - "kind", - "lock_count", - "locked_by_uuid", - "log", - "modified_at", - "modified_by_client_uuid", - "modified_by_user_uuid", - "output_path", - "output_properties", - "output_storage_classes", - "output", - "owner_uuid", - "priority", - "progress", - "runtime_auth_scopes", - "runtime_constraints", - "runtime_status", - "runtime_user_uuid", - "scheduling_parameters", - "started_at", - "state", - "uuid", -] + 'auth_uuid', + 'command', + 'container_image', + 'cost', + 'created_at', + 'cwd', + 'environment', + 'etag', + 'exit_code', + 'finished_at', + 'gateway_address', + 'href', + 'interactive_session_started', + 'kind', + 'lock_count', + 'locked_by_uuid', + 'log', + 'modified_at', + 'modified_by_client_uuid', + 'modified_by_user_uuid', + 'output_path', + 'output_properties', + 'output_storage_classes', + 'output', + 'owner_uuid', + 'priority', + 'progress', + 'runtime_auth_scopes', + 'runtime_constraints', + 'runtime_status', + 'runtime_user_uuid', + 'scheduling_parameters', + 'started_at', + 'state', + 'uuid', +]; -export const cancelRunningWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { priority: 0 }); - dispatch(updateResources([process])); - if (process.containerUuid) { - const container = await services.containerService.get(process.containerUuid, false); - dispatch(updateResources([container])); - } - return process; - } catch (e) { - throw new Error('Could not cancel the process.'); +export const cancelRunningWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { priority: 0 }); + dispatch(updateResources([process])); + if (process.containerUuid) { + const container = await services.containerService.get(process.containerUuid, false); + dispatch(updateResources([container])); } - }; + return process; + } catch (e) { + throw new Error('Could not cancel the process.'); + } +}; -export const resumeOnHoldWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { priority: 500 }); - dispatch(updateResources([process])); - if (process.containerUuid) { - const container = await services.containerService.get(process.containerUuid, false); - dispatch(updateResources([container])); - } - return process; - } catch (e) { - throw new Error('Could not resume the process.'); +export const resumeOnHoldWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { priority: 500 }); + dispatch(updateResources([process])); + if (process.containerUuid) { + const container = await services.containerService.get(process.containerUuid, false); + dispatch(updateResources([container])); } - }; + return process; + } catch (e) { + throw new Error('Could not resume the process.'); + } +}; -export const startWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED }); - if (process) { - dispatch(updateResources([process])); - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process started', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); - } else { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); - } - } catch (e) { +export const startWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED }); + if (process) { + dispatch(updateResources([process])); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process started', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); + } else { dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); } - }; + } catch (e) { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); + } +}; -export const reRunProcess = (processUuid: string, workflowUuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const process = getResource(processUuid)(getState().resources); - const workflows = getState().runProcessPanel.searchWorkflows; - const workflow = workflows.find(workflow => workflow.uuid === workflowUuid); - if (workflow && process) { - const mainWf = getWorkflow(process.mounts[MOUNT_PATH_CWL_WORKFLOW]); - if (mainWf) { mainWf.inputs = getInputs(process); } - const stringifiedDefinition = JSON.stringify(process.mounts[MOUNT_PATH_CWL_WORKFLOW].content); - const newWorkflow = { ...workflow, definition: stringifiedDefinition }; +export const reRunProcess = (processUuid: string, workflowUuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const process = getResource(processUuid)(getState().resources); + const workflows = getState().runProcessPanel.searchWorkflows; + const workflow = workflows.find((workflow) => workflow.uuid === workflowUuid); + if (workflow && process) { + const mainWf = getWorkflow(process.mounts[MOUNT_PATH_CWL_WORKFLOW]); + if (mainWf) { + mainWf.inputs = getInputs(process); + } + const stringifiedDefinition = JSON.stringify(process.mounts[MOUNT_PATH_CWL_WORKFLOW].content); + const newWorkflow = { ...workflow, definition: stringifiedDefinition }; - const owner = getResource(workflow.ownerUuid)(getState().resources); - const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description, owner }; - dispatch(initialize(RUN_PROCESS_BASIC_FORM, basicInitialData)); + const owner = getResource(workflow.ownerUuid)(getState().resources); + const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description, owner }; + dispatch(initialize(RUN_PROCESS_BASIC_FORM, basicInitialData)); - const advancedInitialData: RunProcessAdvancedFormData = { - output: process.outputName, - runtime: process.schedulingParameters.max_run_time, - ram: process.runtimeConstraints.ram, - vcpus: process.runtimeConstraints.vcpus, - keep_cache_ram: process.runtimeConstraints.keep_cache_ram, - acr_container_image: process.containerImage - }; - dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedInitialData)); + const advancedInitialData: RunProcessAdvancedFormData = { + output: process.outputName, + runtime: process.schedulingParameters.max_run_time, + ram: process.runtimeConstraints.ram, + vcpus: process.runtimeConstraints.vcpus, + keep_cache_ram: process.runtimeConstraints.keep_cache_ram, + acr_container_image: process.containerImage, + }; + dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedInitialData)); - dispatch(navigateToRunProcess); - dispatch(goToStep(1)); - dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); - dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(newWorkflow)); - } else { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't re-run this process`, kind: SnackbarKind.ERROR })); - } - }; + dispatch(navigateToRunProcess); + dispatch(goToStep(1)); + dispatch(runProcessPanelActions.SET_STEP_CHANGED(true)); + dispatch(runProcessPanelActions.SET_SELECTED_WORKFLOW(newWorkflow)); + } else { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `You can't re-run this process`, kind: SnackbarKind.ERROR })); + } +}; /* * Fetches raw inputs from containerRequest mounts with fallback to properties @@ -199,34 +199,40 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) => * Returns {} if inputs not found in mounts or props */ export const getRawInputs = (data: any): WorkflowInputsData | undefined => { - if (!data) { return undefined; } + if (!data) { + return undefined; + } const mountInput = data.mounts?.[MOUNT_PATH_CWL_INPUT]?.content; const propsInput = data.properties?.cwl_input; - if (!mountInput && !propsInput) { return {}; } - return (mountInput || propsInput); -} + if (!mountInput && !propsInput) { + return {}; + } + return mountInput || propsInput; +}; export const getInputs = (data: any): CommandInputParameter[] => { // Definitions from mounts are needed so we return early if missing - if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; } - const content = getRawInputs(data) as any; + if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { + return []; + } + const content = getRawInputs(data) as any; // Only escape if content is falsy to allow displaying definitions if no inputs are present // (Don't check raw content length) - if (!content) { return []; } + if (!content) { + return []; + } const inputs = getWorkflowInputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content); - return inputs ? inputs.map( - (it: any) => ( - { - type: it.type, - id: it.id, - label: it.label, - default: content[it.id], - value: content[it.id.split('/').pop()] || [], - doc: it.doc - } - ) - ) : []; + return inputs + ? inputs.map((it: any) => ({ + type: it.type, + id: it.id, + label: it.label, + default: content[it.id], + value: content[it.id.split('/').pop()] || [], + doc: it.doc, + })) + : []; }; /* @@ -234,65 +240,81 @@ export const getInputs = (data: any): CommandInputParameter[] => { * Assumes containerRequest is loaded */ export const getRawOutputs = (data: any): CommandInputParameter[] | undefined => { - if (!data || !data.properties || !data.properties.cwl_output) { return undefined; } - return (data.properties.cwl_output); -} + if (!data || !data.properties || !data.properties.cwl_output) { + return undefined; + } + return data.properties.cwl_output; +}; export type InputCollectionMount = { path: string; pdh: string; -} +}; export const getInputCollectionMounts = (data: any): InputCollectionMount[] => { - if (!data || !data.mounts) { return []; } + if (!data || !data.mounts) { + return []; + } return Object.keys(data.mounts) - .map(key => ({ + .map((key) => ({ ...data.mounts[key], path: key, })) - .filter(mount => mount.kind === 'collection' && - mount.portable_data_hash && - mount.path) - .map(mount => ({ + .filter((mount) => mount.kind === 'collection' && mount.portable_data_hash && mount.path) + .map((mount) => ({ path: mount.path, pdh: mount.portable_data_hash, })); }; export const getOutputParameters = (data: any): CommandOutputParameter[] => { - if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; } + if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { + return []; + } const outputs = getWorkflowOutputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content); - return outputs ? outputs.map( - (it: any) => ( - { - type: it.type, - id: it.id, - label: it.label, - doc: it.doc - } - ) - ) : []; + return outputs + ? outputs.map((it: any) => ({ + type: it.type, + id: it.id, + label: it.label, + doc: it.doc, + })) + : []; }; -export const openRemoveProcessDialog = (uuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(dialogActions.OPEN_DIALOG({ +export const openRemoveProcessDialog = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch( + dialogActions.OPEN_DIALOG({ id: REMOVE_PROCESS_DIALOG, data: { title: 'Remove process permanently', text: 'Are you sure you want to remove this process?', confirmButtonLabel: 'Remove', - uuid - } - })); - }; + uuid, + }, + }) + ); +}; + +export const openRemoveManyProcessesDialog = (list: Array) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch( + dialogActions.OPEN_DIALOG({ + id: REMOVE_PROCESS_DIALOG, + data: { + title: 'Remove processes permanently', + text: `Are you sure you want to remove all ${list.length} processes?`, + confirmButtonLabel: 'Remove', + list, + }, + }) + ); +}; export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog'; -export const removeProcessPermanently = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO })); - await services.containerRequestService.delete(uuid); - dispatch(projectPanelActions.REQUEST_ITEMS()); - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); - }; +export const removeProcessPermanently = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO })); + await services.containerRequestService.delete(uuid); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); +}; diff --git a/src/views-components/process-remove-many-dialog/process-remove-many-dialog.tsx b/src/views-components/process-remove-many-dialog/process-remove-many-dialog.tsx new file mode 100644 index 00000000..271831ec --- /dev/null +++ b/src/views-components/process-remove-many-dialog/process-remove-many-dialog.tsx @@ -0,0 +1,18 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch, compose } from 'redux'; +import { connect } from 'react-redux'; +import { ConfirmationDialog } from 'components/confirmation-dialog/confirmation-dialog'; +import { withDialog, WithDialogProps } from 'store/dialog/with-dialog'; +import { removeProcessPermanently, REMOVE_PROCESS_DIALOG } from 'store/processes/processes-actions'; + +const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps) => ({ + onConfirm: () => { + props.closeDialog(); + dispatch(removeProcessPermanently(props.data.uuid)); + }, +}); + +export const RemoveManyProcessesDialog = compose(withDialog(REMOVE_PROCESS_DIALOG), connect(null, mapDispatchToProps))(ConfirmationDialog); diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index d549c529..7be3c8ba 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -4,12 +4,12 @@ import React from 'react'; import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles'; -import { Route, Switch } from "react-router"; -import { ProjectPanel } from "views/project-panel/project-panel"; +import { Route, Switch } from 'react-router'; +import { ProjectPanel } from 'views/project-panel/project-panel'; import { DetailsPanel } from 'views-components/details-panel/details-panel'; import { ArvadosTheme } from 'common/custom-theme'; -import { ContextMenu } from "views-components/context-menu/context-menu"; -import { FavoritePanel } from "../favorite-panel/favorite-panel"; +import { ContextMenu } from 'views-components/context-menu/context-menu'; +import { FavoritePanel } from '../favorite-panel/favorite-panel'; import { TokenDialog } from 'views-components/token-dialog/token-dialog'; import { RichTextEditorDialog } from 'views-components/rich-text-editor-dialog/rich-text-editor-dialog'; import { Snackbar } from 'views-components/snackbar/snackbar'; @@ -34,9 +34,10 @@ import { MoveCollectionDialog } from 'views-components/dialog-forms/move-collect import { FilesUploadCollectionDialog } from 'views-components/dialog-forms/files-upload-collection-dialog'; import { PartialCopyCollectionDialog } from 'views-components/dialog-forms/partial-copy-collection-dialog'; import { RemoveProcessDialog } from 'views-components/process-remove-dialog/process-remove-dialog'; +import { RemoveManyProcessesDialog } from 'views-components/process-remove-many-dialog/process-remove-many-dialog'; import { MainContentBar } from 'views-components/main-content-bar/main-content-bar'; import { Grid } from '@material-ui/core'; -import { TrashPanel } from "views/trash-panel/trash-panel"; +import { TrashPanel } from 'views/trash-panel/trash-panel'; import { SharedWithMePanel } from 'views/shared-with-me-panel/shared-with-me-panel'; import { RunProcessPanel } from 'views/run-process-panel/run-process-panel'; import SplitterLayout from 'react-splitter-layout'; @@ -45,7 +46,7 @@ import { RegisteredWorkflowPanel } from 'views/workflow-panel/registered-workflo import { SearchResultsPanel } from 'views/search-results-panel/search-results-panel'; import { SshKeyPanel } from 'views/ssh-key-panel/ssh-key-panel'; import { SshKeyAdminPanel } from 'views/ssh-key-panel/ssh-key-admin-panel'; -import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel"; +import { SiteManagerPanel } from 'views/site-manager-panel/site-manager-panel'; import { UserProfilePanel } from 'views/user-profile-panel/user-profile-panel'; import { SharingDialog } from 'views-components/sharing-dialog/sharing-dialog'; import { NotFoundDialog } from 'views-components/not-found-dialog/not-found-dialog'; @@ -100,7 +101,7 @@ import { RestoreCollectionVersionDialog } from 'views-components/collections-dia import { WebDavS3InfoDialog } from 'views-components/webdav-s3-dialog/webdav-s3-dialog'; import { pluginConfig } from 'plugins'; import { ElementListReducer } from 'common/plugintypes'; -import { COLLAPSE_ICON_SIZE } from 'views-components/side-panel-toggle/side-panel-toggle' +import { COLLAPSE_ICON_SIZE } from 'views-components/side-panel-toggle/side-panel-toggle'; import { Banner } from 'views-components/baner/banner'; type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content'; @@ -108,10 +109,10 @@ type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapp const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ root: { paddingTop: theme.spacing.unit * 7, - background: theme.palette.background.default + background: theme.palette.background.default, }, container: { - position: 'relative' + position: 'relative', }, splitter: { '& > .layout-splitter': { @@ -119,16 +120,16 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, '& > .layout-splitter-disabled': { pointerEvents: 'none', - cursor: 'pointer' - } + cursor: 'pointer', + }, }, asidePanel: { paddingTop: theme.spacing.unit, - height: '100%' + height: '100%', }, contentWrapper: { paddingTop: theme.spacing.unit, - minWidth: 0 + minWidth: 0, }, content: { minWidth: 0, @@ -137,7 +138,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ // Reserve vertical space for app bar + MainContentBar minHeight: `calc(100vh - ${theme.spacing.unit * 16}px)`, display: 'flex', - } + }, }); interface WorkbenchDataProps { @@ -158,72 +159,78 @@ const getSplitterInitialSize = () => { const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString()); -let routes = <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; +let routes = ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); -const reduceRoutesFn: (a: React.ReactElement[], - b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a); +const reduceRoutesFn: (a: React.ReactElement[], b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a); routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children))); const applyCollapsedState = (isCollapsed) => { - const rightPanel: Element = document.getElementsByClassName('layout-pane')[1] - const totalWidth: number = document.getElementsByClassName('splitter-layout')[0]?.clientWidth - const rightPanelExpandedWidth = ((totalWidth - COLLAPSE_ICON_SIZE)) / (totalWidth / 100) + const rightPanel: Element = document.getElementsByClassName('layout-pane')[1]; + const totalWidth: number = document.getElementsByClassName('splitter-layout')[0]?.clientWidth; + const rightPanelExpandedWidth = (totalWidth - COLLAPSE_ICON_SIZE) / (totalWidth / 100); if (rightPanel) { - rightPanel.setAttribute('style', `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`) + rightPanel.setAttribute('style', `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`); } - const splitter = document.getElementsByClassName('layout-splitter')[0] - isCollapsed ? splitter?.classList.add('layout-splitter-disabled') : splitter?.classList.remove('layout-splitter-disabled') - -} - -export const WorkbenchPanel = - withStyles(styles)((props: WorkbenchPanelProps) => { + const splitter = document.getElementsByClassName('layout-splitter')[0]; + isCollapsed ? splitter?.classList.add('layout-splitter-disabled') : splitter?.classList.remove('layout-splitter-disabled'); +}; - //panel size will not scale automatically on window resize, so we do it manually - window.addEventListener('resize', () => applyCollapsedState(props.sidePanelIsCollapsed)) - applyCollapsedState(props.sidePanelIsCollapsed) +export const WorkbenchPanel = withStyles(styles)((props: WorkbenchPanelProps) => { + //panel size will not scale automatically on window resize, so we do it manually + window.addEventListener('resize', () => applyCollapsedState(props.sidePanelIsCollapsed)); + applyCollapsedState(props.sidePanelIsCollapsed); - return + return ( + {props.sessionIdleTimeout > 0 && } - - {props.isUserActive && props.isNotLinking && - - } - + + {props.isUserActive && props.isNotLinking && ( + + + + )} + {props.isNotLinking && } @@ -274,6 +281,7 @@ export const WorkbenchPanel = + @@ -299,5 +307,5 @@ export const WorkbenchPanel = {React.createElement(React.Fragment, null, pluginConfig.dialogs)} - } ); +});