From 612d7ca33965f820c4153958286eeee3497fa799 Mon Sep 17 00:00:00 2001 From: Tristan <135599584+Tristan-WorkGH@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:24:34 +0100 Subject: [PATCH 1/6] Fix PR #571 (#581) Because of `@ts-expect-ignore`, some components parameters that changed weren't reported by tsc --- .../criteria-based-edition-dialog.tsx | 4 +- .../explicit-naming-edition-dialog.tsx | 4 +- .../edition/script/script-edition-dialog.tsx | 4 +- .../composite-modification-dialog.tsx | 4 +- src/components/directory-content-dialog.tsx | 307 ++++++++++ src/components/directory-content.tsx | 533 ++++-------------- src/components/empty-directory.tsx | 8 +- .../menus/content-contextual-menu.tsx | 8 +- .../menus/directory-tree-contextual-menu.tsx | 6 +- 9 files changed, 449 insertions(+), 429 deletions(-) create mode 100644 src/components/directory-content-dialog.tsx diff --git a/src/components/dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog.tsx b/src/components/dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog.tsx index c215efb0..afa56ce2 100644 --- a/src/components/dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog.tsx +++ b/src/components/dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog.tsx @@ -57,7 +57,7 @@ export default function CriteriaBasedEditionDialog({ const [languageLocal] = useParameterState(PARAM_LANGUAGE); const [isFetching, setIsFetching] = useState(!!contingencyListId); const { snackError } = useSnackMessage(); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const dispatch = useDispatch(); const methods = useForm({ defaultValues: getContingencyListEmptyFormData(name), @@ -98,7 +98,7 @@ export default function CriteriaBasedEditionDialog({ const onSubmit = (contingencyList: CriteriaBasedEditionFormData) => { saveCriteriaBasedContingencyList(contingencyListId, contingencyList) .then(() => { - if (selectionForCopy.sourceItemUuid === contingencyListId) { + if (itemSelectionForCopy.sourceItemUuid === contingencyListId) { dispatch(setItemSelectionForCopy(NO_ITEM_SELECTION_FOR_COPY)); broadcastChannel.postMessage({ NO_ITEM_SELECTION_FOR_COPY }); } diff --git a/src/components/dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog.tsx b/src/components/dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog.tsx index e01e519a..cad86861 100644 --- a/src/components/dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog.tsx +++ b/src/components/dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog.tsx @@ -65,7 +65,7 @@ export default function ExplicitNamingEditionDialog({ }: Readonly) { const [isFetching, setIsFetching] = useState(!!contingencyListId); const { snackError } = useSnackMessage(); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const dispatch = useDispatch(); const methods = useForm({ defaultValues: emptyFormData(name), @@ -110,7 +110,7 @@ export default function ExplicitNamingEditionDialog({ const onSubmit = (contingencyList: ExplicitNamingEditionFormData) => { editContingencyList(contingencyListId, contingencyList) .then(() => { - if (selectionForCopy.sourceItemUuid === contingencyListId) { + if (itemSelectionForCopy.sourceItemUuid === contingencyListId) { dispatch(setItemSelectionForCopy(NO_ITEM_SELECTION_FOR_COPY)); broadcastChannel.postMessage({ NO_ITEM_SELECTION_FOR_COPY }); } diff --git a/src/components/dialogs/contingency-list/edition/script/script-edition-dialog.tsx b/src/components/dialogs/contingency-list/edition/script/script-edition-dialog.tsx index 5b94ac89..34566683 100644 --- a/src/components/dialogs/contingency-list/edition/script/script-edition-dialog.tsx +++ b/src/components/dialogs/contingency-list/edition/script/script-edition-dialog.tsx @@ -57,7 +57,7 @@ export default function ScriptEditionDialog({ }: Readonly) { const [isFetching, setIsFetching] = useState(!!contingencyListId); const { snackError } = useSnackMessage(); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const dispatch = useDispatch(); const methods = useForm({ @@ -106,7 +106,7 @@ export default function ScriptEditionDialog({ const onSubmit = (contingencyList: ScriptEditionFormData) => { editContingencyList(contingencyListId, contingencyList) .then(() => { - if (selectionForCopy.sourceItemUuid === contingencyListId) { + if (itemSelectionForCopy.sourceItemUuid === contingencyListId) { dispatch(setItemSelectionForCopy(NO_ITEM_SELECTION_FOR_COPY)); broadcastChannel.postMessage({ NO_ITEM_SELECTION_FOR_COPY }); } diff --git a/src/components/dialogs/network-modification/composite-modification/composite-modification-dialog.tsx b/src/components/dialogs/network-modification/composite-modification/composite-modification-dialog.tsx index ce01454d..e6e22061 100644 --- a/src/components/dialogs/network-modification/composite-modification/composite-modification-dialog.tsx +++ b/src/components/dialogs/network-modification/composite-modification/composite-modification-dialog.tsx @@ -62,7 +62,7 @@ export default function CompositeModificationDialog({ const [languageLocal] = useParameterState(PARAM_LANGUAGE); const [isFetching, setIsFetching] = useState(!!compositeModificationId); const { snackError } = useSnackMessage(); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const [modifications, setModifications] = useState([]); const dispatch = useDispatch(); @@ -129,7 +129,7 @@ export default function CompositeModificationDialog({ const onSubmit = (formData: FormData) => { saveCompositeModification(compositeModificationId, formData[FieldConstants.NAME]) .then(() => { - if (selectionForCopy.sourceItemUuid === compositeModificationId) { + if (itemSelectionForCopy.sourceItemUuid === compositeModificationId) { dispatch(setItemSelectionForCopy(NO_ITEM_SELECTION_FOR_COPY)); broadcastChannel.postMessage({ NO_ITEM_SELECTION_FOR_COPY }); } diff --git a/src/components/directory-content-dialog.tsx b/src/components/directory-content-dialog.tsx new file mode 100644 index 00000000..7122938c --- /dev/null +++ b/src/components/directory-content-dialog.tsx @@ -0,0 +1,307 @@ +/* + * Copyright © 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 type { UUID } from 'crypto'; +import { type Dispatch, type SetStateAction, useCallback, useEffect, useState } from 'react'; +import { + DescriptionModificationDialog, + type ElementAttributes, + ElementType, + ExpertFilterEditionDialog, + ExplicitNamingFilterEditionDialog, + isStudyMetadata, + useSnackMessage, +} from '@gridsuite/commons-ui'; +import type { CellClickedEvent } from 'ag-grid-community'; +import { useDispatch, useSelector } from 'react-redux'; +import { useIntl } from 'react-intl'; +import { elementExists, getFilterById, updateElement } from '../utils/rest-api'; +import { ContingencyListType, FilterType, NetworkModificationType } from '../utils/elementType'; +import CompositeModificationDialog from './dialogs/network-modification/composite-modification/composite-modification-dialog'; +import CriteriaBasedEditionDialog from './dialogs/contingency-list/edition/criteria-based/criteria-based-edition-dialog'; +import ScriptEditionDialog from './dialogs/contingency-list/edition/script/script-edition-dialog'; +import ExplicitNamingEditionDialog from './dialogs/contingency-list/edition/explicit-naming/explicit-naming-edition-dialog'; +import { setActiveDirectory, setItemSelectionForCopy } from '../redux/actions'; +import * as constants from '../utils/UIconstants'; +import type { AppState } from '../redux/types'; +import { useParameterState } from './dialogs/use-parameters-dialog'; +import { PARAM_LANGUAGE } from '../utils/config-params'; +import type { useDirectoryContent } from '../hooks/useDirectoryContent'; + +export type DirectoryContentDialogProps = { + cellClicked?: CellClickedEvent; + setCellClicked: Dispatch>; + broadcastChannel: BroadcastChannel; + setOpenDialog: Dispatch>; + activeElement?: ElementAttributes; + setActiveElement: Dispatch>; + selectedDirectoryElementUuid?: UUID; + childrenMetadata: ReturnType[1]; +}; + +export default function DirectoryContentDialog({ + cellClicked: event, + setCellClicked: setEvent, + setOpenDialog, + activeElement, + setActiveElement, + broadcastChannel, + selectedDirectoryElementUuid, + childrenMetadata, +}: Readonly) { + const intl = useIntl(); + const dispatch = useDispatch(); + const { snackError } = useSnackMessage(); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const activeDirectory = useSelector((state: AppState) => state.activeDirectory); + const [languageLocal] = useParameterState(PARAM_LANGUAGE); + const [elementName, setElementName] = useState(''); + + const appsAndUrls = useSelector((state: AppState) => state.appsAndUrls); + const getStudyUrl = useCallback( + (elementUuid: string) => { + const appStudy = appsAndUrls.find(isStudyMetadata); + if (appStudy) { + const studyResource = appStudy.resources?.find((resource) => + resource.types.includes(ElementType.STUDY) + ); + if (studyResource) { + return appStudy.url + studyResource.path.replace('{elementUuid}', elementUuid); + } + } + return null; + }, + [appsAndUrls] + ); + + const [openDescModificationDialog, setOpenDescModificationDialog] = useState(false); + const handleDescDialogClose = useCallback(() => { + setActiveElement(undefined); + setOpenDescModificationDialog(false); + }, [setActiveElement]); + + /* Filters contingency list dialog: window status value for editing a filters contingency list */ + const [currentFiltersContingencyListId, setCurrentFiltersContingencyListId] = useState(); + const handleCloseFiltersContingency = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentFiltersContingencyListId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + /* Explicit Naming contingency list dialog: window status value for editing an explicit naming contingency list */ + const [currentExplicitNamingContingencyListId, setCurrentExplicitNamingContingencyListId] = useState(); + const handleCloseExplicitNamingContingency = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentExplicitNamingContingencyListId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + const [currentExplicitNamingFilterId, setCurrentExplicitNamingFilterId] = useState(); + /* Filters dialog: window status value to edit ExplicitNaming filters */ + const handleCloseExplicitNamingFilterDialog = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentExplicitNamingFilterId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + const [currentNetworkModificationId, setCurrentNetworkModificationId] = useState(); + const handleCloseCompositeModificationDialog = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentNetworkModificationId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + /* Filters dialog: window status value to edit Expert filters */ + const [currentExpertFilterId, setCurrentExpertFilterId] = useState(); + const handleCloseExpertFilterDialog = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentExpertFilterId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + /* Script contingency list dialog: window status value for editing a script contingency list */ + const [currentScriptContingencyListId, setCurrentScriptContingencyListId] = useState(); + const handleCloseScriptContingency = useCallback(() => { + setOpenDialog(constants.DialogsId.NONE); + setCurrentScriptContingencyListId(undefined); + setActiveElement(undefined); + setElementName(''); + }, [setActiveElement, setOpenDialog]); + + useEffect(() => { + if (event !== undefined) { + if (event.colDef.field === 'description') { + setActiveElement(event.data); + setOpenDescModificationDialog(true); + } else if (childrenMetadata[event.data.elementUuid] !== undefined) { + setElementName(childrenMetadata[event.data.elementUuid].elementName); + const subtype = childrenMetadata[event.data.elementUuid].specificMetadata.type as unknown as string; + /** set active directory on the store because it will be used while editing the contingency name */ + dispatch(setActiveDirectory(selectedDirectoryElementUuid)); + switch (event.data.type) { + case ElementType.STUDY: { + const url = getStudyUrl(event.data.elementUuid); + if (url) { + window.open(url, '_blank'); + } else { + snackError({ + messageTxt: intl.formatMessage({ id: 'getAppLinkError' }, { type: event.data.type }), + }); + } + break; + } + case ElementType.CONTINGENCY_LIST: + if (subtype === ContingencyListType.CRITERIA_BASED.id) { + setCurrentFiltersContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === ContingencyListType.SCRIPT.id) { + setCurrentScriptContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === ContingencyListType.EXPLICIT_NAMING.id) { + setCurrentExplicitNamingContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + case ElementType.FILTER: + if (subtype === FilterType.EXPLICIT_NAMING.id) { + setCurrentExplicitNamingFilterId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === FilterType.EXPERT.id) { + setCurrentExpertFilterId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + case ElementType.MODIFICATION: + if (subtype === NetworkModificationType.COMPOSITE.id) { + setCurrentNetworkModificationId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + default: + break; + } + } + setEvent(undefined); // acknowledge parent event + } + }, [ + childrenMetadata, + dispatch, + event, + getStudyUrl, + intl, + selectedDirectoryElementUuid, + setActiveElement, + setEvent, + setOpenDialog, + snackError, + ]); + + if (openDescModificationDialog && activeElement) { + return ( + + ); + } + if (currentNetworkModificationId !== undefined) { + return ( + + ); + } + if (currentFiltersContingencyListId !== undefined) { + return ( + + ); + } + if (currentScriptContingencyListId !== undefined) { + return ( + + ); + } + if (currentExplicitNamingContingencyListId !== undefined) { + return ( + + ); + } + if (currentExplicitNamingFilterId !== undefined) { + return ( + + ); + } + if (currentExpertFilterId !== undefined) { + return ( + + ); + } +} diff --git a/src/components/directory-content.tsx b/src/components/directory-content.tsx index d77fc641..27b664a2 100644 --- a/src/components/directory-content.tsx +++ b/src/components/directory-content.tsx @@ -5,37 +5,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { type MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from 'react-intl'; -import { Box, Button, CircularProgress, Grid, SxProps, Theme } from '@mui/material'; import { - DescriptionModificationDialog, - ElementAttributes, - ElementType, - ExpertFilterEditionDialog, - ExplicitNamingFilterEditionDialog, - isStudyMetadata, - type ItemSelectionForCopy, - NO_ITEM_SELECTION_FOR_COPY, - useSnackMessage, -} from '@gridsuite/commons-ui'; + Box, + type BoxProps, + Button, + type ButtonProps, + CircularProgress, + Grid, + type SxProps, + type Theme, +} from '@mui/material'; +import { type ElementAttributes, type ItemSelectionForCopy, NO_ITEM_SELECTION_FOR_COPY } from '@gridsuite/commons-ui'; import { Add as AddIcon } from '@mui/icons-material'; import { AgGridReact } from 'ag-grid-react'; -import { CellContextMenuEvent } from 'ag-grid-community'; -import { ContingencyListType, FilterType, NetworkModificationType } from '../utils/elementType'; +import type { CellClickedEvent } from 'ag-grid-community'; import * as constants from '../utils/UIconstants'; import { setActiveDirectory, setItemSelectionForCopy } from '../redux/actions'; -import { elementExists, getFilterById, updateElement } from '../utils/rest-api'; import { AnchorStatesType, defaultAnchorStates } from './menus/common-contextual-menu'; import ContentContextualMenu from './menus/content-contextual-menu'; import ContentToolbar from './toolbars/content-toolbar'; import DirectoryTreeContextualMenu from './menus/directory-tree-contextual-menu'; -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'; -import ScriptEditionDialog from './dialogs/contingency-list/edition/script/script-edition-dialog'; -import { useParameterState } from './dialogs/use-parameters-dialog'; -import { PARAM_LANGUAGE } from '../utils/config-params'; import { useDirectoryContent } from '../hooks/useDirectoryContent'; import { computeCheckedElements, @@ -44,11 +36,11 @@ import { isRowUnchecked, } from './utils/directory-content-utils'; import NoContentDirectory from './no-content-directory'; -import { CUSTOM_ROW_CLASS, DirectoryContentTable } from './directory-content-table'; +import { CUSTOM_ROW_CLASS, DirectoryContentTable, type DirectoryContentTableProps } from './directory-content-table'; import { useHighlightSearchedElement } from './search/use-highlight-searched-element'; -import EmptyDirectory from './empty-directory'; -import CompositeModificationDialog from './dialogs/network-modification/composite-modification/composite-modification-dialog'; +import EmptyDirectory, { type EmptyDirectoryProps } from './empty-directory'; import { AppState } from '../redux/types'; +import DirectoryContentDialog from './directory-content-dialog'; const circularProgressSize = '70px'; @@ -93,18 +85,10 @@ const styles = { export default function DirectoryContent() { const treeData = useSelector((state: AppState) => state.treeData); - const { snackError } = useSnackMessage(); const dispatch = useDispatch(); - - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); - const activeDirectory = useSelector((state: AppState) => state.activeDirectory); - const gridRef = useRef(null); - const [onGridReady, getRowStyle] = useHighlightSearchedElement(gridRef?.current?.api ?? null); - const [languageLocal] = useParameterState(PARAM_LANGUAGE); - const [broadcastChannel] = useState(() => { const broadcast = new BroadcastChannel('itemCopyChannel'); broadcast.onmessage = (event: MessageEvent) => { @@ -118,10 +102,9 @@ export default function DirectoryContent() { return broadcast; }); - const appsAndUrls = useSelector((state: AppState) => state.appsAndUrls); const selectedDirectory = useSelector((state: AppState) => state.selectedDirectory); - const [activeElement, setActiveElement] = useState(null); + const [activeElement, setActiveElement] = useState(); const [isMissingDataAfterDirChange, setIsMissingDataAfterDirChange] = useState(true); const intl = useIntl(); @@ -132,89 +115,33 @@ export default function DirectoryContent() { const [directoryMenuAnchorStates, setDirectoryMenuAnchorStates] = useState(defaultAnchorStates); const [openDialog, setOpenDialog] = useState(constants.DialogsId.NONE); - const [elementName, setElementName] = useState(''); - - /** Filters contingency list dialog: window status value for editing a filters contingency list */ - const [currentFiltersContingencyListId, setCurrentFiltersContingencyListId] = useState(null); - const handleCloseFiltersContingency = () => { - setOpenDialog(constants.DialogsId.NONE); - setActiveElement(null); - setCurrentFiltersContingencyListId(null); - setElementName(''); - }; - - /** Explicit Naming contingency list dialog: window status value for editing an explicit naming contingency list */ - const [currentExplicitNamingContingencyListId, setCurrentExplicitNamingContingencyListId] = useState(null); - const handleCloseExplicitNamingContingency = () => { - setOpenDialog(constants.DialogsId.NONE); - setActiveElement(null); - setCurrentExplicitNamingContingencyListId(null); - setElementName(''); - }; - - const [currentExplicitNamingFilterId, setCurrentExplicitNamingFilterId] = useState(null); - /** Filters dialog: window status value to edit ExplicitNaming filters */ - const handleCloseExplicitNamingFilterDialog = () => { - setOpenDialog(constants.DialogsId.NONE); - setCurrentExplicitNamingFilterId(null); - setActiveElement(null); - setElementName(''); - }; - - const [currentNetworkModificationId, setCurrentNetworkModificationId] = useState(null); - const handleCloseCompositeModificationDialog = () => { - setOpenDialog(constants.DialogsId.NONE); - setCurrentNetworkModificationId(null); - setActiveElement(null); - setElementName(''); - }; - - /** - * Filters dialog: window status value to edit Expert filters - */ - const [currentExpertFilterId, setCurrentExpertFilterId] = useState(null); - const handleCloseExpertFilterDialog = () => { - setOpenDialog(constants.DialogsId.NONE); - setCurrentExpertFilterId(null); - setActiveElement(null); - setElementName(''); - }; - - /** Script contingency list dialog: window status value for editing a script contingency list */ - const [currentScriptContingencyListId, setCurrentScriptContingencyListId] = useState(null); - const handleCloseScriptContingency = () => { - setOpenDialog(constants.DialogsId.NONE); - setActiveElement(null); - setCurrentScriptContingencyListId(null); - setElementName(''); - }; /** Contextual Menus */ const [openDirectoryMenu, setOpenDirectoryMenu] = useState(false); const [openContentMenu, setOpenContentMenu] = useState(false); - const handleOpenContentMenu = (event: MouseEvent) => { + const handleOpenContentMenu = useCallback((event: MouseEvent) => { setOpenContentMenu(true); - event?.stopPropagation(); - }; + event.stopPropagation(); + }, []); const handleCloseContentMenu = useCallback(() => { setOpenContentMenu(false); - setActiveElement(null); + setActiveElement(undefined); }, []); - const handleCloseDirectoryMenu = () => { + const handleCloseDirectoryMenu = useCallback(() => { setOpenDirectoryMenu(false); dispatch(setActiveDirectory(undefined)); - }; + }, [dispatch]); - const handleOpenDirectoryMenu = (event: MouseEvent) => { + const handleOpenDirectoryMenu = useCallback((event: MouseEvent) => { setOpenDirectoryMenu(true); event.stopPropagation(); - }; + }, []); const onContextMenu = useCallback( - (event: any, anchorStates: AnchorStatesType = defaultAnchorStates) => { + (event: MouseEvent, anchorStates = defaultAnchorStates) => { if (anchorStates.anchorReference === 'anchorPosition') { // example : right click on empty space or on an element line // then open popover on mouse position with a little shift @@ -227,12 +154,12 @@ export default function DirectoryContent() { }); } else { // else anchorEl - // example : left click on a 'create element' button + // example : left-click on a 'create element' button // then open popover attached to the component clicked setDirectoryMenuAnchorStates(anchorStates); } // We check if the context menu was triggered from a row to prevent displaying both the directory and the content context menus - const isRow = !!event.target.closest(`.${CUSTOM_ROW_CLASS}`); + const isRow = !!(event.target as Element).closest(`.${CUSTOM_ROW_CLASS}`); if (!isRow) { dispatch(setActiveDirectory(selectedDirectory?.elementUuid)); handleOpenDirectoryMenu(event); @@ -240,21 +167,12 @@ export default function DirectoryContent() { handleOpenContentMenu(event); } }, - [dispatch, selectedDirectory?.elementUuid] + [dispatch, handleOpenContentMenu, handleOpenDirectoryMenu, selectedDirectory?.elementUuid] ); /* 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 onCellContextMenu = useCallback( - (cellEvent: CellContextMenuEvent) => { + const onCellContextMenu = useCallback( + (cellEvent) => { if (cellEvent.data && cellEvent.data.uploading !== null) { if (cellEvent.data.type !== 'DIRECTORY') { dispatch(setActiveDirectory(selectedDirectory?.elementUuid)); @@ -263,316 +181,81 @@ export default function DirectoryContent() { specificMetadata: childrenMetadata[cellEvent.data.elementUuid]?.specificMetadata, ...cellEvent.data, }); - 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 (isRowUnchecked(cellEvent.data, checkedRows)) { - gridRef.current?.api.deselectAll(); - } - } else if (isRowUnchecked(cellEvent.data, checkedRows)) { + if (isRowUnchecked(cellEvent.data, checkedRows)) { // If some elements were already selected, we add the active element to the selected list if not already in it. gridRef.current?.api.getRowNode(cellEvent.data.elementUuid)?.setSelected(true); } } - onContextMenu(cellEvent.event); + onContextMenu(cellEvent.event as unknown as MouseEvent); } }, - [ - checkedRows, - childrenMetadata, - contextualMixPolicies.BIG, - contextualMixPolicy, - dispatch, - selectedDirectory?.elementUuid, - onContextMenu, - ] + [checkedRows, childrenMetadata, dispatch, selectedDirectory?.elementUuid, onContextMenu] ); - const handleError = useCallback( - (message: string) => { - snackError({ - messageTxt: message, - }); - }, - [snackError] - ); - - const getStudyUrl = useCallback( - (elementUuid: string): string | null => { - const appStudy = appsAndUrls.find(isStudyMetadata); - if (appStudy) { - const studyResource = appStudy.resources?.find((resource) => - resource.types.includes(ElementType.STUDY) - ); - if (studyResource) { - return appStudy.url + studyResource.path.replace('{elementUuid}', elementUuid); - } - } - return null; - }, - [appsAndUrls] - ); - - const [openDescModificationDialog, setOpenDescModificationDialog] = useState(false); - - const handleDescriptionIconClick = (e: any) => { - setActiveElement(e.data); - setOpenDescModificationDialog(true); - }; - - const handleCellClick = useCallback( - (event: any) => { - if (event.colDef.field === 'description') { - handleDescriptionIconClick(event); - } else if (childrenMetadata[event.data.elementUuid] !== undefined) { - setElementName(childrenMetadata[event.data.elementUuid]?.elementName); - const subtype: string = childrenMetadata[event.data.elementUuid].specificMetadata - .type as unknown as string; - /** set active directory on the store because it will be used while editing the contingency name */ - dispatch(setActiveDirectory(selectedDirectory?.elementUuid)); - switch (event.data.type) { - case ElementType.STUDY: { - const url = getStudyUrl(event.data.elementUuid); - if (url) { - window.open(url, '_blank'); - } else { - handleError(intl.formatMessage({ id: 'getAppLinkError' }, { type: event.data.type })); - } - break; - } - case ElementType.CONTINGENCY_LIST: - if (subtype === ContingencyListType.CRITERIA_BASED.id) { - setCurrentFiltersContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === ContingencyListType.SCRIPT.id) { - setCurrentScriptContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === ContingencyListType.EXPLICIT_NAMING.id) { - setCurrentExplicitNamingContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - case ElementType.FILTER: - if (subtype === FilterType.EXPLICIT_NAMING.id) { - setCurrentExplicitNamingFilterId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === FilterType.EXPERT.id) { - setCurrentExpertFilterId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - case ElementType.MODIFICATION: - if (subtype === NetworkModificationType.COMPOSITE.id) { - setCurrentNetworkModificationId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - default: - break; - } - } - }, - [childrenMetadata, dispatch, getStudyUrl, handleError, intl, selectedDirectory?.elementUuid] + const [cellClicked, setCellClicked] = useState(); + const handleCellClick = useCallback( + (event) => setCellClicked(event), + [] ); - const isActiveElementUnchecked = useMemo( - () => activeElement && !checkedRows.find((children) => children.elementUuid === activeElement.elementUuid), - [activeElement, checkedRows] + const updateCheckedRows = useCallback( + () => setCheckedRows(computeCheckedElements(gridRef, childrenMetadata)), + [childrenMetadata] ); - const updateCheckedRows = useCallback(() => { - setCheckedRows(computeCheckedElements(gridRef, childrenMetadata)); - }, [childrenMetadata]); - // It includes checked rows and the row with its context menu open - const fullSelection: ElementAttributes[] = useMemo(() => { + const fullSelection = useMemo(() => { const selection = [...checkedRows]; - if (isActiveElementUnchecked && activeElement) { + if (activeElement && !checkedRows.find((children) => children.elementUuid === activeElement.elementUuid)) { selection.push(formatMetadata(activeElement, childrenMetadata)); } return selection; - }, [activeElement, checkedRows, childrenMetadata, isActiveElementUnchecked]); - - const handleOpenDialog = useCallback(() => { - setOpenDialog(constants.DialogsId.ADD_ROOT_DIRECTORY); - }, []); - - const renderLoadingContent = () => ( - - - + }, [activeElement, checkedRows, childrenMetadata]); + + const handleOpenDialog = useCallback(() => setOpenDialog(constants.DialogsId.ADD_ROOT_DIRECTORY), []); + + const handleButtonClick = useCallback>( + (mouseEvent) => + onContextMenu(mouseEvent as unknown as MouseEvent, { + anchorReference: 'anchorEl', + anchorEl: mouseEvent.currentTarget, + anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, + transformOrigin: { vertical: 'top', horizontal: 'left' }, + }), + [onContextMenu] ); - const renderEmptyDirContent = () => ( - - onContextMenu(mouseEvent, { - anchorReference: 'anchorEl', - anchorEl: mouseEvent.currentTarget, - anchorOrigin: { vertical: 'center', horizontal: 'right' }, - transformOrigin: { - vertical: 'center', - horizontal: 'left', - }, - }) - } - /> + const handleEmptyDirectoryClick = useCallback( + (mouseEvent) => + onContextMenu(mouseEvent as unknown as MouseEvent, { + anchorReference: 'anchorEl', + anchorEl: mouseEvent.currentTarget, + anchorOrigin: { vertical: 'center', horizontal: 'right' }, + transformOrigin: { vertical: 'center', horizontal: 'left' }, + }), + [onContextMenu] ); - const renderContent = () => { - // Here we wait for Metadata for the folder content - if (isMissingDataAfterDirChange) { - return renderLoadingContent(); - } - - // If no selection or currentChildren = null (first time) render nothing - if (!rows || !selectedDirectory) { - if (treeData.rootDirectories.length === 0 && treeData.initialized) { - return ; + const handleBoxDownClick = useCallback>( + (e) => { + if (e.button === constants.MOUSE_EVENT_RIGHT_BUTTON && openDialog === constants.DialogsId.NONE) { + handleCloseContentMenu(); + handleCloseDirectoryMenu(); } - return undefined; - } - - // If empty dir then render an appropriate content - if (rows.length === 0) { - return renderEmptyDirContent(); - } - - // Finally if we have elements then render the table - return ( - - ); - }; - - const renderDialog = (name: string) => { - if (openDescModificationDialog && activeElement) { - return ( - { - setActiveElement(null); - setOpenDescModificationDialog(false); - }} - // @ts-expect-error TODO: set UUID as parameter type in commons-ui - updateElement={updateElement} - /> - ); - } - // TODO openDialog should also be aware of the dialog's type, not only its subtype, because - // if/when two different dialogs have the same subtype, this function will display the wrong dialog. - switch (openDialog) { - case NetworkModificationType.COMPOSITE.id: - return ( - - ); - case ContingencyListType.CRITERIA_BASED.id: - return ( - - ); - case ContingencyListType.SCRIPT.id: - return ( - - ); - case ContingencyListType.EXPLICIT_NAMING.id: - return ( - - ); - case FilterType.EXPLICIT_NAMING.id: - return ( - - ); - case FilterType.EXPERT.id: - return ( - - ); - default: - return null; - } - }; + }, + [handleCloseContentMenu, handleCloseDirectoryMenu, openDialog] + ); useEffect(() => { - if (!selectedDirectory?.elementUuid) { - return; + if (selectedDirectory?.elementUuid) { + setIsMissingDataAfterDirChange(true); + setCheckedRows([]); } - setIsMissingDataAfterDirChange(true); - setCheckedRows([]); }, [selectedDirectory?.elementUuid]); useEffect(() => { setIsMissingDataAfterDirChange(false); - // update checkecRows ElementAttributes objects if metadata changed + // update checkedRows ElementAttributes objects if metadata changed // ex: when the user renames a selected element updateCheckedRows(); }, [childrenMetadata, updateCheckedRows]); // this will change after switching selectedDirectory @@ -589,14 +272,7 @@ export default function DirectoryContent() { variant="contained" endIcon={} sx={styles.button} - onClick={(mouseEvent) => - onContextMenu(mouseEvent, { - anchorReference: 'anchorEl', - anchorEl: mouseEvent.currentTarget, - anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, - transformOrigin: { vertical: 'top', horizontal: 'left' }, - }) - } + onClick={handleButtonClick} > @@ -604,16 +280,42 @@ export default function DirectoryContent() { ) } - {renderContent()} + {/* eslint-disable no-nested-ternary -- TODO split into sub components */} + { + // Here we wait for Metadata for the folder content + isMissingDataAfterDirChange ? ( + // render loading content + + + + ) : // If no selection or currentChildren = null (first time) render nothing + !rows || !selectedDirectory ? ( + treeData.rootDirectories.length === 0 && treeData.initialized ? ( + + ) : undefined + ) : // If empty dir then render an appropriate content + rows.length === 0 ? ( + + ) : ( + // Finally if we have elements then render the table + + ) + } - { - if (e.button === constants.MOUSE_EVENT_RIGHT_BUTTON && openDialog === constants.DialogsId.NONE) { - handleCloseContentMenu(); - handleCloseDirectoryMenu(); - } - }} - > + {activeElement && ( - {renderDialog(elementName)} + ); } diff --git a/src/components/empty-directory.tsx b/src/components/empty-directory.tsx index 0c2f2292..053f1a8d 100644 --- a/src/components/empty-directory.tsx +++ b/src/components/empty-directory.tsx @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type MouseEvent } from 'react'; import { Box, Button, SvgIcon } from '@mui/material'; import { Add as AddIcon } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; @@ -39,9 +40,10 @@ const styles = { marginTop: theme.spacing(1), }), }; -interface EmptyDirectoryProps { - onCreateElementButtonClick: (e: React.MouseEvent) => void; -} + +export type EmptyDirectoryProps = { + onCreateElementButtonClick: (e: MouseEvent) => void; +}; export default function EmptyDirectory({ onCreateElementButtonClick }: Readonly) { const theme = useSelector((state: AppState) => state[PARAM_THEME]); diff --git a/src/components/menus/content-contextual-menu.tsx b/src/components/menus/content-contextual-menu.tsx index 7b70bea8..40bfa94d 100644 --- a/src/components/menus/content-contextual-menu.tsx +++ b/src/components/menus/content-contextual-menu.tsx @@ -74,7 +74,7 @@ export default function ContentContextualMenu(props: Readonly state.user?.profile.sub); const intl = useIntl(); const dispatch = useDispatch(); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const activeDirectory = useSelector((state: AppState) => state.activeDirectory); const [deleteError, setDeleteError] = useState(''); @@ -317,15 +317,15 @@ export default function ContentContextualMenu(props: Readonly { // if copied element is renamed - if (selectionForCopy.sourceItemUuid === renamedElement[0]) { + if (itemSelectionForCopy.sourceItemUuid === renamedElement[0]) { dispatch( setItemSelectionForCopy({ - ...selectionForCopy, + ...itemSelectionForCopy, nameItem: renamedElement[1], }) ); broadcastChannel.postMessage({ - ...selectionForCopy, + ...itemSelectionForCopy, nameItem: renamedElement[1], }); } diff --git a/src/components/menus/directory-tree-contextual-menu.tsx b/src/components/menus/directory-tree-contextual-menu.tsx index f40d7098..58795d9d 100644 --- a/src/components/menus/directory-tree-contextual-menu.tsx +++ b/src/components/menus/directory-tree-contextual-menu.tsx @@ -112,7 +112,7 @@ export default function DirectoryTreeContextualMenu(props: Readonly handleCloseDialog(response?.elementUuid) ); - const selectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); + const itemSelectionForCopy = useSelector((state: AppState) => state.itemSelectionForCopy); const handleError = useCallback((message: string) => snackError({ messageTxt: message }), [snackError]); @@ -259,9 +259,9 @@ export default function DirectoryTreeContextualMenu(props: Readonly pasteElement(directory.elementUuid, selectionForCopy), + callback: () => pasteElement(directory.elementUuid, itemSelectionForCopy), icon: , - disabled: !selectionForCopy.sourceItemUuid, + disabled: !itemSelectionForCopy.sourceItemUuid, }, { isDivider: true } ); From 1e2491f30c0018fe2392ccdb92b1b389e101369a Mon Sep 17 00:00:00 2001 From: dbraquart <107846716+dbraquart@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:02:06 +0100 Subject: [PATCH 2/6] Rename spreadsheet-config-server into study-config-server (#584) Signed-off-by: David BRAQUART --- src/utils/rest-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts index 0ea1052d..558fb62f 100644 --- a/src/utils/rest-api.ts +++ b/src/utils/rest-api.ts @@ -40,7 +40,7 @@ const PREFIX_NETWORK_CONVERSION_SERVER_QUERIES = `${import.meta.env.VITE_API_GAT const PREFIX_NOTIFICATION_WS = `${import.meta.env.VITE_WS_GATEWAY}/directory-notification`; const PREFIX_FILTERS_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/filter/v1/filters`; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; -const PREFIX_SPREADSHEET_CONFIG_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/spreadsheet-config`; +const PREFIX_SPREADSHEET_CONFIG_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study-config`; export type Script = { id: string; script: string | null | undefined }; From 75b9a45d8fb1200a2bd5a1dd7139c341bb5ec2ff Mon Sep 17 00:00:00 2001 From: basseche Date: Fri, 20 Dec 2024 15:32:38 +0100 Subject: [PATCH 3/6] upgrade commons-ui version (#585) --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d696b240..1edf4135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.75.0", + "@gridsuite/commons-ui": "0.76.0", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -3015,9 +3015,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.75.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.75.0.tgz", - "integrity": "sha512-yjBjVZZkftRUCV3ZWWYNyfL5uu0a0+PstberxcC7QUulrwGbZuSzYh3F5Nr29qHgNTWxGi/jaz+E+ANkpOGWdw==", + "version": "0.76.0", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.76.0.tgz", + "integrity": "sha512-q7oUHxiPNNQ5MHhyXIoSNnrqauUTfXo6CZ03i6Uc9wqE9D4vSOz0mQw+rixNsvBu6Fc6yoByimQjn5EmeFmorQ==", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", diff --git a/package.json b/package.json index 8e20dfe2..0d2a477f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.75.0", + "@gridsuite/commons-ui": "0.76.0", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", From 35dbb10f0e325607959bf6dcf18fc765f0edf600 Mon Sep 17 00:00:00 2001 From: basseche Date: Fri, 20 Dec 2024 16:26:58 +0100 Subject: [PATCH 4/6] change the way we get case base name (#579) * change the way we get case base name --- .../dialogs/commons/prefilled-name-input.tsx | 14 ++++++++++---- src/utils/rest-api.ts | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/dialogs/commons/prefilled-name-input.tsx b/src/components/dialogs/commons/prefilled-name-input.tsx index b3db4a33..c7a17a6c 100644 --- a/src/components/dialogs/commons/prefilled-name-input.tsx +++ b/src/components/dialogs/commons/prefilled-name-input.tsx @@ -9,7 +9,7 @@ import { useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { ElementType, FieldConstants, UniqueNameInput } from '@gridsuite/commons-ui'; import { useSelector } from 'react-redux'; -import { elementExists } from '../../../utils/rest-api'; +import { elementExists, getBaseName } from '../../../utils/rest-api'; import { AppState } from '../../../redux/types'; export interface PrefilledNameInputProps { @@ -45,9 +45,15 @@ export default function PrefilledNameInput({ label, name, elementType }: Readonl if (caseName) { clearErrors(name); - setValue(name, caseName?.substring(0, caseName.indexOf('.')), { - shouldDirty: true, - }); + getBaseName(caseName) + .then((response) => { + setValue(name, response, { + shouldDirty: true, + }); + }) + .catch((error) => { + console.error('Error fetching base name:', error); + }); } } }, [caseFile, modifiedByUser, apiCallErrorMessage, caseFileErrorMessage, setValue, clearErrors, name]); diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts index 558fb62f..35a15825 100644 --- a/src/utils/rest-api.ts +++ b/src/utils/rest-api.ts @@ -816,3 +816,11 @@ export function searchElementsInfos(searchTerm: string, currentDirectoryUuid: UU } ); } + +export const getBaseName = (caseName: string) => { + const caseNameUrl = `${PREFIX_CASE_QUERIES}/v1/cases/caseBaseName?caseName=${encodeURIComponent(caseName)}`; + console.info('Get base name for case', caseName); + return backendFetchText(caseNameUrl, { + method: 'GET', + }); +}; From c057c9550dcb3a1028a3487f0ad9f90d4c245692 Mon Sep 17 00:00:00 2001 From: Tristan <135599584+Tristan-WorkGH@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:28:38 +0100 Subject: [PATCH 5/6] fix `window.open` in `onClick` popup problem (#586) --- src/components/directory-content-dialog.tsx | 171 +++++++++++--------- src/components/directory-content.tsx | 12 +- 2 files changed, 100 insertions(+), 83 deletions(-) diff --git a/src/components/directory-content-dialog.tsx b/src/components/directory-content-dialog.tsx index 7122938c..13cec461 100644 --- a/src/components/directory-content-dialog.tsx +++ b/src/components/directory-content-dialog.tsx @@ -6,7 +6,15 @@ */ import type { UUID } from 'crypto'; -import { type Dispatch, type SetStateAction, useCallback, useEffect, useState } from 'react'; +import { + type Dispatch, + ForwardedRef, + forwardRef, + type SetStateAction, + useCallback, + useImperativeHandle, + useState, +} from 'react'; import { DescriptionModificationDialog, type ElementAttributes, @@ -32,9 +40,11 @@ import { useParameterState } from './dialogs/use-parameters-dialog'; import { PARAM_LANGUAGE } from '../utils/config-params'; import type { useDirectoryContent } from '../hooks/useDirectoryContent'; +export type DirectoryContentDialogApi = { + handleClick: (event: CellClickedEvent) => void; +}; + export type DirectoryContentDialogProps = { - cellClicked?: CellClickedEvent; - setCellClicked: Dispatch>; broadcastChannel: BroadcastChannel; setOpenDialog: Dispatch>; activeElement?: ElementAttributes; @@ -43,16 +53,17 @@ export type DirectoryContentDialogProps = { childrenMetadata: ReturnType[1]; }; -export default function DirectoryContentDialog({ - cellClicked: event, - setCellClicked: setEvent, - setOpenDialog, - activeElement, - setActiveElement, - broadcastChannel, - selectedDirectoryElementUuid, - childrenMetadata, -}: Readonly) { +function DirectoryContentDialog( + { + setOpenDialog, + activeElement, + setActiveElement, + broadcastChannel, + selectedDirectoryElementUuid, + childrenMetadata, + }: Readonly, + refApi: ForwardedRef +) { const intl = useIntl(); const dispatch = useDispatch(); const { snackError } = useSnackMessage(); @@ -137,73 +148,77 @@ export default function DirectoryContentDialog({ setElementName(''); }, [setActiveElement, setOpenDialog]); - useEffect(() => { - if (event !== undefined) { - if (event.colDef.field === 'description') { - setActiveElement(event.data); - setOpenDescModificationDialog(true); - } else if (childrenMetadata[event.data.elementUuid] !== undefined) { - setElementName(childrenMetadata[event.data.elementUuid].elementName); - const subtype = childrenMetadata[event.data.elementUuid].specificMetadata.type as unknown as string; - /** set active directory on the store because it will be used while editing the contingency name */ - dispatch(setActiveDirectory(selectedDirectoryElementUuid)); - switch (event.data.type) { - case ElementType.STUDY: { - const url = getStudyUrl(event.data.elementUuid); - if (url) { - window.open(url, '_blank'); - } else { - snackError({ - messageTxt: intl.formatMessage({ id: 'getAppLinkError' }, { type: event.data.type }), - }); + useImperativeHandle( + refApi, + () => ({ + handleClick: (event: CellClickedEvent) => { + if (event.colDef.field === 'description') { + setActiveElement(event.data); + setOpenDescModificationDialog(true); + } else if (childrenMetadata[event.data.elementUuid] !== undefined) { + setElementName(childrenMetadata[event.data.elementUuid].elementName); + const subtype = childrenMetadata[event.data.elementUuid].specificMetadata.type as unknown as string; + /** set active directory on the store because it will be used while editing the contingency name */ + dispatch(setActiveDirectory(selectedDirectoryElementUuid)); + switch (event.data.type) { + case ElementType.STUDY: { + const url = getStudyUrl(event.data.elementUuid); + if (url) { + window.open(url, '_blank'); + } else { + snackError({ + messageTxt: intl.formatMessage( + { id: 'getAppLinkError' }, + { type: event.data.type } + ), + }); + } + break; } - break; + case ElementType.CONTINGENCY_LIST: + if (subtype === ContingencyListType.CRITERIA_BASED.id) { + setCurrentFiltersContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === ContingencyListType.SCRIPT.id) { + setCurrentScriptContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === ContingencyListType.EXPLICIT_NAMING.id) { + setCurrentExplicitNamingContingencyListId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + case ElementType.FILTER: + if (subtype === FilterType.EXPLICIT_NAMING.id) { + setCurrentExplicitNamingFilterId(event.data.elementUuid); + setOpenDialog(subtype); + } else if (subtype === FilterType.EXPERT.id) { + setCurrentExpertFilterId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + case ElementType.MODIFICATION: + if (subtype === NetworkModificationType.COMPOSITE.id) { + setCurrentNetworkModificationId(event.data.elementUuid); + setOpenDialog(subtype); + } + break; + default: + break; } - case ElementType.CONTINGENCY_LIST: - if (subtype === ContingencyListType.CRITERIA_BASED.id) { - setCurrentFiltersContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === ContingencyListType.SCRIPT.id) { - setCurrentScriptContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === ContingencyListType.EXPLICIT_NAMING.id) { - setCurrentExplicitNamingContingencyListId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - case ElementType.FILTER: - if (subtype === FilterType.EXPLICIT_NAMING.id) { - setCurrentExplicitNamingFilterId(event.data.elementUuid); - setOpenDialog(subtype); - } else if (subtype === FilterType.EXPERT.id) { - setCurrentExpertFilterId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - case ElementType.MODIFICATION: - if (subtype === NetworkModificationType.COMPOSITE.id) { - setCurrentNetworkModificationId(event.data.elementUuid); - setOpenDialog(subtype); - } - break; - default: - break; } - } - setEvent(undefined); // acknowledge parent event - } - }, [ - childrenMetadata, - dispatch, - event, - getStudyUrl, - intl, - selectedDirectoryElementUuid, - setActiveElement, - setEvent, - setOpenDialog, - snackError, - ]); + }, + }), + [ + childrenMetadata, + dispatch, + getStudyUrl, + intl, + selectedDirectoryElementUuid, + setActiveElement, + setOpenDialog, + snackError, + ] + ); if (openDescModificationDialog && activeElement) { return ( @@ -305,3 +320,5 @@ export default function DirectoryContentDialog({ ); } } + +export default forwardRef(DirectoryContentDialog); diff --git a/src/components/directory-content.tsx b/src/components/directory-content.tsx index 27b664a2..590c113f 100644 --- a/src/components/directory-content.tsx +++ b/src/components/directory-content.tsx @@ -21,7 +21,6 @@ import { import { type ElementAttributes, type ItemSelectionForCopy, NO_ITEM_SELECTION_FOR_COPY } from '@gridsuite/commons-ui'; import { Add as AddIcon } from '@mui/icons-material'; import { AgGridReact } from 'ag-grid-react'; -import type { CellClickedEvent } from 'ag-grid-community'; import * as constants from '../utils/UIconstants'; import { setActiveDirectory, setItemSelectionForCopy } from '../redux/actions'; import { AnchorStatesType, defaultAnchorStates } from './menus/common-contextual-menu'; @@ -40,7 +39,7 @@ import { CUSTOM_ROW_CLASS, DirectoryContentTable, type DirectoryContentTableProp import { useHighlightSearchedElement } from './search/use-highlight-searched-element'; import EmptyDirectory, { type EmptyDirectoryProps } from './empty-directory'; import { AppState } from '../redux/types'; -import DirectoryContentDialog from './directory-content-dialog'; +import DirectoryContentDialog, { type DirectoryContentDialogApi } from './directory-content-dialog'; const circularProgressSize = '70px'; @@ -192,9 +191,11 @@ export default function DirectoryContent() { [checkedRows, childrenMetadata, dispatch, selectedDirectory?.elementUuid, onContextMenu] ); - const [cellClicked, setCellClicked] = useState(); + const dialogsApi = useRef(null); const handleCellClick = useCallback( - (event) => setCellClicked(event), + /* The `window.open()` call MUST be inside the on-click callback, or else navigators like Firefox will + * block it with their anti-popup, and user must explicitly whitelist the url/domain */ + (event) => dialogsApi.current?.handleClick(event), [] ); @@ -339,9 +340,8 @@ export default function DirectoryContent() { /> Date: Thu, 2 Jan 2025 09:20:37 +0100 Subject: [PATCH 6/6] Upgrade to commons ui 0.76.2 (#590) Signed-off-by: jamal-khey --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1edf4135..513fd4ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.76.0", + "@gridsuite/commons-ui": "0.76.2", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -3015,9 +3015,9 @@ } }, "node_modules/@gridsuite/commons-ui": { - "version": "0.76.0", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.76.0.tgz", - "integrity": "sha512-q7oUHxiPNNQ5MHhyXIoSNnrqauUTfXo6CZ03i6Uc9wqE9D4vSOz0mQw+rixNsvBu6Fc6yoByimQjn5EmeFmorQ==", + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.76.2.tgz", + "integrity": "sha512-ecIWTX/gvddjbKDtSZSCaTGnk3tspBtsKvxSD0x4jvzxUM+DL3y9X9eLugmW+7eI9vEtUjnTPifJiKTueu5WZg==", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", diff --git a/package.json b/package.json index 0d2a477f..ed5bbf02 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.76.0", + "@gridsuite/commons-ui": "0.76.2", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169",