Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions src/components/grid-layout/cards/diagrams/diagram.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,6 @@ export type SubstationDiagramParams = DiagramBaseParams & {
type: DiagramType.SUBSTATION;
substationId: string;
};
export type NetworkAreaDiagramParams = DiagramBaseParams & {
type: DiagramType.NETWORK_AREA_DIAGRAM;
nadConfigUuid: UUID | undefined;
filterUuid: UUID | undefined;
currentFilterUuid?: UUID;
voltageLevelIds: string[];
voltageLevelToExpandIds: string[];
voltageLevelToOmitIds: string[];
positions: DiagramConfigPosition[];
};

export type DiagramParams = VoltageLevelDiagramParams | SubstationDiagramParams | NetworkAreaDiagramParams;

// diagrams model
export type DiagramBase = {
type: DiagramType;
Expand All @@ -57,12 +44,12 @@ export type SubstationDiagram = DiagramBase & {
};
export type NetworkAreaDiagram = DiagramBase & {
type: DiagramType.NETWORK_AREA_DIAGRAM;
svg: DiagramSvg | null;
title?: string;
nadConfigUuid: UUID | undefined;
filterUuid: UUID | undefined;
currentFilterUuid: UUID | undefined;
currentNadConfigUuid?: UUID;
initialVoltageLevelIds: string[];
voltageLevelIds: string[];
voltageLevelToExpandIds: string[];
voltageLevelToOmitIds: string[];
Expand All @@ -87,7 +74,7 @@ export interface SldSvg {
}

export interface VoltageLevel {
id?: string;
id: string;
substationId: UUID;
country?: string;
name?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ import {
} from '@gridsuite/commons-ui';
import IconButton from '@mui/material/IconButton';
import UploadIcon from '@mui/icons-material/Upload';
import Button from '@mui/material/Button';
import SaveIcon from '@mui/icons-material/Save';
import SearchIcon from '@mui/icons-material/Search';
import AddLocationAltOutlinedIcon from '@mui/icons-material/AddLocationAltOutlined';
import { Tooltip } from '@mui/material';
import { FormControlLabel, Switch, type Theme, Tooltip } from '@mui/material';
import { AppState } from 'redux/reducer.type';
import { FormattedMessage, useIntl } from 'react-intl';
import type { UUID } from 'node:crypto';
Expand All @@ -38,34 +37,40 @@ import { fetchNetworkElementInfos } from 'services/study/network';
import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types';
import VoltageLevelSearchMenu from './voltage-level-search-menu';

const getControlsBackgroundColor = (theme: Theme) =>
theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.background.default;

const styles = {
actionIcon: (theme) => ({
width: theme.spacing(3),
height: theme.spacing(3),
}),
panel: (theme) => ({
backgroundColor: theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.background.default,
backgroundColor: getControlsBackgroundColor(theme),
borderRadius: theme.spacing(1),
padding: theme.spacing(0.5),
display: 'block',
position: 'absolute',
top: theme.spacing(1),
left: theme.spacing(1),
}),
buttonPanel: (theme) => ({
borderRadius: theme.spacing(1),
padding: theme.spacing(0.5),
display: 'block',
position: 'absolute',
top: '5px',
right: '5px',
}),
icon: {
fontSize: 'medium',
},
button: {
minWidth: 'auto',
},

editModeSwitch: (theme) => ({
position: 'absolute',
top: theme.spacing(1),
right: theme.spacing(1),
height: theme.spacing(4),
margin: 0,
paddingLeft: theme.spacing(1),
borderRadius: theme.spacing(1),
backgroundColor: getControlsBackgroundColor(theme),
'& .MuiFormControlLabel-label': {
fontSize: theme.typography.body2.fontSize,
},
}),
divider: (theme) => ({
borderColor: theme.palette.grey[600],
margin: '2px 4px',
Expand All @@ -77,7 +82,7 @@ interface DiagramControlsProps {
onUpdate?: (data: IElementUpdateDialog) => void;
onLoad?: (elementUuid: UUID, elementType: ElementType, elementName: string) => void;
isEditNadMode: boolean;
onToggleEditNadMode?: (isEditMode: boolean) => void;
onToggleEditNadMode?: () => void;
onExpandAllVoltageLevels?: () => void;
onAddVoltageLevel: (vlId: string) => void;
onAddVoltageLevelsFromFilter: (elementUuid: UUID) => void;
Expand Down Expand Up @@ -185,7 +190,7 @@ const DiagramControls: React.FC<DiagramControlsProps> = ({
};

const handleToggleEditMode = () => {
onToggleEditNadMode?.(!isEditNadMode);
onToggleEditNadMode?.();
};

const handleVoltageLevelSelect = useCallback(
Expand Down Expand Up @@ -321,11 +326,12 @@ const DiagramControls: React.FC<DiagramControlsProps> = ({
)}
</Box>
</Box>
<Box sx={styles.buttonPanel}>
<Button size="small" sx={styles.button} onClick={handleToggleEditMode}>
<FormattedMessage id={isEditNadMode ? 'apply' : 'EditNad'} />
</Button>
</Box>
<FormControlLabel
sx={styles.editModeSwitch}
labelPlacement="start"
label={<FormattedMessage id="EditNad" />}
control={<Switch size="small" checked={isEditNadMode} onChange={handleToggleEditMode} />}
/>
{studyUuid && (
<>
{isSaveDialogOpen && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ import { EQUIPMENT_INFOS_TYPES } from 'components/utils/equipment-types';
import GenericEquipmentPopover from 'components/tooltips/generic-equipment-popover';
import { GenericEquipmentInfos } from 'components/tooltips/equipment-popover-type';
import { GenericPopoverContent } from 'components/tooltips/generic-popover-content';
import { selectActiveWorkspaceId } from 'redux/slices/workspace-selectors';
import { selectActiveWorkspaceId, selectPanelEditMode } from 'redux/slices/workspace-selectors';
import type { RootState } from 'redux/store';
import { useWorkspacePanelActions } from 'components/workspace/hooks/use-workspace-panel-actions';
import { getLocalStoragePanelState, saveLocalStoragePanelState } from 'redux/session-storage/workspace-local-storage';
import { PanelType } from 'components/workspace/types/workspace.types';
import { DiagramAdditionalMetadata } from '../diagram.type';
Expand Down Expand Up @@ -86,7 +88,6 @@ type NetworkAreaDiagramContentProps = {
readonly onMoveNode: (voltageLevelId: string, x: number, y: number) => void;
readonly onMoveTextNode: (voltageLevelId: string, shiftX: number, shiftY: number) => void;
readonly onReplaceNad: (name: string, nadConfigUuid?: UUID, filterUuid?: UUID) => void;
readonly onSaveNad?: () => void;
};

const NetworkAreaDiagramContent = memo(function NetworkAreaDiagramContent(props: NetworkAreaDiagramContentProps) {
Expand All @@ -112,7 +113,6 @@ const NetworkAreaDiagramContent = memo(function NetworkAreaDiagramContent(props:
loadingState,
isNadCreationFromFilter,
showInSpreadsheet,
onSaveNad,
} = props;
const svgRef = useRef(null);
const { snackError, snackInfo } = useSnackMessage();
Expand All @@ -128,7 +128,8 @@ const NetworkAreaDiagramContent = memo(function NetworkAreaDiagramContent(props:
const [shouldDisplayMenu, setShouldDisplayMenu] = useState(false);
const currentNode = useSelector((state: AppState) => state.currentTreeNode);
const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid);
const [isEditNadMode, setIsEditNadMode] = useState<boolean>(false);
const isEditNadMode = useSelector((state: RootState) => selectPanelEditMode(state, nadPanelId));
const { togglePanelEditMode } = useWorkspacePanelActions();
const workspaceId = useSelector(selectActiveWorkspaceId);

// Workaround for https://github.com/react/react/issues/35187 and https://github.com/react/react/issues/35034:
Expand Down Expand Up @@ -163,15 +164,9 @@ const NetworkAreaDiagramContent = memo(function NetworkAreaDiagramContent(props:
diagramViewerRef.current.enableDragInteraction = isEditNadMode;
}

// save nad when exiting edit mode
const handleSetIsEditNadMode = useCallback(
(newMode: boolean) => {
if (isEditNadMode && !newMode) {
onSaveNad?.();
}
setIsEditNadMode(newMode);
},
[isEditNadMode, onSaveNad]
const handleToggleEditNadMode = useCallback(
() => togglePanelEditMode({ panelId: nadPanelId }),
[nadPanelId, togglePanelEditMode]
);

const handleToggleHover: OnToggleNadHoverCallbackType = useEffectEvent(
Expand Down Expand Up @@ -600,7 +595,7 @@ const NetworkAreaDiagramContent = memo(function NetworkAreaDiagramContent(props:
onUpdate={handleUpdateNadConfig}
onLoad={handleReplaceNadConfig}
isEditNadMode={isEditNadMode}
onToggleEditNadMode={handleSetIsEditNadMode}
onToggleEditNadMode={handleToggleEditNadMode}
onExpandAllVoltageLevels={handleExpandAllVoltageLevels}
onAddVoltageLevel={handleAddVoltageLevel}
onAddVoltageLevelsFromFilter={handleAddVoltageLevelsFromFilter}
Expand Down
2 changes: 2 additions & 0 deletions src/components/study-container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getWorkspacesMetadata, getWorkspace } from '../services/study/workspace
import {
getLocalStorageActiveWorkspaceId,
getLocalStoragePanelStates,
normalizeWorkspacePanels,
} from '../redux/session-storage/workspace-local-storage';

import WaitingLoader from './utils/waiting-loader';
Expand Down Expand Up @@ -524,6 +525,7 @@ export function StudyContainer() {
.then((workspace) => {
if (workspace) {
const savedPanels = getLocalStoragePanelStates(studyUuid, workspace.id);
workspace.panels = normalizeWorkspacePanels(workspace.panels);
workspace.panels.forEach((panel, index) => {
panel.zIndex = savedPanels[panel.id]?.zIndex ?? index + 1;
});
Expand Down
48 changes: 22 additions & 26 deletions src/components/workspace/core/panel-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,30 @@ import type { AppState } from '../../../redux/reducer.type';
import { SldAssociationButton } from './sld-association-button';
import { setDirtyComputationParameters } from 'redux/actions';
import { SelectOptionsDialog } from 'utils/dialogs';
import { getPanelBorder } from './utils/panel-border';
import { selectPanelEditMode } from '../../../redux/slices/workspace-selectors';
import type { RootState } from '../../../redux/store';

const getHeaderStyles = (theme: Theme, isFocused: boolean, maximized: boolean) => {
let backgroundColor: string;
let border: string;
if (theme.palette.mode === 'light') {
backgroundColor = isFocused ? theme.palette.grey[200] : 'white';
border = `1px solid ${theme.palette.grey[500]}`;
} else {
backgroundColor = '#292e33';
border =
isFocused && !maximized ? `1px solid ${theme.palette.grey[100]}` : `1px solid ${theme.palette.grey[800]}`;
const getHeaderBackground = (theme: Theme, isFocused: boolean) => {
if (theme.palette.mode !== 'light') {
return '#292e33';
}

return {
paddingLeft: theme.spacing(1),
display: 'flex',
alignItems: 'center',
backgroundColor,
border,
borderRadius: theme.spacing(2) + ' ' + theme.spacing(2) + ' 0 0',
borderBottom: 'none',
cursor: 'grab',
userSelect: 'none',
'&:active': {
cursor: 'grabbing',
},
};
return isFocused ? theme.palette.grey[200] : 'white';
};

const getHeaderStyles = (theme: Theme, isFocused: boolean, maximized: boolean, isEditing: boolean) => ({
paddingLeft: theme.spacing(1),
display: 'flex',
alignItems: 'center',
backgroundColor: getHeaderBackground(theme, isFocused),
borderBottom: getPanelBorder(theme, isFocused, maximized, isEditing),
cursor: 'grab',
userSelect: 'none',
'&:active': {
cursor: 'grabbing',
},
});

const styles = {
title: {
display: 'flex',
Expand Down Expand Up @@ -99,6 +94,7 @@ export const PanelHeader = memo(({ panelId, title, panelType, pinned, maximized,
const { deletePanel, minimizePanel, maximizePanel, pinPanel } = useWorkspacePanelActions();
const displayTitle = intl.messages[title] ? intl.formatMessage({ id: title }) : title || '';
const isDirtyComputationParameters = useSelector((state: AppState) => state.isDirtyComputationParameters);
const isEditing = useSelector((state: RootState) => selectPanelEditMode(state, panelId));
const [isConfirmCloseOpen, setIsConfirmCloseOpen] = useState(false);

const handleClose = () => {
Expand Down Expand Up @@ -126,7 +122,7 @@ export const PanelHeader = memo(({ panelId, title, panelType, pinned, maximized,
}, []);

return (
<Box className="panel-header" sx={(theme) => getHeaderStyles(theme, isFocused, maximized)}>
<Box className="panel-header" sx={(theme) => getHeaderStyles(theme, isFocused, maximized, isEditing)}>
<Box sx={styles.title}>
<Box sx={styles.titleContent}>
{getPanelConfig(panelType).icon}
Expand Down
30 changes: 9 additions & 21 deletions src/components/workspace/core/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { memo, useCallback } from 'react';
import { Box, Theme } from '@mui/material';
import { Rnd, type RndDragCallback, type RndResizeCallback } from 'react-rnd';
import { useSelector } from 'react-redux';
import { selectPanel } from '../../../redux/slices/workspace-selectors';
import { selectPanel, selectPanelEditMode } from '../../../redux/slices/workspace-selectors';
import { useWorkspacePanelActions } from '../hooks/use-workspace-panel-actions';
import type { RootState } from '../../../redux/store';
import { PANEL_CONTENT_REGISTRY } from '../panel-contents/panel-content-registry';
Expand All @@ -20,19 +20,10 @@ import type { AppState } from '../../../redux/reducer.type';
import { getSnapZone, type SnapRect } from './utils/snap-utils';
import { calculatePanelDimensions, positionToRelative, sizeToRelative } from './utils/coordinate-utils';
import PanelErrorBoundary from './panel-error-boundary';
import { getPanelBorder } from './utils/panel-border';

const RESIZE_HANDLE_SIZE = 12;

const getBorder = (theme: Theme, isFocused: boolean, maximized: boolean) => {
if (theme.palette.mode === 'light') {
return `1px solid ${theme.palette.grey[500]}`;
}
if (isFocused && !maximized) {
return `1px solid ${theme.palette.grey[100]}`;
}
return `1px solid ${theme.palette.grey[800]}`;
};

const styles = {
panel: {
display: 'flex',
Expand All @@ -54,8 +45,6 @@ const styles = {
overflow: 'hidden',
position: 'relative',
backgroundColor: theme.palette.mode === 'light' ? theme.palette.background.paper : '#292e33',
borderRadius: '0 0 ' + theme.spacing(2) + ' ' + theme.spacing(2),
borderTop: 'none',
}),
resizeHandles: {
bottomRight: { width: RESIZE_HANDLE_SIZE, height: RESIZE_HANDLE_SIZE, right: 0, bottom: 0 },
Expand All @@ -78,6 +67,7 @@ export const Panel = memo(({ panelId, containerRect, snapPreview, onSnapPreview,
const studyUuid = useSelector((state: AppState) => state.studyUuid);
const currentRootNetworkUuid = useSelector((state: AppState) => state.currentRootNetworkUuid);
const currentNode = useSelector((state: AppState) => state.currentTreeNode);
const isEditing = useSelector((state: RootState) => selectPanelEditMode(state, panelId));

const handleDrag = useCallback(
(e: MouseEvent) => {
Expand Down Expand Up @@ -161,7 +151,11 @@ export const Panel = memo(({ panelId, containerRect, snapPreview, onSnapPreview,
>
<Box
onPointerDown={ensureFocused}
sx={(theme) => ({ ...styles.panel, boxShadow: isFocused ? theme.shadows[18] : 'none' })}
sx={(theme) => ({
...styles.panel,
boxShadow: isFocused ? theme.shadows[18] : 'none',
border: getPanelBorder(theme, isFocused, panel.maximized, isEditing),
})}
>
<PanelHeader
panelId={panelId}
Expand All @@ -171,13 +165,7 @@ export const Panel = memo(({ panelId, containerRect, snapPreview, onSnapPreview,
maximized={panel.maximized}
isFocused={isFocused}
/>
<Box
className="panel-content"
sx={(theme) => ({
...styles.content(theme),
border: getBorder(theme, isFocused, panel.maximized),
})}
>
<Box className="panel-content" sx={styles.content}>
{studyUuid && currentRootNetworkUuid && currentNode ? (
<PanelErrorBoundary key={`${panelId}-${panel.type}`}>
{PANEL_CONTENT_REGISTRY[panel.type]({
Expand Down
21 changes: 21 additions & 0 deletions src/components/workspace/core/utils/panel-border.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026, 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 { Theme } from '@mui/material';

export const getPanelBorder = (theme: Theme, isFocused: boolean, maximized: boolean, isEditing: boolean) => {
if (isEditing) {
return isFocused ? `2px solid ${theme.palette.primary.main}` : `1px solid ${theme.palette.primary.main}`;
}
if (theme.palette.mode === 'light') {
return `1px solid ${theme.palette.grey[500]}`;
}
if (isFocused && !maximized) {
return `1px solid ${theme.palette.grey[100]}`;
}
return `1px solid ${theme.palette.grey[800]}`;
};
Loading