@gridsuite/commons-ui 0.116.2 → 0.116.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/components/contingencyList/criteriaBased/CriteriaBasedForm.js +3 -0
  2. package/dist/components/dialogs/customMuiDialog/CustomMuiDialog.js +4 -3
  3. package/dist/components/dialogs/elementSaveDialog/ElementSaveDialog.js +35 -26
  4. package/dist/components/dialogs/elementSaveDialog/utils.d.ts +23 -0
  5. package/dist/components/dialogs/elementSaveDialog/utils.js +33 -0
  6. package/dist/components/directoryItemSelector/DirectoryItemSelector.d.ts +1 -1
  7. package/dist/components/directoryItemSelector/DirectoryItemSelector.js +72 -15
  8. package/dist/components/directoryItemSelector/utils.d.ts +52 -0
  9. package/dist/components/directoryItemSelector/utils.js +95 -0
  10. package/dist/components/dnd-table/dnd-table-add-rows-dialog.js +2 -2
  11. package/dist/components/filter/FilterForm.js +3 -0
  12. package/dist/components/filter/expert/ExpertFilterForm.js +3 -0
  13. package/dist/components/icons/ArrowsOutputIcon.d.ts +2 -0
  14. package/dist/components/icons/ArrowsOutputIcon.js +9 -0
  15. package/dist/components/icons/index.d.ts +1 -0
  16. package/dist/components/icons/index.js +2 -0
  17. package/dist/components/index.js +2 -0
  18. package/dist/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.d.ts +3 -2
  19. package/dist/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.js +9 -2
  20. package/dist/components/inputs/reactHookForm/selectInputs/SelectInput.d.ts +3 -2
  21. package/dist/components/inputs/reactHookForm/selectInputs/SelectInput.js +2 -0
  22. package/dist/components/parameters/common/ProviderParam.js +2 -2
  23. package/dist/components/parameters/common/limitreductions/limit-reduction-table-cell.js +2 -2
  24. package/dist/components/parameters/common/limitreductions/limit-reductions-table-form.js +2 -2
  25. package/dist/components/parameters/common/name-element-editor/name-element-editor-utils.js +1 -1
  26. package/dist/components/parameters/common/parameters-creation-dialog.js +2 -0
  27. package/dist/components/parameters/common/voltage-level-table/custom-voltage-level-table-cell.js +2 -2
  28. package/dist/components/parameters/common/widget/parameter-float.js +2 -2
  29. package/dist/components/parameters/common/widget/parameter-line-slider.js +2 -2
  30. package/dist/components/parameters/loadflow/load-flow-general-parameters.js +2 -2
  31. package/dist/components/parameters/loadflow/load-flow-parameter-field.js +2 -2
  32. package/dist/components/parameters/loadflow/load-flow-parameters-content.js +2 -2
  33. package/dist/components/parameters/loadflow/load-flow-parameters-dialog.js +4 -4
  34. package/dist/components/parameters/loadflow/load-flow-parameters-form.js +2 -2
  35. package/dist/components/parameters/loadflow/load-flow-parameters-header.js +2 -2
  36. package/dist/components/parameters/loadflow/load-flow-parameters-utils.js +3 -3
  37. package/dist/components/parameters/loadflow/use-load-flow-parameters-form.js +2 -2
  38. package/dist/components/parameters/network-visualizations/map-parameters.js +2 -2
  39. package/dist/components/parameters/network-visualizations/network-area-diagram-parameters.js +2 -2
  40. package/dist/components/parameters/network-visualizations/network-visualizations-form.js +2 -2
  41. package/dist/components/parameters/network-visualizations/single-line-diagram-parameters.js +2 -2
  42. package/dist/components/parameters/network-visualizations/use-network-visualizations-parameters-form.js +1 -1
  43. package/dist/components/parameters/security-analysis/security-analysis-parameters-dialog.js +2 -2
  44. package/dist/components/parameters/security-analysis/security-analysis-parameters-form.js +2 -2
  45. package/dist/components/parameters/security-analysis/security-analysis-parameters-selector.js +2 -2
  46. package/dist/components/parameters/security-analysis/security-analysis-violations-hiding.js +2 -2
  47. package/dist/components/parameters/sensi/sensitivity-Flow-parameters.js +2 -2
  48. package/dist/components/parameters/sensi/sensitivity-analysis-parameters-dialog.js +2 -2
  49. package/dist/components/parameters/sensi/sensitivity-analysis-parameters-form.js +2 -2
  50. package/dist/components/parameters/sensi/sensitivity-parameters-selector.js +2 -2
  51. package/dist/components/parameters/sensi/sensitivity-table.js +2 -2
  52. package/dist/components/parameters/sensi/use-sensitivity-analysis-parameters.js +2 -2
  53. package/dist/components/parameters/short-circuit/short-circuit-fields.js +2 -2
  54. package/dist/components/parameters/short-circuit/short-circuit-parameters-form.js +2 -2
  55. package/dist/components/parameters/short-circuit/use-short-circuit-parameters-form.js +1 -1
  56. package/dist/components/parameters/voltage-init/equipment-selection-parameters.js +2 -2
  57. package/dist/components/parameters/voltage-init/general-parameters.js +2 -2
  58. package/dist/components/parameters/voltage-init/use-voltage-init-parameters-form.js +1 -1
  59. package/dist/components/parameters/voltage-init/voltage-init-form-utils.js +2 -2
  60. package/dist/components/parameters/voltage-init/voltage-init-parameters-form.js +2 -2
  61. package/dist/components/treeViewFinder/TreeViewFinder.js +14 -13
  62. package/dist/hooks/useModificationLabelComputer.js +1 -0
  63. package/dist/index.js +9 -1
  64. package/dist/services/directory.d.ts +1 -0
  65. package/dist/services/directory.js +10 -2
  66. package/dist/services/index.js +2 -1
  67. package/dist/translations/en/networkModificationsEn.d.ts +1 -0
  68. package/dist/translations/en/networkModificationsEn.js +2 -1
  69. package/dist/translations/fr/networkModificationsFr.d.ts +1 -0
  70. package/dist/translations/fr/networkModificationsFr.js +2 -1
  71. package/dist/utils/constants/configConstants.d.ts +4 -0
  72. package/dist/utils/constants/configConstants.js +10 -0
  73. package/dist/utils/constants/index.d.ts +1 -0
  74. package/dist/utils/constants/index.js +5 -0
  75. package/dist/utils/index.js +5 -0
  76. package/dist/utils/types/modificationType.d.ts +5 -1
  77. package/dist/utils/types/modificationType.js +5 -0
  78. package/package.json +1 -1
