From 1c0840c72a70fc8f71505e3d0510937698c8c1f8 Mon Sep 17 00:00:00 2001 From: Florent MILLOT <75525996+flomillot@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:29:47 +0100 Subject: [PATCH 1/4] Add connected, ratedS, marginalCost, plannedOutageRate, forcedOutageRate, and vlId fields to the expert filter type and update their translations. (#329) Signed-off-by: Florent MILLOT --- .../filter/expert/expert-filter-constants.ts | 41 +++++++++++++++++++ .../filter/expert/expert-filter.type.ts | 6 +++ src/translations/en.json | 12 ++++-- src/translations/fr.json | 12 ++++-- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/components/dialogs/filter/expert/expert-filter-constants.ts b/src/components/dialogs/filter/expert/expert-filter-constants.ts index 85e5e7fb7..5764ac995 100644 --- a/src/components/dialogs/filter/expert/expert-filter-constants.ts +++ b/src/components/dialogs/filter/expert/expert-filter-constants.ts @@ -168,6 +168,41 @@ export const FIELDS_OPTIONS = { dataType: DataType.NUMBER, inputType: 'number', }, + CONNECTED: { + name: FieldType.CONNECTED, + label: 'connected', + dataType: DataType.BOOLEAN, + valueEditorType: 'switch', + }, + RATED_S: { + name: FieldType.RATED_S, + label: 'ratedS', + dataType: DataType.NUMBER, + inputType: 'number', + }, + MARGINAL_COST: { + name: FieldType.MARGINAL_COST, + label: 'marginalCost', + dataType: DataType.NUMBER, + inputType: 'number', + }, + PLANNED_OUTAGE_RATE: { + name: FieldType.PLANNED_OUTAGE_RATE, + label: 'plannedOutageRate', + dataType: DataType.NUMBER, + inputType: 'number', + }, + FORCED_OUTAGE_RATE: { + name: FieldType.FORCED_OUTAGE_RATE, + label: 'forcedOutageRate', + dataType: DataType.NUMBER, + inputType: 'number', + }, + VOLTAGE_LEVEL_ID: { + name: FieldType.VOLTAGE_LEVEL_ID, + label: 'vlId', + dataType: DataType.STRING, + }, }; export const fields: Record = { @@ -184,6 +219,12 @@ export const fields: Record = { FIELDS_OPTIONS.COUNTRY, FIELDS_OPTIONS.VOLTAGE_REGULATOR_ON, FIELDS_OPTIONS.PLANNED_ACTIVE_POWER_SET_POINT, + FIELDS_OPTIONS.CONNECTED, + FIELDS_OPTIONS.RATED_S, + FIELDS_OPTIONS.MARGINAL_COST, + FIELDS_OPTIONS.PLANNED_OUTAGE_RATE, + FIELDS_OPTIONS.FORCED_OUTAGE_RATE, + FIELDS_OPTIONS.VOLTAGE_LEVEL_ID, ], LOAD: [FIELDS_OPTIONS.ID], }; diff --git a/src/components/dialogs/filter/expert/expert-filter.type.ts b/src/components/dialogs/filter/expert/expert-filter.type.ts index 332b3e2ba..c86f4397f 100644 --- a/src/components/dialogs/filter/expert/expert-filter.type.ts +++ b/src/components/dialogs/filter/expert/expert-filter.type.ts @@ -39,6 +39,12 @@ export enum FieldType { COUNTRY = 'COUNTRY', VOLTAGE_REGULATOR_ON = 'VOLTAGE_REGULATOR_ON', PLANNED_ACTIVE_POWER_SET_POINT = 'PLANNED_ACTIVE_POWER_SET_POINT', + CONNECTED = 'CONNECTED', + RATED_S = 'RATED_S', + MARGINAL_COST = 'MARGINAL_COST', + PLANNED_OUTAGE_RATE = 'PLANNED_OUTAGE_RATE', + FORCED_OUTAGE_RATE = 'FORCED_OUTAGE_RATE', + VOLTAGE_LEVEL_ID = 'VOLTAGE_LEVEL_ID', } export enum DataType { diff --git a/src/translations/en.json b/src/translations/en.json index 894674ed1..6bfc04ea3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -250,13 +250,19 @@ "name": "Name", "minP": "Minimum active power", "maxP": "Maximum active power", - "targetP": "Active power", - "targetV": "Voltage", - "targetQ": "Reactive power", + "targetP": "Active power target", + "targetV": "Voltage target", + "targetQ": "Reactive power target", "energySource": "Energy source", "country": "Country", "voltageRegulatorOn": "Voltage regulation", "PlannedActivePowerSetPoint": "Planning active power set point", + "connected": "Connected", + "ratedS": "Rated nominal power", + "marginalCost": "Cost", + "plannedOutageRate": "Planning outage rate", + "forcedOutageRate": "Forced outage rate", + "vlId": "Voltage level ID", "downloadCases": "Download Case(s)", "descriptionModificationError": "An error when modifying the description", diff --git a/src/translations/fr.json b/src/translations/fr.json index b13a90d90..4752d37f6 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -265,13 +265,19 @@ "name": "Nom", "minP": "Puissance active min", "maxP": "Puissance active max", - "targetP": "Puissance active", - "targetV": "Tension", - "targetQ": "Puissance réactive", + "targetP": "Consigne puissance active", + "targetV": "Consigne tension", + "targetQ": "Consigne puissance réactive", "energySource": "Source d'énergie", "country": "Pays", "voltageRegulatorOn": "Réglage de tension", "PlannedActivePowerSetPoint": "Puissance imposée", + "connected": "Connecté", + "ratedS": "Puissance nominale", + "marginalCost": "Coût", + "plannedOutageRate": "Indisponibilité programmée", + "forcedOutageRate": "Indisponibilité fortuite", + "vlId": "ID poste", "downloadCases": "Télécharger la(les) situation(s)", "descriptionModificationError": "Erreur lors de la modification de la description", From 393d8293398769a57306383083d3c2ed89aa8a67 Mon Sep 17 00:00:00 2001 From: YenguiSeddik <101111441+YenguiSeddik@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:21:19 +0100 Subject: [PATCH 2/4] Fix description field (#325) Signed-off-by: Seddik Yengui --- .../create-case-dialog-utils.ts | 2 +- .../create-case-dialog/create-case-dialog.tsx | 11 +-- .../create-study-dialog-utils.js | 2 +- .../create-study-dialog.js | 12 +-- .../description-input.tsx | 81 +++++++++++++++++++ .../description-modification-dialogue.tsx | 15 ++-- src/components/directory-content.js | 23 ++++-- src/translations/en.json | 2 +- src/translations/fr.json | 2 +- src/utils/rest-api.js | 2 +- 10 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 src/components/dialogs/description-modification/description-input.tsx diff --git a/src/components/dialogs/create-case-dialog/create-case-dialog-utils.ts b/src/components/dialogs/create-case-dialog/create-case-dialog-utils.ts index 89889273c..163ab60ef 100644 --- a/src/components/dialogs/create-case-dialog/create-case-dialog-utils.ts +++ b/src/components/dialogs/create-case-dialog/create-case-dialog-utils.ts @@ -9,6 +9,6 @@ export const getCreateCaseDialogFormValidationDefaultValues = () => ({ export const createCaseDialogFormValidationSchema = yup.object().shape({ [CASE_NAME]: yup.string().trim().required('nameEmpty'), - [DESCRIPTION]: yup.string(), + [DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'), [CASE_FILE]: yup.mixed().nullable().required(), }); diff --git a/src/components/dialogs/create-case-dialog/create-case-dialog.tsx b/src/components/dialogs/create-case-dialog/create-case-dialog.tsx index 3aef6a1b7..c01500d91 100644 --- a/src/components/dialogs/create-case-dialog/create-case-dialog.tsx +++ b/src/components/dialogs/create-case-dialog/create-case-dialog.tsx @@ -20,7 +20,7 @@ import { ElementType } from '../../../utils/elementType'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { useForm } from 'react-hook-form'; import { CASE_FILE, CASE_NAME, DESCRIPTION } from '../../utils/field-constants'; -import { ErrorInput, TextInput, FieldErrorAlert } from '@gridsuite/commons-ui'; +import { ErrorInput, FieldErrorAlert } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import CustomMuiDialog from '../commons/custom-mui-dialog/custom-mui-dialog'; import { @@ -29,6 +29,7 @@ import { } from './create-case-dialog-utils'; import { ReduxState } from '../../../redux/reducer.type'; import PrefilledNameInput from '../commons/prefilled-name-input'; +import DescriptionInput from '../description-modification/description-input'; interface IFormData { [CASE_NAME]: string; @@ -126,13 +127,7 @@ const CreateCaseDialog: React.FunctionComponent = ({ /> - + diff --git a/src/components/dialogs/create-study-dialog/create-study-dialog-utils.js b/src/components/dialogs/create-study-dialog/create-study-dialog-utils.js index 579093fc6..63b704b69 100644 --- a/src/components/dialogs/create-study-dialog/create-study-dialog-utils.js +++ b/src/components/dialogs/create-study-dialog/create-study-dialog-utils.js @@ -29,7 +29,7 @@ export const getCreateStudyDialogFormDefaultValues = ({ export const createStudyDialogFormValidationSchema = yup.object().shape({ [STUDY_NAME]: yup.string().trim().required('nameEmpty'), [FORMATTED_CASE_PARAMETERS]: yup.mixed().required(), - [DESCRIPTION]: yup.string(), + [DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'), [CURRENT_PARAMETERS]: yup.mixed().required(), [CASE_UUID]: yup.string().required(), [CASE_FILE]: yup.mixed().nullable().required(), diff --git a/src/components/dialogs/create-study-dialog/create-study-dialog.js b/src/components/dialogs/create-study-dialog/create-study-dialog.js index e59871009..ce1cf80f0 100644 --- a/src/components/dialogs/create-study-dialog/create-study-dialog.js +++ b/src/components/dialogs/create-study-dialog/create-study-dialog.js @@ -39,14 +39,14 @@ import { CASE_NAME, CASE_UUID, CURRENT_PARAMETERS, - DESCRIPTION, FORMATTED_CASE_PARAMETERS, STUDY_NAME, } from '../../utils/field-constants'; import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import CustomMuiDialog from '../commons/custom-mui-dialog/custom-mui-dialog'; -import { ErrorInput, FieldErrorAlert, TextInput } from '@gridsuite/commons-ui'; +import { ErrorInput, FieldErrorAlert } from '@gridsuite/commons-ui'; import PrefilledNameInput from '../commons/prefilled-name-input'; +import DescriptionInput from '../description-modification/description-input'; const STRING_LIST = 'STRING_LIST'; @@ -259,13 +259,7 @@ const CreateStudyDialog = ({ open, onClose, providedExistingCase }) => { /> - + {providedExistingCase ? ( diff --git a/src/components/dialogs/description-modification/description-input.tsx b/src/components/dialogs/description-modification/description-input.tsx new file mode 100644 index 000000000..f66a7cd3d --- /dev/null +++ b/src/components/dialogs/description-modification/description-input.tsx @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { FunctionComponent } from 'react'; +import { DESCRIPTION } from '../../utils/field-constants'; +import { InputAdornment, SxProps } from '@mui/material'; +import { TextInput } from '@gridsuite/commons-ui'; +import React, { useMemo } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; + +interface IDescriptionInput { + maxCharactersNumber?: number; + rows?: number; + minRows?: number; + maxRows?: number; + sx?: SxProps; +} + +const DescriptionInput: FunctionComponent = ({ + maxCharactersNumber, + rows, + minRows, + maxRows, + sx, +}) => { + const { control } = useFormContext(); + const descriptionWatch = useWatch({ + name: `${DESCRIPTION}`, + control, + }); + + const maxCounter = maxCharactersNumber ?? 500; + const isOverTheLimit = descriptionWatch?.length > maxCounter; + const descriptionCounter = useMemo(() => { + const descriptionLength = descriptionWatch?.length ?? 0; + return descriptionLength + '/' + maxCounter; + }, [descriptionWatch, maxCounter]); + + const formProps = { + size: 'medium', + multiline: true, + InputProps: { + endAdornment: ( + +
+ {descriptionCounter} +
+
+ ), + }, + ...(minRows && { minRows: minRows }), + ...(rows && { rows: rows }), + ...(maxRows && { maxRows: maxRows }), + ...(sx && { sx: sx }), + }; + return ( + + ); +}; + +export default DescriptionInput; diff --git a/src/components/dialogs/description-modification/description-modification-dialogue.tsx b/src/components/dialogs/description-modification/description-modification-dialogue.tsx index 2c763bac8..3b1d26538 100644 --- a/src/components/dialogs/description-modification/description-modification-dialogue.tsx +++ b/src/components/dialogs/description-modification/description-modification-dialogue.tsx @@ -11,9 +11,10 @@ import { DESCRIPTION } from '../../utils/field-constants'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { updateElement } from '../../../utils/rest-api'; -import { TextInput, useSnackMessage } from '@gridsuite/commons-ui'; +import { useSnackMessage } from '@gridsuite/commons-ui'; import CustomMuiDialog from '../commons/custom-mui-dialog/custom-mui-dialog'; import React from 'react'; +import DescriptionInput from './description-input'; interface IDescriptionModificationDialogue { elementUuid: string; @@ -23,7 +24,7 @@ interface IDescriptionModificationDialogue { } const schema = yup.object().shape({ - [DESCRIPTION]: yup.string().nullable(), + [DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'), }); const DescriptionModificationDialogue: FunctionComponent< @@ -70,14 +71,10 @@ const DescriptionModificationDialogue: FunctionComponent< onSave={onSubmit} formSchema={schema} formMethods={methods} - titleId={'descriptionModificationDialog'} + titleId={'description'} + removeOptional={true} > - + ); }; diff --git a/src/components/directory-content.js b/src/components/directory-content.js index 792382b9e..36339a71b 100644 --- a/src/components/directory-content.js +++ b/src/components/directory-content.js @@ -17,8 +17,7 @@ import Tooltip from '@mui/material/Tooltip'; import CircularProgress from '@mui/material/CircularProgress'; import SettingsIcon from '@mui/icons-material/Settings'; import FolderOpenRoundedIcon from '@mui/icons-material/FolderOpenRounded'; -import StickyNote2Icon from '@mui/icons-material/StickyNote2'; -import StickyNote2IconOutlined from '@mui/icons-material/StickyNote2Outlined'; +import StickyNote2OutlinedIcon from '@mui/icons-material/StickyNote2Outlined'; import VirtualizedTable from './virtualized-table'; import { @@ -43,6 +42,7 @@ import PhotoIcon from '@mui/icons-material/Photo'; import PhotoLibraryIcon from '@mui/icons-material/PhotoLibrary'; import ArticleIcon from '@mui/icons-material/Article'; import OfflineBoltIcon from '@mui/icons-material/OfflineBolt'; +import CreateIcon from '@mui/icons-material/Create'; import ExplicitNamingFilterEditionDialog from './dialogs/filter/explicit-naming/explicit-naming-filter-edition-dialog'; import CriteriaBasedEditionDialog from './dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog'; import ExplicitNamingEditionDialog from './dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog'; @@ -507,26 +507,35 @@ const DirectoryContent = () => { (e) => e.elementUuid === cellData.rowData.elementUuid ); - const handleClick = (e) => { + const description = element.description; + const descriptionLines = description?.split('\n'); + if (descriptionLines?.length > 3) { + descriptionLines[2] = '...'; + } + const tooltip = descriptionLines?.join('\n'); + + const handleDescriptionIconClick = (e) => { setActiveElement(element); setOpenDescModificationDialog(true); e.stopPropagation(); }; - const icon = element.description ? ( + const icon = description ? ( } placement="right" > - + ) : ( - + ); return ( <> diff --git a/src/translations/en.json b/src/translations/en.json index 6bfc04ea3..8c7e1e0a0 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -266,5 +266,5 @@ "downloadCases": "Download Case(s)", "descriptionModificationError": "An error when modifying the description", - "descriptionModificationDialog": "Description modification" + "descriptionLimitError": "Description exceeds character limit" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 4752d37f6..e11e0d922 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -281,5 +281,5 @@ "downloadCases": "Télécharger la(les) situation(s)", "descriptionModificationError": "Erreur lors de la modification de la description", - "descriptionModificationDialog": "Modification de la description" + "descriptionLimitError": "La description dépasse la limite de caractères" } diff --git a/src/utils/rest-api.js b/src/utils/rest-api.js index efc86d91d..3c3cea8a9 100644 --- a/src/utils/rest-api.js +++ b/src/utils/rest-api.js @@ -464,7 +464,7 @@ export function duplicateStudy( export function createCase({ name, description, file, parentDirectoryUuid }) { console.info('Creating a new case...'); let urlSearchParams = new URLSearchParams(); - urlSearchParams.append('description', encodeURIComponent(description)); + urlSearchParams.append('description', description); urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); const url = From 0b464741fde6f86a241d68b113d7a456006fff92 Mon Sep 17 00:00:00 2001 From: Charly B <47325354+EstherDarkish@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:07:52 +0100 Subject: [PATCH 3/4] Fixes the contextual selection of elements. (#327) * Fixes the contextual selection of elements. Zimbra works like Google and others and as such, contextualMixPolicies.ZIMBRA is removed. --------- Signed-off-by: BOUTIER Charly --- src/components/directory-content.js | 118 ++++++++++-------- .../menus/content-contextual-menu.js | 1 + 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/src/components/directory-content.js b/src/components/directory-content.js index 36339a71b..6e91b74dd 100644 --- a/src/components/directory-content.js +++ b/src/components/directory-content.js @@ -282,6 +282,15 @@ const DirectoryContent = () => { }; /* User interactions */ + const contextualMixPolicies = useMemo( + () => ({ + BIG: 'GoogleMicrosoft', // if !selectedUuids.has(selected.Uuid) deselects selectedUuids + ALL: 'All', // union of activeElement.Uuid and selectedUuids (currently implemented) + }), + [] + ); + const contextualMixPolicy = contextualMixPolicies.ALL; + const onContextMenu = useCallback( (event) => { const element = currentChildren.find( @@ -295,6 +304,28 @@ const DirectoryContent = () => { if (element && element.uploading !== null) { if (element.type !== 'DIRECTORY') { setActiveElement(element); + + if (contextualMixPolicy === contextualMixPolicies.BIG) { + // If some elements were already selected and the active element is not in them, we deselect the already selected elements. + if ( + selectedUuids?.size && + element?.elementUuid && + !selectedUuids.has(element.elementUuid) + ) { + setSelectedUuids(new Set()); + } + } else { + // If some elements were already selected, we add the active element to the selected list if not already in it. + if ( + selectedUuids?.size && + element?.elementUuid && + !selectedUuids.has(element.elementUuid) + ) { + let updatedSelectedUuids = new Set(selectedUuids); + updatedSelectedUuids.add(element.elementUuid); + setSelectedUuids(updatedSelectedUuids); + } + } } setMousePosition({ mouseX: event.event.clientX + constants.HORIZONTAL_SHIFT, @@ -309,7 +340,14 @@ const DirectoryContent = () => { handleOpenDirectoryMenu(event); } }, - [currentChildren, dispatch, selectedDirectory] + [ + currentChildren, + dispatch, + selectedDirectory, + selectedUuids, + contextualMixPolicies, + contextualMixPolicy, + ] ); const abbreviationFromUserName = (name) => { @@ -728,60 +766,36 @@ const DirectoryContent = () => { setSelectedUuids(new Set()); }, [handleError, currentChildren, currentChildrenRef]); - const contextualMixPolicies = { - BIG: 'GoogleMicrosoft', // if !selectedUuids.has(selected.Uuid) deselects selectedUuids - ZIMBRA: 'Zimbra', // if !selectedUuids.has(selected.Uuid) just use activeElement - ALL: 'All', // union of activeElement.Uuid and selectedUuids (actually implemented) - }; - let contextualMixPolicy = contextualMixPolicies.ALL; - - const getSelectedChildren = (mayChange = false) => { - let acc = []; - let ctxtUuid = activeElement ? activeElement.elementUuid : null; - if (activeElement) { - acc.push( - Object.assign( - { - subtype: - childrenMetadata[activeElement.elementUuid] - ?.subtype, - }, - activeElement - ) - ); - } + const getSelectedChildren = () => { + let selectedChildren = []; + if (currentChildren?.length > 0) { + // Adds the previously selected elements + if (selectedUuids?.size) { + selectedChildren = currentChildren + .filter( + (child) => + selectedUuids.has(child.elementUuid) && + child.elementUuid !== activeElement?.elementUuid + ) + .map((child) => { + return { + subtype: + childrenMetadata[child.elementUuid]?.subtype, + ...child, + }; + }); + } - if (selectedUuids && currentChildren) { - if ( - contextualMixPolicy === contextualMixPolicies.ALL || - ctxtUuid === null || - selectedUuids.has(ctxtUuid) - ) { - acc = acc.concat( - currentChildren - .filter( - (child) => - selectedUuids.has(child.elementUuid) && - child.elementUuid !== activeElement?.elementUuid - ) - .map((child2) => { - return Object.assign( - { - subtype: - childrenMetadata[child2.elementUuid], - }, - child2 - ); - }) - ); - } else if ( - mayChange && - contextualMixPolicy === contextualMixPolicies.BIG - ) { - setSelectedUuids(null); + // Adds the active element + if (activeElement) { + selectedChildren.push({ + ...activeElement, + subtype: + childrenMetadata[activeElement.elementUuid]?.subtype, + }); } } - return [...new Set(acc)]; + return [...new Set(selectedChildren)]; }; const rows = useMemo( diff --git a/src/components/menus/content-contextual-menu.js b/src/components/menus/content-contextual-menu.js index dce8c73a0..d2d244d73 100644 --- a/src/components/menus/content-contextual-menu.js +++ b/src/components/menus/content-contextual-menu.js @@ -586,6 +586,7 @@ const ContentContextualMenu = (props) => { icon: , }); } + if (allowsDownloadCase()) { // is export allowed menuItems.push({ From ff2be1da4556e981eede7b1ebd6f01f3095a5878 Mon Sep 17 00:00:00 2001 From: dbraquart <107846716+dbraquart@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:11:25 +0100 Subject: [PATCH 4/4] Hide some contextual menu items while creating case or study (#318) Signed-off-by: David BRAQUART --- src/components/directory-content.js | 14 ++++++++- .../menus/content-contextual-menu.js | 31 ++++++++++--------- src/components/toolbars/content-toolbar.js | 22 +++++++++---- src/translations/en.json | 2 +- src/translations/fr.json | 2 +- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/components/directory-content.js b/src/components/directory-content.js index 6e91b74dd..b8b9e124e 100644 --- a/src/components/directory-content.js +++ b/src/components/directory-content.js @@ -303,7 +303,12 @@ const DirectoryContent = () => { if (element && element.uploading !== null) { if (element.type !== 'DIRECTORY') { - setActiveElement(element); + setActiveElement({ + hasMetadata: + childrenMetadata[event.rowData.elementUuid] !== + undefined, + ...element, + }); if (contextualMixPolicy === contextualMixPolicies.BIG) { // If some elements were already selected and the active element is not in them, we deselect the already selected elements. @@ -347,6 +352,7 @@ const DirectoryContent = () => { selectedUuids, contextualMixPolicies, contextualMixPolicy, + childrenMetadata, ] ); @@ -781,6 +787,9 @@ const DirectoryContent = () => { return { subtype: childrenMetadata[child.elementUuid]?.subtype, + hasMetadata: + childrenMetadata[child.elementUuid] !== + undefined, ...child, }; }); @@ -792,6 +801,9 @@ const DirectoryContent = () => { ...activeElement, subtype: childrenMetadata[activeElement.elementUuid]?.subtype, + hasMetadata: + childrenMetadata[activeElement.elementUuid] !== + undefined, }); } } diff --git a/src/components/menus/content-contextual-menu.js b/src/components/menus/content-contextual-menu.js index d2d244d73..b5ff2e54a 100644 --- a/src/components/menus/content-contextual-menu.js +++ b/src/components/menus/content-contextual-menu.js @@ -422,8 +422,8 @@ const ContentContextualMenu = (props) => { false ); - const isNotUploadingElement = useCallback(() => { - return selectedElements.every((el) => !el.uploading); + const noCreationInProgress = useCallback(() => { + return selectedElements.every((el) => el.hasMetadata); }, [selectedElements]); // Allowance @@ -434,14 +434,14 @@ const ContentContextualMenu = (props) => { }, [selectedElements, userId]); const allowsDelete = useCallback(() => { - return isUserAllowed() && isNotUploadingElement(); - }, [isUserAllowed, isNotUploadingElement]); + return isUserAllowed() && noCreationInProgress(); + }, [isUserAllowed, noCreationInProgress]); const allowsRename = useCallback(() => { return ( selectedElements.length === 1 && isUserAllowed() && - !selectedElements[0].uploading + selectedElements[0].hasMetadata ); }, [isUserAllowed, selectedElements]); @@ -449,21 +449,22 @@ const ContentContextualMenu = (props) => { return ( selectedElements.every( (element) => - element.type !== ElementType.DIRECTORY && !element.uploading + element.type !== ElementType.DIRECTORY && + element.hasMetadata ) && isUserAllowed() ); }, [isUserAllowed, selectedElements]); const allowsDuplicate = useCallback(() => { return ( + selectedElements[0].hasMetadata && selectedElements.length === 1 && (selectedElements[0].type === ElementType.CASE || selectedElements[0].type === ElementType.STUDY || selectedElements[0].type === ElementType.CONTINGENCY_LIST || selectedElements[0].type === ElementType.FILTER || selectedElements[0].type === - ElementType.VOLTAGE_INIT_PARAMETERS) && - !selectedElements[0].uploading + ElementType.VOLTAGE_INIT_PARAMETERS) ); }, [selectedElements]); @@ -471,7 +472,7 @@ const ContentContextualMenu = (props) => { return ( selectedElements.length === 1 && selectedElements[0].type === ElementType.CASE && - !selectedElements[0].uploading + selectedElements[0].hasMetadata ); }, [selectedElements]); @@ -496,10 +497,12 @@ const ContentContextualMenu = (props) => { const allowsDownloadCase = useCallback(() => { //if selectedElements contains at least one case - return selectedElements.some( - (element) => element.type === ElementType.CASE + return ( + selectedElements.some( + (element) => element.type === ElementType.CASE + ) && noCreationInProgress() ); - }, [selectedElements]); + }, [selectedElements, noCreationInProgress]); const handleDownloadCases = useCallback(async () => { const casesUuids = selectedElements @@ -613,9 +616,9 @@ const ContentContextualMenu = (props) => { if (menuItems.length === 0) { menuItems.push({ - messageDescriptorId: isNotUploadingElement() + messageDescriptorId: noCreationInProgress() ? 'notElementCreator' - : 'uploadingElement', + : 'elementCreationInProgress', icon: , disabled: true, }); diff --git a/src/components/toolbars/content-toolbar.js b/src/components/toolbars/content-toolbar.js index 44eba4fc8..5a19ce3d4 100644 --- a/src/components/toolbars/content-toolbar.js +++ b/src/components/toolbars/content-toolbar.js @@ -131,22 +131,32 @@ const ContentToolbar = (props) => { [selectedElements, userId] ); - const allowsDelete = useMemo(() => isUserAllowed, [isUserAllowed]); + const noCreationInProgress = useMemo( + () => selectedElements.every((el) => el.hasMetadata), + [selectedElements] + ); + + const allowsDelete = useMemo( + () => isUserAllowed && noCreationInProgress, + [isUserAllowed, noCreationInProgress] + ); const allowsMove = useMemo( () => selectedElements.every( (element) => element.type !== ElementType.DIRECTORY - ) && isUserAllowed, - [isUserAllowed, selectedElements] + ) && + isUserAllowed && + noCreationInProgress, + [isUserAllowed, selectedElements, noCreationInProgress] ); const allowsDownloadCases = useMemo( () => - selectedElements.every( + selectedElements.some( (element) => element.type === ElementType.CASE - ), - [selectedElements] + ) && noCreationInProgress, + [selectedElements, noCreationInProgress] ); const items = useMemo( diff --git a/src/translations/en.json b/src/translations/en.json index 8c7e1e0a0..e59fe6f2d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -212,7 +212,7 @@ "moveElementNotFoundError": "The element or the targeted folder was not found", "moveElementNotAllowedError": "You cannot move this element to the targeted folder. Unauthorized action", "notElementCreator": "You are not the element's creator", - "uploadingElement": "Upload in progress: no operation permitted", + "elementCreationInProgress": "Creation in progress: no operation permitted", "serverConnectionFailed": "Failed to connect to server. Please retry later.", "invalidFormatOrName": "Imported file name or format invalid", "parameterLoadingProblem": "problem of loading parameters", diff --git a/src/translations/fr.json b/src/translations/fr.json index e11e0d922..56c7878d8 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -212,7 +212,7 @@ "moveElementNotFoundError": "L'élément ou le dossier cible n'a pas été trouvé", "moveElementNotAllowedError": "Vous ne pouvez pas déplacer cet élément dans le dossier cible. Action non autorisée", "notElementCreator": "Vous n'êtes pas le créateur de l'élément", - "uploadingElement": "Téléversement en cours : pas d'opération autorisée", + "elementCreationInProgress": "Création en cours : pas d'opération autorisée", "serverConnectionFailed": "Échec de connexion avec le serveur. Veuillez réessayer ultérieurement", "invalidFormatOrName": "Format ou nom du fichier importé non valide", "parameterLoadingProblem": "Problème de chargement des paramètres",