Skip to content

Commit b41d75f

Browse files
Merge branch 'main' into jorism/fix-spreadsheet-update-on-notifications
2 parents 45d9d08 + 42d6f02 commit b41d75f

File tree

18 files changed

+523
-81
lines changed

18 files changed

+523
-81
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@mui/material": "^5.18.0",
2020
"@mui/x-charts": "^7.29.1",
2121
"@mui/x-tree-view": "^7.29.1",
22-
"@powsybl/network-viewer": "1.10.0",
22+
"@powsybl/network-viewer": "1.11.0",
2323
"@reduxjs/toolkit": "^2.9.0",
2424
"@svgdotjs/svg.js": "^3.2.4",
2525
"@xyflow/react": "^12.8.4",

src/components/grid-layout/cards/diagrams/diagram-styles.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ export const styles = {
104104
transform: 'unset !important',
105105
},
106106
},
107+
nadDefaultCursors: {
108+
'& .nad-branch-edges .nad-edge-path, & .nad-3wt-edges .nad-edge-path, & .nad-branch-edges .nad-winding, & .nad-3wt-nodes .nad-winding, & .nad-edge-infos':
109+
{
110+
cursor: 'context-menu',
111+
},
112+
},
113+
nadEditModeCursors: {
114+
'& .nad-label-box, & .nad-vl-nodes .nad-busnode': {
115+
cursor: 'grab',
116+
},
117+
'& .nad-edge-infos': {
118+
cursor: 'default',
119+
},
120+
},
107121
paperBorders: (theme) => ({
108122
borderLeft: '1px solid ' + theme.palette.action.disabled,
109123
borderBottom: '1px solid ' + theme.palette.action.disabledBackground,

src/components/grid-layout/cards/diagrams/networkAreaDiagram/network-area-diagram-content.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ function NetworkAreaDiagramContent(props: NetworkAreaDiagramContentProps) {
393393
styles.divDiagram,
394394
styles.divNetworkAreaDiagram,
395395
loadFlowStatus !== RunningStatus.SUCCEED ? styles.divDiagramInvalid : undefined,
396-
isEditNadMode && !showLabels ? styles.hideLabels : undefined
396+
isEditNadMode && !showLabels ? styles.hideLabels : undefined,
397+
isEditNadMode ? styles.nadEditModeCursors : styles.nadDefaultCursors
397398
)}
398399
/>
399400
<DiagramControls

src/components/spreadsheet-view/add-spreadsheet/dialogs/add-spreadsheets-from-collection-dialog.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,11 @@ export default function AddSpreadsheetsFromCollectionDialog({
119119
options={[
120120
{
121121
id: SpreadsheetCollectionImportMode.REPLACE,
122-
label: intl.formatMessage({
123-
id: 'spreadsheet/create_new_spreadsheet/apply_spreadsheet_collection_mode_replace',
124-
}),
122+
label: 'spreadsheet/create_new_spreadsheet/apply_spreadsheet_collection_mode_replace',
125123
},
126124
{
127125
id: SpreadsheetCollectionImportMode.APPEND,
128-
label: intl.formatMessage({
129-
id: 'spreadsheet/create_new_spreadsheet/apply_spreadsheet_collection_mode_append',
130-
}),
126+
label: 'spreadsheet/create_new_spreadsheet/apply_spreadsheet_collection_mode_append',
131127
},
132128
]}
133129
/>

src/components/spreadsheet-view/spreadsheet-tabs/toolbar/PartialLoadingMenuButton.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
4747
(state: AppState) =>
4848
state.spreadsheetOptionalLoadingParameters[SpreadsheetEquipmentType.GENERATOR].regulatingTerminal
4949
);
50+
const remoteBusNetworkComponents = useSelector(
51+
(state: AppState) => state.spreadsheetOptionalLoadingParameters[SpreadsheetEquipmentType.BUS].networkComponents
52+
);
5053
const [localBranchOlg, setLocalBranchOlg] = useState<boolean>(remoteBranchOlg);
5154
const [localLineOlg, setLocalLineOlg] = useState<boolean>(remoteLineOlg);
5255
const [localTwtOlg, setLocalTwtOlg] = useState<boolean>(remoteTwtOlg);
5356
const [localGeneratorRegTerm, setLocalGeneratorRegTerm] = useState<boolean>(remoteGeneratorRegTerm);
57+
const [localBusNetworkComponents, setLocalBusNetworkComponents] = useState<boolean>(remoteBusNetworkComponents);
5458

5559
const handleClick = useCallback<NonNullable<TooltipIconButtonProps['onClick']>>(
5660
(event) => {
@@ -59,8 +63,9 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
5963
setLocalLineOlg(remoteLineOlg);
6064
setLocalTwtOlg(remoteTwtOlg);
6165
setLocalGeneratorRegTerm(remoteGeneratorRegTerm);
66+
setLocalBusNetworkComponents(remoteBusNetworkComponents);
6267
},
63-
[remoteBranchOlg, remoteGeneratorRegTerm, remoteLineOlg, remoteTwtOlg]
68+
[remoteBranchOlg, remoteBusNetworkComponents, remoteGeneratorRegTerm, remoteLineOlg, remoteTwtOlg]
6469
);
6570

6671
const handleClose = useCallback(() => {
@@ -69,7 +74,8 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
6974
localBranchOlg !== remoteBranchOlg ||
7075
localLineOlg !== remoteLineOlg ||
7176
localTwtOlg !== remoteTwtOlg ||
72-
localGeneratorRegTerm !== remoteGeneratorRegTerm
77+
localGeneratorRegTerm !== remoteGeneratorRegTerm ||
78+
localBusNetworkComponents !== remoteBusNetworkComponents
7379
) {
7480
if (studyUuid) {
7581
updateSpreadsheetParameters(studyUuid, {
@@ -81,6 +87,9 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
8187
[SpreadsheetEquipmentType.GENERATOR]: {
8288
regulatingTerminal: localGeneratorRegTerm,
8389
},
90+
[SpreadsheetEquipmentType.BUS]: {
91+
networkComponents: localBusNetworkComponents,
92+
},
8493
});
8594
}
8695
}
@@ -93,14 +102,16 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
93102
remoteTwtOlg,
94103
localGeneratorRegTerm,
95104
remoteGeneratorRegTerm,
105+
localBusNetworkComponents,
106+
remoteBusNetworkComponents,
96107
studyUuid,
97108
]);
98109

99110
const open = anchorEl !== undefined;
100111

101112
const isOptionalData = useMemo(
102-
() => remoteBranchOlg || remoteLineOlg || remoteTwtOlg || remoteGeneratorRegTerm,
103-
[remoteBranchOlg, remoteGeneratorRegTerm, remoteLineOlg, remoteTwtOlg]
113+
() => remoteBranchOlg || remoteLineOlg || remoteTwtOlg || remoteGeneratorRegTerm || remoteBusNetworkComponents,
114+
[remoteBranchOlg, remoteBusNetworkComponents, remoteGeneratorRegTerm, remoteLineOlg, remoteTwtOlg]
104115
);
105116

106117
return (
@@ -166,6 +177,15 @@ export default function PartialLoadingMenuButton({ disabled, ...props }: Readonl
166177
labelId="spreadsheet/tabs/lazy_loading/labels/regulatingTerminal"
167178
onChange={setLocalGeneratorRegTerm}
168179
/>
180+
181+
<ListSubheader sx={styles.headers}>
182+
<FormattedMessage id="BUS" />
183+
</ListSubheader>
184+
<PartialLoadingMenuItem
185+
value={localBusNetworkComponents}
186+
labelId="spreadsheet/tabs/lazy_loading/labels/networkComponentsInformation"
187+
onChange={setLocalBusNetworkComponents}
188+
/>
169189
</Menu>
170190
</>
171191
);

src/components/spreadsheet-view/spreadsheet/spreadsheet-content/hooks/use-optional-loading-parameters-for-equipments.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ export function useOptionalLoadingParametersForEquipments(type: SpreadsheetEquip
2929
(state: AppState) =>
3030
state.spreadsheetOptionalLoadingParameters[SpreadsheetEquipmentType.GENERATOR].regulatingTerminal
3131
);
32+
const remoteBusNetworkComponents = useSelector(
33+
(state: AppState) => state.spreadsheetOptionalLoadingParameters[SpreadsheetEquipmentType.BUS].networkComponents
34+
);
3235
const [branchOlg, setBranchOlg] = useState<boolean>(remoteBranchOlg);
3336
const [lineOlg, setLineOlg] = useState<boolean>(remoteLineOlg);
3437
const [twtOlg, setTwtOlg] = useState<boolean>(remoteTwtOlg);
3538
const [generatorRegTerm, setGeneratorRegTerm] = useState<boolean>(remoteGeneratorRegTerm);
39+
const [busNetworkComponents, setBusNetworkComponents] = useState<boolean>(remoteBusNetworkComponents);
3640
const {
3741
value: shouldLoadOptionalLoadingParameters,
3842
setValue: setShouldLoadOptionalLoadingParameters,
@@ -77,16 +81,31 @@ export function useOptionalLoadingParametersForEquipments(type: SpreadsheetEquip
7781
!remoteGeneratorRegTerm
7882
) {
7983
setShouldCleanOptionalLoadingParameters(true);
84+
} else if (
85+
type === SpreadsheetEquipmentType.BUS &&
86+
remoteBusNetworkComponents !== busNetworkComponents &&
87+
remoteBusNetworkComponents
88+
) {
89+
setShouldLoadOptionalLoadingParameters(true);
90+
} else if (
91+
type === SpreadsheetEquipmentType.BUS &&
92+
remoteBusNetworkComponents !== busNetworkComponents &&
93+
!remoteBusNetworkComponents
94+
) {
95+
setShouldCleanOptionalLoadingParameters(true);
8096
}
8197
setBranchOlg(remoteBranchOlg);
8298
setLineOlg(remoteLineOlg);
8399
setTwtOlg(remoteTwtOlg);
84100
setGeneratorRegTerm(remoteGeneratorRegTerm);
101+
setBusNetworkComponents(remoteBusNetworkComponents);
85102
}, [
86103
branchOlg,
104+
busNetworkComponents,
87105
generatorRegTerm,
88106
lineOlg,
89107
remoteBranchOlg,
108+
remoteBusNetworkComponents,
90109
remoteGeneratorRegTerm,
91110
remoteLineOlg,
92111
remoteTwtOlg,

src/components/spreadsheet-view/spreadsheet/spreadsheet-toolbar/global-model-editor/formula-editor.tsx

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,106 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77
import { ExpandingTextField, type ExpandingTextFieldProps, type SxStyle } from '@gridsuite/commons-ui';
8+
import { Box } from '@mui/material';
9+
import { useTheme } from '@mui/material/styles';
10+
import { useMemo } from 'react';
11+
import { useFormContext, useWatch } from 'react-hook-form';
12+
import { useFormulaSearch } from './formula-search-context';
813

914
const styles = {
10-
flexGrow: 1,
11-
} as const satisfies SxStyle;
15+
container: {
16+
position: 'relative',
17+
flexGrow: 1,
18+
},
19+
overlay: {
20+
position: 'absolute',
21+
top: 0,
22+
left: 0,
23+
width: '100%',
24+
height: '100%',
25+
whiteSpace: 'pre-wrap',
26+
color: 'transparent',
27+
pointerEvents: 'none',
28+
padding: '16.5px 14px',
29+
boxSizing: 'border-box',
30+
},
31+
textField: {
32+
position: 'relative',
33+
backgroundColor: 'transparent',
34+
},
35+
} as const satisfies Record<'container' | 'overlay' | 'textField', SxStyle>;
36+
37+
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1238

1339
export default function FormulaEditor({ name }: Readonly<ExpandingTextFieldProps>) {
14-
return <ExpandingTextField name={name} label="" minRows={3} rows={3} sx={styles} />;
40+
const theme = useTheme();
41+
const { control } = useFormContext();
42+
const value = useWatch({ name, control }) as string | undefined;
43+
const { searchTerm } = useFormulaSearch();
44+
45+
const overlaySx = useMemo(() => {
46+
const { fontFamily, body1 } = theme.typography;
47+
return {
48+
...styles.overlay,
49+
fontFamily,
50+
fontSize: body1?.fontSize,
51+
fontWeight: body1?.fontWeight,
52+
lineHeight: '1.4375em',
53+
letterSpacing: body1?.letterSpacing,
54+
} satisfies SxStyle;
55+
}, [theme]);
56+
57+
const highlighted = useMemo(() => {
58+
const formula = value ?? '';
59+
if (!searchTerm) {
60+
return formula;
61+
}
62+
63+
const escaped = escapeRegExp(searchTerm);
64+
const regex = new RegExp(escaped, 'gi');
65+
const nodes: (string | JSX.Element)[] = [];
66+
let lastIndex = 0;
67+
let match: RegExpExecArray | null;
68+
let occurrence = 0;
69+
70+
while ((match = regex.exec(formula)) !== null) {
71+
const matchIndex = match.index;
72+
const matchText = match[0];
73+
74+
if (matchIndex > lastIndex) {
75+
nodes.push(formula.slice(lastIndex, matchIndex));
76+
}
77+
78+
nodes.push(
79+
<span
80+
key={`${matchIndex}-${occurrence}`}
81+
style={{ backgroundColor: theme.searchedText.highlightColor }}
82+
>
83+
{matchText}
84+
</span>
85+
);
86+
87+
lastIndex = matchIndex + matchText.length;
88+
occurrence += 1;
89+
90+
if (matchText.length === 0) {
91+
regex.lastIndex += 1;
92+
}
93+
}
94+
95+
if (lastIndex < formula.length) {
96+
nodes.push(formula.slice(lastIndex));
97+
}
98+
99+
return nodes.length > 0 ? nodes : formula;
100+
}, [searchTerm, theme.searchedText.highlightColor, value]);
101+
102+
return (
103+
<Box sx={styles.container}>
104+
<Box aria-hidden sx={overlaySx}>
105+
{highlighted}
106+
</Box>
107+
<ExpandingTextField name={name} label="" minRows={3} rows={3} sx={styles.textField} />
108+
</Box>
109+
);
15110
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
*/
7+
import React, { createContext, useContext, useState } from 'react';
8+
9+
export type FormulaSearchContextType = {
10+
searchTerm: string;
11+
setSearchTerm: (value: string) => void;
12+
searchResults: number[];
13+
setSearchResults: (value: number[]) => void;
14+
currentResultIndex: number;
15+
setCurrentResultIndex: (value: number) => void;
16+
};
17+
18+
const FormulaSearchContext = createContext<FormulaSearchContextType | undefined>(undefined);
19+
20+
export const FormulaSearchProvider = ({ children }: { children: React.ReactNode }) => {
21+
const [searchTerm, setSearchTerm] = useState('');
22+
const [searchResults, setSearchResults] = useState<number[]>([]);
23+
const [currentResultIndex, setCurrentResultIndex] = useState(-1);
24+
25+
return (
26+
<FormulaSearchContext.Provider
27+
value={{
28+
searchTerm,
29+
setSearchTerm,
30+
searchResults,
31+
setSearchResults,
32+
currentResultIndex,
33+
setCurrentResultIndex,
34+
}}
35+
>
36+
{children}
37+
</FormulaSearchContext.Provider>
38+
);
39+
};
40+
41+
// eslint-disable-next-line react-refresh/only-export-components
42+
export const useFormulaSearch = () => {
43+
const context = useContext(FormulaSearchContext);
44+
if (context === undefined) {
45+
throw new Error('useFormulaSearch must be used within a FormulaSearchProvider');
46+
}
47+
return context;
48+
};

0 commit comments

Comments
 (0)