@@ -12,6 +12,9 @@ import "../../dialogs/elementSaveDialog/ElementSaveDialog.js";
12
12
  import "react-intl";
13
13
  import "@mui/icons-material";
14
14
  import "../../treeViewFinder/TreeViewFinder.js";
15
+ import "../../../utils/conversionUtils.js";
16
+ import "../../../utils/types/equipmentType.js";
17
+ import "../../../utils/yupConfig.js";
15
18
  function CriteriaBasedForm({ equipments, defaultValues, children }) {
16
19
  const { getValues, setValue } = useFormContext();
17
20
  const { snackError } = useSnackMessage();
@@ -120,16 +120,17 @@ function CustomMuiDialog({
120
120
  ...dialogProps,
121
121
  children: [
122
122
  isDataFetching && /* @__PURE__ */ jsx(LinearProgress, {}),
123
- /* @__PURE__ */ jsx(DialogTitle, { children: /* @__PURE__ */ jsx(Grid, { item: true, xs: 11, children: /* @__PURE__ */ jsx(FormattedMessage, { id: titleId }) }) }),
123
+ /* @__PURE__ */ jsx(DialogTitle, { "data-testid": "DialogTitle", children: /* @__PURE__ */ jsx(Grid, { item: true, xs: 11, children: /* @__PURE__ */ jsx(FormattedMessage, { id: titleId }) }) }),
124
124
  /* @__PURE__ */ jsx(DialogContent, { sx: unscrollableFullHeight ? unscrollableDialogStyles.unscrollableContainer : null, children }),
125
125
  /* @__PURE__ */ jsxs(DialogActions, { children: [
126
- /* @__PURE__ */ jsx(CancelButton, { onClick: handleCancel }),
126
+ /* @__PURE__ */ jsx(CancelButton, { onClick: handleCancel, "data-testid": "CancelButton" }),
127
127
  /* @__PURE__ */ jsx(
128
128
  SubmitButton,
129
129
  {
130
130
  variant: "outlined",
131
131
  disabled: disabledSave,
132
- onClick: handleSubmit(handleValidate, handleValidationError)
132
+ onClick: handleSubmit(handleValidate, handleValidationError),
133
+ "data-testid": "ValidateButton"
133
134
  }
134
135
  )
135
136
  ] })
@@ -6,7 +6,6 @@ import { useForm } from "react-hook-form";
6
6
  import { yupResolver } from "@hookform/resolvers/yup";
7
7
  import "../../../utils/yupConfig.js";
8
8
  import "../../../utils/types/equipmentType.js";
9
- import { fetchDirectoryElementPath } from "../../../services/directory.js";
10
9
  import "localized-countries";
11
10
  import "localized-countries/data/fr";
12
11
  import "localized-countries/data/en";
@@ -36,6 +35,7 @@ import "uuid";
36
35
  import "../../inputs/reactQueryBuilder/PropertyValueEditor.js";
37
36
  import "react-querybuilder";
38
37
  import { CustomMuiDialog } from "../customMuiDialog/CustomMuiDialog.js";
38
+ import { initializeDirectory } from "./utils.js";
39
39
  var OperationType = /* @__PURE__ */ ((OperationType2) => {
40
40
  OperationType2["CREATE"] = "CREATE";
41
41
  OperationType2["UPDATE"] = "UPDATE";
@@ -73,6 +73,7 @@ function ElementSaveDialog({
73
73
  const [directorySelectorOpen, setDirectorySelectorOpen] = useState(false);
74
74
  const [destinationFolder, setDestinationFolder] = useState();
75
75
  const [selectedItem, setSelectedItem] = useState();
76
+ const [expanded, setExpanded] = useState([]);
76
77
  const formMethods = useForm({
77
78
  defaultValues: {
78
79
  ...emptyFormData,
@@ -89,8 +90,35 @@ function ElementSaveDialog({
89
90
  const operationType = createOnlyMode ? "CREATE" : watch(FieldConstants.OPERATION_TYPE);
90
91
  const isCreateMode = operationType === "CREATE";
91
92
  const disableSave = Object.keys(errors).length > 0 || isCreateMode && !destinationFolder || !isCreateMode && !selectedItem;
93
+ const setDestinationFolderWithPath = useCallback(
94
+ (elementUuid, elementName, path) => {
95
+ setDestinationFolder({
96
+ id: elementUuid,
97
+ name: elementName
98
+ });
99
+ if (path && path.length > 0) {
100
+ const expandPath = path.map((element) => element.elementUuid);
101
+ setExpanded(expandPath);
102
+ }
103
+ },
104
+ []
105
+ );
106
+ const initializeDestinationFolder = useCallback(async () => {
107
+ const config = {
108
+ studyUuid,
109
+ initDirectory,
110
+ onError: (messageTxt, headerId) => {
111
+ snackError({ messageTxt, headerId });
112
+ }
113
+ };
114
+ const result = await initializeDirectory(config);
115
+ if (result) {
116
+ setDestinationFolderWithPath(result.element.elementUuid, result.element.elementName, result.path);
117
+ }
118
+ }, [studyUuid, initDirectory, snackError, setDestinationFolderWithPath]);
92
119
  const onCancel = useCallback(() => {
93
120
  reset({ ...emptyFormData, [FieldConstants.OPERATION_TYPE]: initialOperation });
121
+ setExpanded([]);
94
122
  onClose();
95
123
  }, [onClose, reset, initialOperation]);
96
124
  useEffect(() => {
@@ -110,32 +138,11 @@ function ElementSaveDialog({
110
138
  }
111
139
  }, [prefixIdForGeneratedName, intl, reset, isCreateMode]);
112
140
  useEffect(() => {
113
- if (open && isCreateMode && studyUuid) {
114
- fetchDirectoryElementPath(studyUuid).then((res) => {
115
- if (!res || res.length < 2) {
116
- snackError({
117
- messageTxt: "unknown study directory",
118
- headerId: "studyDirectoryFetchingError"
119
- });
120
- return;
121
- }
122
- const parentFolderIndex = res.length - 2;
123
- const { elementUuid, elementName } = res[parentFolderIndex];
124
- setDestinationFolder({
125
- id: elementUuid,
126
- name: elementName
127
- });
128
- });
129
- }
130
- }, [studyUuid, open, snackError, isCreateMode]);
131
- useEffect(() => {
132
- if (open && isCreateMode && initDirectory) {
133
- setDestinationFolder({
134
- id: initDirectory.elementUuid,
135
- name: initDirectory.elementName
136
- });
141
+ if (!open || !isCreateMode) {
142
+ return;
137
143
  }
138
- }, [initDirectory, open, isCreateMode]);
144
+ initializeDestinationFolder();
145
+ }, [open, isCreateMode, initializeDestinationFolder]);
139
146
  const handleChangeFolder = useCallback(() => {
140
147
  setDirectorySelectorOpen(true);
141
148
  }, []);
@@ -145,6 +152,7 @@ function ElementSaveDialog({
145
152
  if ((items == null ? void 0 : items.length) > 0 && items[0].id !== (destinationFolder == null ? void 0 : destinationFolder.id)) {
146
153
  const { id, name } = items[0];
147
154
  setDestinationFolder({ id, name });
155
+ setExpanded([]);
148
156
  }
149
157
  } else if ((items == null ? void 0 : items.length) > 0 && items[0].id !== (selectedItem == null ? void 0 : selectedItem.id)) {
150
158
  const { id, name, description, parents } = items[0];
@@ -251,6 +259,7 @@ function ElementSaveDialog({
251
259
  types: isCreateMode ? [ElementType.DIRECTORY] : [type],
252
260
  onlyLeaves: isCreateMode ? false : void 0,
253
261
  multiSelect: false,
262
+ expanded: isCreateMode ? expanded : [],
254
263
  validationButtonText: intl.formatMessage({
255
264
  id: "validate"
256
265
  }),
@@ -0,0 +1,23 @@
1
+ import { UUID } from 'crypto';
2
+ import { ElementAttributes } from '../../../utils';
3
+ /**
4
+ * Generic directory initialization configuration
5
+ */
6
+ export interface DirectoryInitConfig {
7
+ studyUuid?: UUID;
8
+ initDirectory?: ElementAttributes;
9
+ onError?: (message: string, headerId: string) => void;
10
+ }
11
+ /**
12
+ * Generic destination directory initialization that follows the standard priority:
13
+ * 1. Last selected directory from localStorage
14
+ * 2. Study UUID (if provided)
15
+ * 3. Initial directory (if provided)
16
+ *
17
+ * @param config Configuration object
18
+ * @returns Promise resolving to { element, path } or null if all methods fail
19
+ */
20
+ export declare function initializeDirectory(config: DirectoryInitConfig): Promise<{
21
+ element: ElementAttributes;
22
+ path?: ElementAttributes[];
23
+ } | null>;
@@ -0,0 +1,33 @@
1
+ import { getLastSelectedDirectoryId, fetchDirectoryPathSafe, clearLastSelectedDirectory } from "../../directoryItemSelector/utils.js";
2
+ async function initializeDirectory(config) {
3
+ const { studyUuid, initDirectory, onError } = config;
4
+ const lastSelectedDirId = getLastSelectedDirectoryId();
5
+ if (lastSelectedDirId) {
6
+ const path = await fetchDirectoryPathSafe(lastSelectedDirId);
7
+ if (path && path.length > 0) {
8
+ const targetElement = path[path.length - 1];
9
+ return { element: targetElement, path };
10
+ }
11
+ await clearLastSelectedDirectory();
12
+ }
13
+ if (studyUuid) {
14
+ const path = await fetchDirectoryPathSafe(studyUuid);
15
+ if (path && path.length >= 2) {
16
+ const parentFolderIndex = path.length - 2;
17
+ const parentElement = path[parentFolderIndex];
18
+ return {
19
+ element: parentElement,
20
+ path: path.slice(0, parentFolderIndex + 1)
21
+ };
22
+ }
23
+ onError == null ? void 0 : onError("unknown study directory", "studyDirectoryFetchingError");
24
+ return null;
25
+ }
26
+ if (initDirectory) {
27
+ return { element: initDirectory };
28
+ }
29
+ return null;
30
+ }
31
+ export {
32
+ initializeDirectory
33
+ };
@@ -8,4 +8,4 @@ export interface DirectoryItemSelectorProps extends TreeViewFinderProps {
8
8
  expanded?: UUID[];
9
9
  selected?: UUID[];
10
10
  }
11
- export declare function DirectoryItemSelector({ open, types, equipmentTypes, itemFilter, expanded, selected, ...otherTreeViewFinderProps }: Readonly<DirectoryItemSelectorProps>): import("react/jsx-runtime").JSX.Element;
11
+ export declare function DirectoryItemSelector({ open, types, equipmentTypes, itemFilter, expanded, selected, onClose, ...otherTreeViewFinderProps }: Readonly<DirectoryItemSelectorProps>): import("react/jsx-runtime").JSX.Element;
@@ -6,6 +6,7 @@ import { TreeViewFinder } from "../treeViewFinder/TreeViewFinder.js";
6
6
  import { useSnackMessage } from "../../hooks/useSnackMessage.js";
7
7
  import { fetchElementsInfos } from "../../services/explore.js";
8
8
  import { fetchRootFolders, fetchDirectoryContent } from "../../services/directory.js";
9
+ import { getExpansionPathsForSelected, fetchChildrenForExpandedNodes, initializeFromLastSelected, saveLastSelectedDirectoryFromNode } from "./utils.js";
9
10
  const styles = {
10
11
  icon: (theme) => ({
11
12
  marginRight: theme.spacing(1),
@@ -108,10 +109,13 @@ function DirectoryItemSelector({
108
109
  itemFilter,
109
110
  expanded,
110
111
  selected,
112
+ onClose,
111
113
  ...otherTreeViewFinderProps
112
114
  }) {
113
115
  const [data, setData] = useState([]);
114
116
  const [rootDirectories, setRootDirectories] = useState([]);
117
+ const [isRootsLoaded, setIsRootsLoaded] = useState(false);
118
+ const [autoExpandedNodes, setAutoExpandedNodes] = useState([]);
115
119
  const nodeMap = useRef({});
116
120
  const dataRef = useRef([]);
117
121
  dataRef.current = data;
@@ -158,6 +162,7 @@ function DirectoryItemSelector({
158
162
  [convertRoots]
159
163
  );
160
164
  const updateRootDirectories = useCallback(() => {
165
+ setIsRootsLoaded(false);
161
166
  fetchRootFolders(types).then((newData) => {
162
167
  const [nrs, mdr] = updatedTree(rootsRef.current, nodeMap.current, null, newData);
163
168
  setRootDirectories(nrs);
@@ -168,6 +173,8 @@ function DirectoryItemSelector({
168
173
  messageTxt: error.message,
169
174
  headerId: "DirectoryItemSelector"
170
175
  });
176
+ }).finally(() => {
177
+ setIsRootsLoaded(true);
171
178
  });
172
179
  }, [convertRoots, types, snackError]);
173
180
  const fetchDirectoryChildren = useCallback(
@@ -200,24 +207,73 @@ function DirectoryItemSelector({
200
207
  },
201
208
  [types, equipmentTypes, itemFilter, contentFilter, addToDirectory]
202
209
  );
203
- useEffect(() => {
204
- if (open && expanded && selected) {
205
- const isSelectedItemFetched = selected.every((id) => nodeMap.current[id]);
206
- if (!isSelectedItemFetched) {
207
- expanded.forEach((nodeId) => {
208
- const node = nodeMap.current[nodeId];
209
- if ((node == null ? void 0 : node.children) && node.children.length === 0) {
210
- fetchDirectoryChildren(nodeId);
211
- }
212
- });
213
- }
210
+ const fetchNodeChildrenIfNeeded = useCallback(
211
+ (nodeId, delay = 0) => {
212
+ setTimeout(() => {
213
+ const node = nodeMap.current[nodeId];
214
+ if (node && (!node.children || node.children.length === 0) && node.type === ElementType.DIRECTORY) {
215
+ fetchDirectoryChildren(nodeId);
216
+ }
217
+ }, delay);
218
+ },
219
+ [fetchDirectoryChildren]
220
+ );
221
+ const handleSelectedExpansion = useCallback(async () => {
222
+ if (!selected || selected.length === 0) {
223
+ return false;
224
+ }
225
+ const expandedArray = await getExpansionPathsForSelected(selected, expanded);
226
+ setAutoExpandedNodes(expandedArray);
227
+ fetchChildrenForExpandedNodes(expandedArray, fetchNodeChildrenIfNeeded);
228
+ return true;
229
+ }, [selected, expanded, fetchNodeChildrenIfNeeded]);
230
+ const handleProvidedExpansion = useCallback(() => {
231
+ if (!expanded || expanded.length === 0) {
232
+ return false;
214
233
  }
215
- }, [open, expanded, fetchDirectoryChildren, selected, data]);
234
+ setAutoExpandedNodes(expanded);
235
+ fetchChildrenForExpandedNodes(expanded, fetchNodeChildrenIfNeeded);
236
+ return true;
237
+ }, [expanded, fetchNodeChildrenIfNeeded]);
238
+ const handleLastSelectedExpansion = useCallback(async () => {
239
+ const expandPath = await initializeFromLastSelected();
240
+ if (!expandPath) {
241
+ return false;
242
+ }
243
+ setAutoExpandedNodes(expandPath);
244
+ fetchChildrenForExpandedNodes(expandPath, fetchNodeChildrenIfNeeded);
245
+ return true;
246
+ }, [fetchNodeChildrenIfNeeded]);
247
+ const initializeExpansion = useCallback(async () => {
248
+ const selectedSuccess = await handleSelectedExpansion();
249
+ if (selectedSuccess) return;
250
+ const expandedSuccess = handleProvidedExpansion();
251
+ if (expandedSuccess) return;
252
+ await handleLastSelectedExpansion();
253
+ }, [handleSelectedExpansion, handleProvidedExpansion, handleLastSelectedExpansion]);
216
254
  useEffect(() => {
217
- if (open) {
255
+ if (!open) {
256
+ setIsRootsLoaded(false);
257
+ setAutoExpandedNodes([]);
258
+ return;
259
+ }
260
+ if (!isRootsLoaded) {
218
261
  updateRootDirectories();
262
+ return;
219
263
  }
220
- }, [open, updateRootDirectories]);
264
+ initializeExpansion();
265
+ }, [open, isRootsLoaded, updateRootDirectories, initializeExpansion]);
266
+ const handleClose = useCallback(
267
+ (nodes) => {
268
+ if (nodes && nodes.length > 0) {
269
+ const lastSelectedNode = nodes[0];
270
+ saveLastSelectedDirectoryFromNode(lastSelectedNode);
271
+ }
272
+ setAutoExpandedNodes([]);
273
+ onClose(nodes);
274
+ },
275
+ [onClose]
276
+ );
221
277
  return /* @__PURE__ */ jsx(
222
278
  TreeViewFinder,
223
279
  {
@@ -225,9 +281,10 @@ function DirectoryItemSelector({
225
281
  sortMethod: sortHandlingDirectories,
226
282
  multiSelect: true,
227
283
  open,
228
- expanded,
284
+ expanded: autoExpandedNodes,
229
285
  onlyLeaves: true,
230
286
  selected,
287
+ onClose: handleClose,
231
288
  ...otherTreeViewFinderProps,
232
289
  data
233
290
  }
@@ -0,0 +1,52 @@
1
+ import { UUID } from 'crypto';
2
+ import { ElementAttributes } from '../../utils';
3
+ /**
4
+ * Gets the last selected directory ID from localStorage with proper null handling
5
+ * @returns UUID or null if no valid directory ID is stored
6
+ */
7
+ export declare function getLastSelectedDirectoryId(): UUID | null;
8
+ /**
9
+ * Clears the last selected directory from both localStorage and backend
10
+ */
11
+ export declare function clearLastSelectedDirectory(): Promise<void>;
12
+ /**
13
+ * Saves a directory ID as the last selected directory
14
+ * @param directoryId The directory UUID to save
15
+ */
16
+ export declare function saveLastSelectedDirectory(directoryId: UUID): Promise<void>;
17
+ /**
18
+ * Fetches directory path with error handling and validation
19
+ * @param directoryId The directory UUID to fetch path for
20
+ * @returns Promise resolving to path array or null if failed
21
+ */
22
+ export declare function fetchDirectoryPathSafe(directoryId: UUID): Promise<ElementAttributes[] | null>;
23
+ /**
24
+ * Initializes directory expansion from last selected directory
25
+ * @returns Promise resolving to expansion path or null if failed
26
+ */
27
+ export declare function initializeFromLastSelected(): Promise<UUID[] | null>;
28
+ /**
29
+ * Fetches expansion paths for multiple selected items
30
+ * @param selectedIds Array of selected item UUIDs
31
+ * @param expanded Optional existing expanded nodes
32
+ * @returns Promise resolving to combined expansion array
33
+ */
34
+ export declare function getExpansionPathsForSelected(selectedIds: UUID[], expanded?: UUID[]): Promise<UUID[]>;
35
+ /**
36
+ * Saves the last selected directory from a TreeViewFinderNode
37
+ * @param node The selected node
38
+ */
39
+ export declare function saveLastSelectedDirectoryFromNode(node: {
40
+ id: UUID;
41
+ type?: string;
42
+ parents?: Array<{
43
+ id: UUID;
44
+ }>;
45
+ }): Promise<void>;
46
+ /**
47
+ * Fetches children for expanded nodes with staggered delay to avoid overwhelming the server
48
+ * @param expandedNodes Array of node UUIDs to fetch children for
49
+ * @param fetchChildrenCallback Function to fetch children for a single node
50
+ * @param delayBetweenRequests Delay in milliseconds between requests (default: 100ms)
51
+ */
52
+ export declare function fetchChildrenForExpandedNodes(expandedNodes: UUID[], fetchChildrenCallback: (nodeId: UUID, delay: number) => void, delayBetweenRequests?: number): void;
@@ -0,0 +1,95 @@
1
+ import { LAST_SELECTED_DIRECTORY } from "../../utils/constants/configConstants.js";
2
+ import "../../utils/conversionUtils.js";
3
+ import "react/jsx-runtime";
4
+ import "@mui/icons-material";
5
+ import { ElementType } from "../../utils/types/elementType.js";
6
+ import "../../utils/types/equipmentType.js";
7
+ import "../../utils/yupConfig.js";
8
+ import { updateConfigParameter, fetchDirectoryElementPath } from "../../services/directory.js";
9
+ function getLastSelectedDirectoryId() {
10
+ const lastSelectedDirId = localStorage.getItem(LAST_SELECTED_DIRECTORY);
11
+ if (!lastSelectedDirId || lastSelectedDirId === "null") {
12
+ return null;
13
+ }
14
+ return lastSelectedDirId;
15
+ }
16
+ async function clearLastSelectedDirectory() {
17
+ localStorage.removeItem(LAST_SELECTED_DIRECTORY);
18
+ try {
19
+ await updateConfigParameter(LAST_SELECTED_DIRECTORY, "null");
20
+ } catch (error) {
21
+ console.error("Failed to clear last selected directory:", error);
22
+ }
23
+ }
24
+ async function saveLastSelectedDirectory(directoryId) {
25
+ try {
26
+ await updateConfigParameter(LAST_SELECTED_DIRECTORY, directoryId);
27
+ } catch (error) {
28
+ console.error("Failed to save last selected directory:", error);
29
+ }
30
+ }
31
+ async function fetchDirectoryPathSafe(directoryId) {
32
+ try {
33
+ const path = await fetchDirectoryElementPath(directoryId);
34
+ if (!path || path.length === 0) {
35
+ return null;
36
+ }
37
+ return path;
38
+ } catch (error) {
39
+ console.warn(`Failed to fetch directory path for ${directoryId}:`, error);
40
+ return null;
41
+ }
42
+ }
43
+ async function initializeFromLastSelected() {
44
+ const lastSelectedDirId = getLastSelectedDirectoryId();
45
+ if (!lastSelectedDirId) {
46
+ return null;
47
+ }
48
+ const path = await fetchDirectoryPathSafe(lastSelectedDirId);
49
+ if (!path) {
50
+ await clearLastSelectedDirectory();
51
+ return null;
52
+ }
53
+ return path.map((element) => element.elementUuid);
54
+ }
55
+ async function getExpansionPathsForSelected(selectedIds, expanded = []) {
56
+ const expandedSet = new Set(expanded);
57
+ const fetchPromises = selectedIds.map(async (selectedId) => {
58
+ const path = await fetchDirectoryPathSafe(selectedId);
59
+ if (path && path.length > 0) {
60
+ path.forEach((element, index) => {
61
+ if (index < path.length - 1) {
62
+ expandedSet.add(element.elementUuid);
63
+ }
64
+ });
65
+ }
66
+ });
67
+ await Promise.all(fetchPromises);
68
+ return Array.from(expandedSet);
69
+ }
70
+ async function saveLastSelectedDirectoryFromNode(node) {
71
+ let lastSelectedDirId;
72
+ if (node.type === ElementType.DIRECTORY || node.type === void 0) {
73
+ lastSelectedDirId = node.id;
74
+ } else if (node.parents && node.parents.length > 0) {
75
+ lastSelectedDirId = node.parents[node.parents.length - 1].id;
76
+ }
77
+ if (lastSelectedDirId) {
78
+ await saveLastSelectedDirectory(lastSelectedDirId);
79
+ }
80
+ }
81
+ function fetchChildrenForExpandedNodes(expandedNodes, fetchChildrenCallback, delayBetweenRequests = 100) {
82
+ expandedNodes.forEach((nodeId, index) => {
83
+ fetchChildrenCallback(nodeId, index * delayBetweenRequests);
84
+ });
85
+ }
86
+ export {
87
+ clearLastSelectedDirectory,
88
+ fetchChildrenForExpandedNodes,
89
+ fetchDirectoryPathSafe,
90
+ getExpansionPathsForSelected,
91
+ getLastSelectedDirectoryId,
92
+ initializeFromLastSelected,
93
+ saveLastSelectedDirectory,
94
+ saveLastSelectedDirectoryFromNode
95
+ };
@@ -14,14 +14,14 @@ import "@mui/icons-material";
14
14
  import "yup";
15
15
  import "../overflowableText/OverflowableText.js";
16
16
  import "../treeViewFinder/TreeViewFinder.js";
17
+ import "../../utils/conversionUtils.js";
18
+ import "../../utils/yupConfig.js";
17
19
  import "../inputs/reactHookForm/agGridTable/BottomRightButtons.js";
18
20
  import "../customAGGrid/customAggrid.js";
19
21
  import "ag-grid-community";
20
22
  import "react-papaparse";
21
23
  import "react-csv-downloader";
22
24
  import "../inputs/reactHookForm/numbers/RangeInput.js";
23
- import "../../utils/conversionUtils.js";
24
- import "../../utils/yupConfig.js";
25
25
  import "@react-querybuilder/material";
26
26
  import "../filter/expert/expertFilterConstants.js";
27
27
  import "../inputs/reactQueryBuilder/CustomReactQueryBuilder.js";
@@ -13,6 +13,9 @@ import "react-hook-form";
13
13
  import "@mui/icons-material";
14
14
  import "../treeViewFinder/TreeViewFinder.js";
15
15
  import "notistack";
16
+ import "../../utils/conversionUtils.js";
17
+ import "../../utils/types/equipmentType.js";
18
+ import "../../utils/yupConfig.js";
16
19
  function FilterForm({
17
20
  sourceFilterForExplicitNamingConversion,
18
21
  creation,
@@ -19,7 +19,10 @@ import "react-intl";
19
19
  import "@mui/icons-material";
20
20
  import "../../treeViewFinder/TreeViewFinder.js";
21
21
  import "notistack";
22
+ import "../../../utils/conversionUtils.js";
23
+ import "../../../utils/types/equipmentType.js";
22
24
  import { FieldType } from "../../../utils/types/fieldType.js";
25
+ import "../../../utils/yupConfig.js";
23
26
  import { useFormatLabelWithUnit } from "../../../hooks/useFormatLabelWithUnit.js";
24
27
  import { filterStyles } from "../HeaderFilterForm.js";
25
28
  yup.setLocale({
@@ -0,0 +1,2 @@
1
+ import { SvgIconProps } from '@mui/material';
2
+ export declare function ArrowsOutputIcon(props: SvgIconProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { SvgIcon } from "@mui/material";
3
+ import ArrowsOutput from "@material-symbols/svg-400/outlined/arrows_output.svg?react";
4
+ function ArrowsOutputIcon(props) {
5
+ return /* @__PURE__ */ jsx(SvgIcon, { component: ArrowsOutput, inheritViewBox: true, ...props });
6
+ }
7
+ export {
8
+ ArrowsOutputIcon
9
+ };
@@ -5,5 +5,6 @@
5
5
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
6
  */
7
7
  export { LeftPanelOpenIcon } from './LeftPanelOpenIcon';
8
+ export { ArrowsOutputIcon } from './ArrowsOutputIcon';
8
9
  export { LeftPanelCloseIcon } from './LeftPanelCloseIcon';
9
10
  export { DeviceHubIcon } from './DeviceHubIcon';
@@ -1,7 +1,9 @@
1
1
  import { LeftPanelOpenIcon } from "./LeftPanelOpenIcon.js";
2
+ import { ArrowsOutputIcon } from "./ArrowsOutputIcon.js";
2
3
  import { LeftPanelCloseIcon } from "./LeftPanelCloseIcon.js";
3
4
  import { DeviceHubIcon } from "./DeviceHubIcon.js";
4
5
  export {
6
+ ArrowsOutputIcon,
5
7
  DeviceHubIcon,
6
8
  LeftPanelCloseIcon,
7
9
  LeftPanelOpenIcon
@@ -125,6 +125,7 @@ import { NotificationsContext } from "./notifications/contexts/NotificationsCont
125
125
  import { useNotificationsListener } from "./notifications/hooks/useNotificationsListener.js";
126
126
  import { useListenerManager } from "./notifications/hooks/useListenerManager.js";
127
127
  import { LeftPanelOpenIcon } from "./icons/LeftPanelOpenIcon.js";
128
+ import { ArrowsOutputIcon } from "./icons/ArrowsOutputIcon.js";
128
129
  import { LeftPanelCloseIcon } from "./icons/LeftPanelCloseIcon.js";
129
130
  import { DeviceHubIcon } from "./icons/DeviceHubIcon.js";
130
131
  import { ComputingType, formatComputingTypeLabel, isValidComputingType } from "./parameters/common/computing-type.js";
@@ -173,6 +174,7 @@ export {
173
174
  AddButton,
174
175
  AnnouncementBanner,
175
176
  AnnouncementNotification,
177
+ ArrowsOutputIcon,
176
178
  AuthenticationRouter,
177
179
  default2 as AuthenticationRouterErrorDisplay,
178
180
  AutocompleteInput,
@@ -1,5 +1,5 @@
1
1
  import { AutocompleteProps, TextFieldProps } from '@mui/material';
2
- import { Option } from '../../../../utils/types/types';
2
+ import { Option } from '../../../../utils';
3
3
  export interface AutocompleteInputProps extends Omit<AutocompleteProps<Option, boolean | undefined, boolean | undefined, boolean | undefined>, 'value' | 'onChange' | 'renderInput'> {
4
4
  name: string;
5
5
  options: Option[];
@@ -12,8 +12,9 @@ export interface AutocompleteInputProps extends Omit<AutocompleteProps<Option, b
12
12
  onChangeCallback?: () => void;
13
13
  formProps?: Omit<TextFieldProps, 'value' | 'onChange' | 'inputRef' | 'inputProps' | 'InputProps'>;
14
14
  disabledTooltip?: boolean;
15
+ onCheckNewValue?: (value: Option | null) => boolean;
15
16
  }
16
17
  export declare function AutocompleteInput({ name, label, options, outputTransform, // transform materialUi input value before sending it to react hook form, mostly used to deal with select fields that need to return a string
17
18
  inputTransform, // transform react hook form value before sending it to materialUi input, mostly used to deal with select fields that need to return a string
18
19
  readOnly, previousValue, allowNewValue, onChangeCallback, // method called when input value is changing
19
- formProps, disabledTooltip, ...props }: AutocompleteInputProps): import("react/jsx-runtime").JSX.Element;
20
+ formProps, disabledTooltip, onCheckNewValue, ...props }: AutocompleteInputProps): import("react/jsx-runtime").JSX.Element;
@@ -2,10 +2,13 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { Autocomplete, TextField } from "@mui/material";
4
4
  import { useController } from "react-hook-form";
5
- import { identity, genHelperError, isFieldRequired } from "../utils/functions.js";
5
+ import "react-intl";
6
6
  import { FieldLabel } from "../utils/FieldLabel.js";
7
- import { useCustomFormContext } from "../provider/useCustomFormContext.js";
7
+ import "@mui/icons-material";
8
+ import { identity, genHelperError, isFieldRequired } from "../utils/functions.js";
8
9
  import { HelperPreviousValue } from "../utils/HelperPreviousValue.js";
10
+ import "../provider/CustomFormProvider.js";
11
+ import { useCustomFormContext } from "../provider/useCustomFormContext.js";
9
12
  function AutocompleteInput({
10
13
  name,
11
14
  label,
@@ -21,6 +24,7 @@ function AutocompleteInput({
21
24
  // method called when input value is changing
22
25
  formProps,
23
26
  disabledTooltip,
27
+ onCheckNewValue,
24
28
  ...props
25
29
  }) {
26
30
  const { validationSchema, getValues, removeOptional, isNodeBuilt, isUpdate } = useCustomFormContext();
@@ -33,6 +37,9 @@ function AutocompleteInput({
33
37
  if ((currentValue == null ? void 0 : currentValue.id) === newValue) {
34
38
  return;
35
39
  }
40
+ if (!(onCheckNewValue == null ? void 0 : onCheckNewValue(newValue))) {
41
+ return;
42
+ }
36
43
  onChangeCallback == null ? void 0 : onChangeCallback();
37
44
  if (!allowNewValue || typeof newValue !== "string") {
38
45
  onChange(outputTransform(newValue));
@@ -1,6 +1,7 @@
1
- import { AutocompleteInputProps } from '../autocompleteInputs/AutocompleteInput';
2
- import { Option } from '../../../../utils/types/types';
1
+ import { AutocompleteInputProps } from '../autocompleteInputs';
2
+ import { Option } from '../../../../utils';
3
3
  export interface SelectInputProps extends Omit<AutocompleteInputProps, 'outputTransform' | 'inputTransform' | 'readOnly' | 'getOptionLabel'> {
4
4
  options: Option[];
5
+ onCheckNewValue?: (value: Option | null) => boolean;
5
6
  }
6
7
  export declare function SelectInput(props: SelectInputProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useIntl } from "react-intl";
3
3
  import { AutocompleteInput } from "../autocompleteInputs/AutocompleteInput.js";
4
+ import "react";
5
+ import "react-hook-form";
4
6
  function SelectInput(props) {
5
7
  const intl = useIntl();
6
8
  const { options } = props;