@topconsultnpm/sdkui-react-beta 6.13.34 → 6.13.35
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.
- package/lib/components/base/Styled.d.ts +0 -1
- package/lib/components/base/Styled.js +11 -19
- package/lib/components/base/TMPanel.js +4 -4
- package/lib/components/base/TMPanelManager.d.ts +9 -0
- package/lib/components/base/TMPanelManager.js +465 -0
- package/lib/components/base/TMPanelManagerToolbar.d.ts +16 -0
- package/lib/components/base/TMPanelManagerToolbar.js +100 -0
- package/lib/components/base/TMPanelManagerUtils.d.ts +36 -0
- package/lib/components/base/TMPanelManagerUtils.js +27 -0
- package/lib/components/features/documents/TMDcmtForm.js +7 -5
- package/lib/components/features/documents/TMDcmtPreview.js +1 -1
- package/lib/components/features/search/TMSearchQueryPanel.js +1 -0
- package/lib/components/features/search/TMSearchResult.js +5 -3
- package/lib/components/index.d.ts +3 -2
- package/lib/components/index.js +3 -2
- package/lib/components/sidebar/TMCommandsPanel.js +1 -1
- package/package.json +1 -1
- package/lib/components/base/TMPanelManagerMatrix.d.ts +0 -9
- package/lib/components/base/TMPanelManagerMatrix.js +0 -262
- package/lib/components/base/TMPanelManagerMatrixUtils.d.ts +0 -69
- package/lib/components/base/TMPanelManagerMatrixUtils.js +0 -155
@@ -11,7 +11,6 @@ export declare const StyledPanelPage: import("styled-components/dist/types").ISt
|
|
11
11
|
$isOpen?: boolean;
|
12
12
|
$padding?: string;
|
13
13
|
$background?: string;
|
14
|
-
$withMenu?: boolean;
|
15
14
|
}>> & string;
|
16
15
|
export declare const StyledOffCanvasPanel: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
17
16
|
$isOpen: boolean;
|
@@ -36,26 +36,18 @@ export const StyledToolbarForm = styled.div `
|
|
36
36
|
gap: 2px;
|
37
37
|
background-color: ${TMColors.toolbar_background};
|
38
38
|
`;
|
39
|
-
export const StyledPanelPage = styled.div `
|
40
|
-
position: absolute;
|
41
|
-
width: calc(100% - 50px);
|
42
|
-
top: ${(props) => props.$isOpen ? '50px' : '-9999px'};
|
43
|
-
left: ${(props) => props.$isOpen ? '50px' : '-9999px'};
|
44
|
-
visibility: ${(props) => props.$isOpen ? 'visible' : 'hidden'};
|
45
|
-
bottom: 0;
|
46
|
-
border-radius: 0px;
|
47
|
-
background: ${(props) => props.$background ?? 'transparent'};
|
48
|
-
z-index: 1500;
|
39
|
+
export const StyledPanelPage = styled.div `
|
40
|
+
position: absolute;
|
41
|
+
width: calc(100% - 50px);
|
42
|
+
top: ${(props) => props.$isOpen ? '50px' : '-9999px'};
|
43
|
+
left: ${(props) => props.$isOpen ? '50px' : '-9999px'};
|
44
|
+
visibility: ${(props) => props.$isOpen ? 'visible' : 'hidden'};
|
45
|
+
bottom: 0;
|
46
|
+
border-radius: 0px;
|
47
|
+
background: ${(props) => props.$background ?? 'transparent'};
|
48
|
+
z-index: 1500;
|
49
49
|
overflow: hidden;
|
50
|
-
|
51
|
-
@media (min-width: 768px) {
|
52
|
-
padding: ${(props) => props.$padding ?? `${Gutters.defaultDesktop}px`};
|
53
|
-
padding-right: ${(props) => props.$withMenu ? '0' : 'auto'};
|
54
|
-
}
|
55
|
-
|
56
|
-
@media (max-width: 767px) {
|
57
|
-
padding: ${(props) => props.$padding ?? `${Gutters.defaultMobile}px`};
|
58
|
-
}
|
50
|
+
padding: ${props => props.$padding ?? `${Gutters.getGutters()}px`};
|
59
51
|
`;
|
60
52
|
export const StyledOffCanvasPanel = styled.div `
|
61
53
|
position: fixed;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
3
3
|
import styled from 'styled-components';
|
4
|
-
import { IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber,
|
4
|
+
import { IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Localizator } from '../../helper';
|
5
5
|
import TMButton from './TMButton';
|
6
6
|
const StyledPanelContainer = styled.div `
|
7
7
|
width: 100%;
|
@@ -16,10 +16,10 @@ const StyledPanelContainer = styled.div `
|
|
16
16
|
position: ${({ $isMaximized }) => $isMaximized ? 'fixed' : 'relative'};
|
17
17
|
top: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
|
18
18
|
left: ${({ $isMaximized }) => $isMaximized ? '50px' : 'auto'};
|
19
|
-
width: ${({ $isMaximized }) => $isMaximized ?
|
20
|
-
height: ${({ $isMaximized }) => $isMaximized ?
|
19
|
+
width: ${({ $isMaximized }) => $isMaximized ? 'calc(100vw - 90px)' : '100%'};
|
20
|
+
height: ${({ $isMaximized }) => $isMaximized ? 'calc(100vh - 90px)' : '100%'};
|
21
21
|
z-index: ${({ $isMaximized }) => $isMaximized ? 2000 : 'auto'};
|
22
|
-
margin: ${({ $isMaximized }) => $isMaximized ?
|
22
|
+
margin: ${({ $isMaximized }) => $isMaximized ? '20px' : '0'};
|
23
23
|
/* transition: all 0.2s; */
|
24
24
|
`;
|
25
25
|
const StyledPanelHeader = styled.div `
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { TMPanelItemConfig } from './TMPanelManagerUtils';
|
2
|
+
type TMPanelManagerProps = {
|
3
|
+
panels: Array<TMPanelItemConfig>;
|
4
|
+
initialMobilePanelId: string;
|
5
|
+
showToolbar?: boolean;
|
6
|
+
gutters?: number;
|
7
|
+
};
|
8
|
+
declare const TMPanelManager: (props: TMPanelManagerProps) => import("react/jsx-runtime").JSX.Element;
|
9
|
+
export default TMPanelManager;
|
@@ -0,0 +1,465 @@
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
3
|
+
import { flattenPanels, isAtLeastOnePanelVisible, isPanelArray } from './TMPanelManagerUtils';
|
4
|
+
import { SDKUI_Globals, IconInfo, SDKUI_Localizator } from '../../helper';
|
5
|
+
import { useDeviceType, DeviceType } from './TMDeviceProvider';
|
6
|
+
import TMPanel from './TMPanel';
|
7
|
+
import TMPanelManagerToolbar from './TMPanelManagerToolbar';
|
8
|
+
const TMPanelManager = (props) => {
|
9
|
+
const { panels, initialMobilePanelId, showToolbar = true, gutters = SDKUI_Globals.userSettings.themeSettings.gutters } = props;
|
10
|
+
const deviceType = useDeviceType();
|
11
|
+
let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
|
12
|
+
const allPanels = useMemo(() => flattenPanels(panels), [panels]);
|
13
|
+
const panelMap = useMemo(() => {
|
14
|
+
const map = new Map();
|
15
|
+
allPanels.forEach(p => map.set(p.fullId, p));
|
16
|
+
return map;
|
17
|
+
}, [allPanels]);
|
18
|
+
const panelRefs = useRef({});
|
19
|
+
// Active buttons state & panel map
|
20
|
+
const [activeButtons, setActiveButtons] = useState(() => Object.fromEntries(allPanels.map(p => [p.fullId, p.config.toolbarOptions?.isActive ?? false])));
|
21
|
+
const [originalSizes, setOriginalSizes] = useState(new Map());
|
22
|
+
const [panelsState, setPanelsState] = useState(new Map());
|
23
|
+
const [maximizedPanelPath, setMaximizedPanelPath] = useState([]);
|
24
|
+
const [isRowDragging, setIsRowDragging] = useState(null);
|
25
|
+
const [isColumnDragging, setIsColumnDragging] = useState(null);
|
26
|
+
const [updatedDisabledButton, setUpdatedDisabledButton] = useState(undefined);
|
27
|
+
const [updatedVisibleButton, setUpdatedVisibleButton] = useState(undefined);
|
28
|
+
// Initialize two new Maps to store panel state and their original sizes
|
29
|
+
useEffect(() => {
|
30
|
+
const newState = new Map();
|
31
|
+
const newOriginalSizes = new Map();
|
32
|
+
// Recursive function to initialize panel map data
|
33
|
+
const initPanelsMap = (panels, parentPath = '') => {
|
34
|
+
panels.forEach(panel => {
|
35
|
+
// Skip if the panel has no contentOptions
|
36
|
+
if (!panel.contentOptions)
|
37
|
+
return;
|
38
|
+
// Construct a unique full ID for each panel based on its parent path
|
39
|
+
const fullId = parentPath ? `${parentPath}.${panel.id}` : panel.id;
|
40
|
+
// Use default values '0' if width/height is not provided
|
41
|
+
const width = panel.contentOptions.width;
|
42
|
+
const height = panel.contentOptions.height;
|
43
|
+
// Check if the panel contains nested children
|
44
|
+
const hasChildren = Array.isArray(panel.contentOptions.content);
|
45
|
+
// Store the original width and height for future reference
|
46
|
+
newOriginalSizes.set(fullId, { width, height });
|
47
|
+
// Determine the layout orientation
|
48
|
+
// A panel is considered a "column" layout if height is 100% and width isn't.
|
49
|
+
// A panel is considered a "row" layout if width is 100% and height isn't.
|
50
|
+
const isColumn = height === '100%' && width !== '100%';
|
51
|
+
const isRow = width === '100%' && height !== '100%';
|
52
|
+
// Save the current panel's state including dimensions and layout direction
|
53
|
+
newState.set(fullId, { width, height, isColumn, isRow, });
|
54
|
+
// If the panel has nested children, recursively initialize their states
|
55
|
+
if (hasChildren) {
|
56
|
+
initPanelsMap(panel.contentOptions.content, fullId);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
};
|
60
|
+
initPanelsMap(panels);
|
61
|
+
setOriginalSizes(newOriginalSizes);
|
62
|
+
setPanelsState(newState);
|
63
|
+
}, [panels]);
|
64
|
+
useEffect(() => {
|
65
|
+
// If there are no original sizes available, exit early
|
66
|
+
if (!originalSizes.size)
|
67
|
+
return;
|
68
|
+
// Clone the current state of the panels into a new Map to modify it safely
|
69
|
+
const newState = new Map(panelsState);
|
70
|
+
// Helper function to check if a panel is currently maximized
|
71
|
+
const isMaximized = (panelId) => maximizedPanelPath.includes(panelId);
|
72
|
+
// Iterate through all panel sizes stored in originalSizes
|
73
|
+
for (const [panelId, sizes] of originalSizes.entries()) {
|
74
|
+
const currentPanel = newState.get(panelId);
|
75
|
+
if (!currentPanel) {
|
76
|
+
// If for some reason the panel doesn't exist in state, skip it
|
77
|
+
continue;
|
78
|
+
}
|
79
|
+
if (isMaximized(panelId)) {
|
80
|
+
// If the panel is maximized, set its dimensions to take up the full available space
|
81
|
+
newState.set(panelId, { ...currentPanel, width: '100%', height: '100%' });
|
82
|
+
}
|
83
|
+
else if (maximizedPanelPath.length > 0) {
|
84
|
+
// If at least one panel is maximized, collapse all others to zero size
|
85
|
+
newState.set(panelId, { ...currentPanel, width: '0', height: '0' });
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
// If no panels are maximized, restore their original dimensions
|
89
|
+
newState.set(panelId, { ...currentPanel, width: sizes.width, height: sizes.height });
|
90
|
+
}
|
91
|
+
}
|
92
|
+
// Apply the updated panel states
|
93
|
+
setPanelsState(newState);
|
94
|
+
// Run this effect whenever maximizedPanelPath or originalSizes change
|
95
|
+
}, [maximizedPanelPath, originalSizes]);
|
96
|
+
useEffect(() => {
|
97
|
+
const isDragging = isColumnDragging || isRowDragging;
|
98
|
+
// Handles the end of dragging (mouse release)
|
99
|
+
const onMouseUp = () => {
|
100
|
+
setIsColumnDragging(null);
|
101
|
+
setIsRowDragging(null);
|
102
|
+
document.body.style.userSelect = '';
|
103
|
+
};
|
104
|
+
// Handles column resizing during dragging
|
105
|
+
const onColumnMouseMove = (e) => {
|
106
|
+
if (!isColumnDragging)
|
107
|
+
return;
|
108
|
+
const columnKeys = getKeysByOrientation('column');
|
109
|
+
const currentIndex = columnKeys.indexOf(isColumnDragging);
|
110
|
+
const nextKey = columnKeys[currentIndex + 1];
|
111
|
+
if (!nextKey)
|
112
|
+
return;
|
113
|
+
const deltaPercent = (e.movementX / window.innerWidth) * 100;
|
114
|
+
resizePanels(columnKeys, currentIndex, nextKey, deltaPercent, 'width');
|
115
|
+
};
|
116
|
+
// Handles row resizing during dragging
|
117
|
+
const onRowMouseMove = (e) => {
|
118
|
+
if (!isRowDragging)
|
119
|
+
return;
|
120
|
+
const rowKeys = getKeysByOrientation('row');
|
121
|
+
const currentIndex = rowKeys.indexOf(isRowDragging);
|
122
|
+
const nextKey = rowKeys[currentIndex + 1];
|
123
|
+
if (!nextKey)
|
124
|
+
return;
|
125
|
+
const deltaPercent = (e.movementY / window.innerHeight) * 100;
|
126
|
+
resizePanels(rowKeys, currentIndex, nextKey, deltaPercent, 'height');
|
127
|
+
};
|
128
|
+
// Returns panel keys that match the given orientation (row or column)
|
129
|
+
const getKeysByOrientation = (type) => {
|
130
|
+
return Array.from(panelsState.entries())
|
131
|
+
.filter(([_, value]) => (type === 'row' ? value.isRow : value.isColumn))
|
132
|
+
.map(([key]) => key);
|
133
|
+
};
|
134
|
+
// Shared resizing logic for both rows and columns
|
135
|
+
const resizePanels = (keys, currentIndex, nextKey, deltaPercent, dimension) => {
|
136
|
+
setPanelsState(prev => {
|
137
|
+
const updated = new Map(prev);
|
138
|
+
const updatedOriginalSizes = new Map(originalSizes);
|
139
|
+
const currentKey = keys[currentIndex];
|
140
|
+
const currentSize = parseFloat(updated.get(currentKey)?.[dimension] || '0');
|
141
|
+
const nextSize = parseFloat(updated.get(nextKey)?.[dimension] || '0');
|
142
|
+
const total = currentSize + nextSize;
|
143
|
+
let newCurrent = Math.min(currentSize + deltaPercent, 100);
|
144
|
+
let newNext = Math.min(nextSize - deltaPercent, 100);
|
145
|
+
// Enforce minimum panel size
|
146
|
+
if (newCurrent < 5) {
|
147
|
+
newCurrent = 5;
|
148
|
+
newNext = total - newCurrent;
|
149
|
+
}
|
150
|
+
else if (newNext < 5) {
|
151
|
+
newNext = 5;
|
152
|
+
newCurrent = total - newNext;
|
153
|
+
}
|
154
|
+
// Safely get current and next panel data
|
155
|
+
const currentPanel = updated.get(currentKey);
|
156
|
+
const nextPanel = updated.get(nextKey);
|
157
|
+
// Ensure both panels exist before updating
|
158
|
+
if (!currentPanel || !nextPanel)
|
159
|
+
return prev;
|
160
|
+
// Update panel dimensions
|
161
|
+
updated.set(currentKey, { ...currentPanel, [dimension]: `${newCurrent}%` });
|
162
|
+
updated.set(nextKey, { ...nextPanel, [dimension]: `${newNext}%` });
|
163
|
+
// Update original sizes for persistence
|
164
|
+
updatedOriginalSizes.set(currentKey, { ...currentPanel, [dimension]: `${newCurrent}%` });
|
165
|
+
updatedOriginalSizes.set(nextKey, { ...nextPanel, [dimension]: `${newNext}%` });
|
166
|
+
setOriginalSizes(updatedOriginalSizes);
|
167
|
+
return updated;
|
168
|
+
});
|
169
|
+
};
|
170
|
+
// Add event listeners when dragging starts
|
171
|
+
if (isDragging) {
|
172
|
+
if (isColumnDragging) {
|
173
|
+
window.addEventListener('mousemove', onColumnMouseMove);
|
174
|
+
}
|
175
|
+
else if (isRowDragging) {
|
176
|
+
window.addEventListener('mousemove', onRowMouseMove);
|
177
|
+
}
|
178
|
+
window.addEventListener('mouseup', onMouseUp);
|
179
|
+
document.body.style.userSelect = 'none'; // Prevents text selection during drag
|
180
|
+
}
|
181
|
+
// Cleanup function to remove event listeners
|
182
|
+
return () => {
|
183
|
+
window.removeEventListener('mousemove', onColumnMouseMove);
|
184
|
+
window.removeEventListener('mousemove', onRowMouseMove);
|
185
|
+
window.removeEventListener('mouseup', onMouseUp);
|
186
|
+
document.body.style.userSelect = '';
|
187
|
+
};
|
188
|
+
}, [isColumnDragging, isRowDragging, panelsState, originalSizes]);
|
189
|
+
useEffect(() => {
|
190
|
+
if (isMobile) {
|
191
|
+
setActiveButtons(prev => {
|
192
|
+
// Clone the previous state to avoid mutating it directly
|
193
|
+
const newActive = { ...prev };
|
194
|
+
// Get the panel object from the panel map
|
195
|
+
const panel = panelMap.get(initialMobilePanelId);
|
196
|
+
Object.keys(newActive).forEach(panelId => {
|
197
|
+
newActive[panelId] = false;
|
198
|
+
});
|
199
|
+
// Toggle the selected panel's visibility
|
200
|
+
newActive[initialMobilePanelId] = true;
|
201
|
+
// If the panel is being hidden and has children, recursively hide its children
|
202
|
+
if (panel && panel.childrenIds.length > 0 && !newActive[initialMobilePanelId]) {
|
203
|
+
const hideChildrenRecursively = (ids) => {
|
204
|
+
ids.forEach(childId => {
|
205
|
+
newActive[childId] = false;
|
206
|
+
const childPanel = panelMap.get(childId);
|
207
|
+
if (childPanel?.childrenIds.length) {
|
208
|
+
hideChildrenRecursively(childPanel.childrenIds);
|
209
|
+
}
|
210
|
+
});
|
211
|
+
};
|
212
|
+
hideChildrenRecursively(panel.childrenIds);
|
213
|
+
}
|
214
|
+
// After toggling, ensure the visibility of parent panels is in sync with their children
|
215
|
+
if (panel?.parentId) {
|
216
|
+
syncParentVisibilityWithChildren(panel.parentId, newActive);
|
217
|
+
}
|
218
|
+
// Return the updated visibility map
|
219
|
+
return newActive;
|
220
|
+
});
|
221
|
+
}
|
222
|
+
}, [isMobile]);
|
223
|
+
// Handler for starting to drag a column divider (for resizing panels horizontally)
|
224
|
+
const onColumnMouseDown = (panelKey) => {
|
225
|
+
setIsColumnDragging(panelKey); // Set the state to indicate which column is being dragged
|
226
|
+
};
|
227
|
+
// Handler for starting to drag a row divider (for resizing panels vertically)
|
228
|
+
const onRowMouseDown = (rowKey) => {
|
229
|
+
setIsRowDragging(rowKey); // Set the state to indicate which row is being dragged
|
230
|
+
};
|
231
|
+
// Function to disable a toolbar button by its ID
|
232
|
+
const handleDisableButton = (id, value) => {
|
233
|
+
// Update state to inform toolbar which button should be disabled/enabled
|
234
|
+
setUpdatedDisabledButton({ id, value });
|
235
|
+
};
|
236
|
+
// Function to change the visibility of a toolbar button by its ID
|
237
|
+
const handleVisibilityButton = (id, value) => {
|
238
|
+
// Update state to inform toolbar which button should be shown/hidden
|
239
|
+
setUpdatedVisibleButton({ id, value });
|
240
|
+
};
|
241
|
+
/**
|
242
|
+
* Recursively updates the visibility status of parent panels based on the visibility of their child panels.
|
243
|
+
* If all children are hidden, the parent is hidden.
|
244
|
+
* If at least one child is visible, the parent is shown. Propagates visibility changes up the tree.
|
245
|
+
*/
|
246
|
+
const syncParentVisibilityWithChildren = (panelId, visibleMap) => {
|
247
|
+
const panel = panelMap.get(panelId);
|
248
|
+
if (!panel || panel.childrenIds.length === 0)
|
249
|
+
return;
|
250
|
+
const allChildrenHidden = panel.childrenIds.every(id => !visibleMap[id]);
|
251
|
+
if (allChildrenHidden && visibleMap[panelId]) {
|
252
|
+
visibleMap[panelId] = false;
|
253
|
+
if (panel.parentId) {
|
254
|
+
syncParentVisibilityWithChildren(panel.parentId, visibleMap);
|
255
|
+
}
|
256
|
+
}
|
257
|
+
else if (!allChildrenHidden && !visibleMap[panelId]) {
|
258
|
+
visibleMap[panelId] = true;
|
259
|
+
}
|
260
|
+
};
|
261
|
+
/**
|
262
|
+
* Toggles the maximized state of a panel based on its full ID.
|
263
|
+
* If the panel is already maximized (i.e., the maximized path matches its hierarchy path),
|
264
|
+
* it will be unmaximized. Otherwise, it will be maximized along with its ancestor panels.
|
265
|
+
* The full dot-separated ID of the panel (e.g., "root.section.panel")
|
266
|
+
*/
|
267
|
+
const handleToggleMaximize = (fullId) => {
|
268
|
+
// Split the full panel ID into its segments (e.g., ["root", "section", "panel"])
|
269
|
+
const segments = fullId.split('.');
|
270
|
+
// Build the full path hierarchy up to this panel (e.g., ["root", "root.section", "root.section.panel"])
|
271
|
+
const path = segments.map((_, i) => segments.slice(0, i + 1).join('.'));
|
272
|
+
// Check if this panel is already maximized by comparing path arrays
|
273
|
+
const isAlreadyMaximized = maximizedPanelPath.length === path.length && maximizedPanelPath.every((v, i) => v === path[i]);
|
274
|
+
if (isAlreadyMaximized) {
|
275
|
+
// Unmaximize by clearing the maximized path
|
276
|
+
setMaximizedPanelPath([]);
|
277
|
+
}
|
278
|
+
else {
|
279
|
+
// Maximize this panel and its parent hierarchy
|
280
|
+
setMaximizedPanelPath(path);
|
281
|
+
}
|
282
|
+
};
|
283
|
+
/**
|
284
|
+
* Toggles the active (visible) state of a panel by its ID.
|
285
|
+
* - On **desktop**, the panel's visibility is toggled independently,
|
286
|
+
* and if the panel is hidden, all its child panels are also recursively hidden.
|
287
|
+
* - On **mobile**, only one panel can be visible at a time:
|
288
|
+
* activating a panel will hide all others, including their children.
|
289
|
+
* After updating visibility, the function also ensures that each parent panel's
|
290
|
+
* visibility is synchronized with the state of its children.
|
291
|
+
*
|
292
|
+
*/
|
293
|
+
const handleTogglePanel = (id) => {
|
294
|
+
// If the panel is currently maximized, toggle its maximization state first
|
295
|
+
if (maximizedPanelPath.includes(id)) {
|
296
|
+
handleToggleMaximize(id);
|
297
|
+
}
|
298
|
+
// Update the activeButtons state
|
299
|
+
setActiveButtons(prev => {
|
300
|
+
// Clone the previous state to avoid mutating it directly
|
301
|
+
const newActive = { ...prev };
|
302
|
+
// Get the panel object from the panel map
|
303
|
+
const panel = panelMap.get(id);
|
304
|
+
// Determine if the current panel is currently visible
|
305
|
+
const currentlyVisible = !!newActive[id];
|
306
|
+
// Mobile-specific behavior: only one panel can be visible at a time
|
307
|
+
if (isMobile) {
|
308
|
+
// Hide all panels
|
309
|
+
Object.keys(newActive).forEach(panelId => {
|
310
|
+
newActive[panelId] = false;
|
311
|
+
});
|
312
|
+
// Toggle the selected panel's visibility
|
313
|
+
newActive[id] = !currentlyVisible;
|
314
|
+
// If the panel is being hidden and has children, recursively hide its children
|
315
|
+
if (panel && panel.childrenIds.length > 0 && !newActive[id]) {
|
316
|
+
const hideChildrenRecursively = (ids) => {
|
317
|
+
ids.forEach(childId => {
|
318
|
+
newActive[childId] = false;
|
319
|
+
const childPanel = panelMap.get(childId);
|
320
|
+
if (childPanel?.childrenIds.length) {
|
321
|
+
hideChildrenRecursively(childPanel.childrenIds);
|
322
|
+
}
|
323
|
+
});
|
324
|
+
};
|
325
|
+
hideChildrenRecursively(panel.childrenIds);
|
326
|
+
}
|
327
|
+
}
|
328
|
+
else {
|
329
|
+
// Desktop behavior: toggle visibility of the selected panel
|
330
|
+
if (!currentlyVisible) {
|
331
|
+
// If the panel is currently not visible (we are activating it)
|
332
|
+
if (panel?.alternativePanelIds.length) {
|
333
|
+
// If the panel has alternative panels defined
|
334
|
+
panel.alternativePanelIds.forEach(altId => {
|
335
|
+
// Deactivate each alternative panel
|
336
|
+
newActive[altId] = false;
|
337
|
+
});
|
338
|
+
}
|
339
|
+
// Activate the selected panel
|
340
|
+
newActive[id] = true;
|
341
|
+
}
|
342
|
+
else {
|
343
|
+
// The panel is currently visible, so we are deactivating it
|
344
|
+
newActive[id] = false;
|
345
|
+
}
|
346
|
+
// If hiding the panel and it has children, hide them recursively
|
347
|
+
if (panel && panel.childrenIds.length > 0 && !newActive[id]) {
|
348
|
+
const hideChildrenRecursively = (ids) => {
|
349
|
+
ids.forEach(childId => {
|
350
|
+
newActive[childId] = false;
|
351
|
+
const childPanel = panelMap.get(childId);
|
352
|
+
if (childPanel?.childrenIds.length) {
|
353
|
+
hideChildrenRecursively(childPanel.childrenIds);
|
354
|
+
}
|
355
|
+
});
|
356
|
+
};
|
357
|
+
hideChildrenRecursively(panel.childrenIds);
|
358
|
+
}
|
359
|
+
}
|
360
|
+
// After toggling, ensure the visibility of parent panels is in sync with their children
|
361
|
+
if (panel?.parentId) {
|
362
|
+
syncParentVisibilityWithChildren(panel.parentId, newActive);
|
363
|
+
}
|
364
|
+
// Return the updated visibility map
|
365
|
+
return newActive;
|
366
|
+
});
|
367
|
+
};
|
368
|
+
// Render panels
|
369
|
+
const renderPanels = (panels, parentPath = '', direction = 'horizontal') => {
|
370
|
+
const visiblePanels = panels.filter(p => {
|
371
|
+
const fullId = parentPath ? `${parentPath}.${p.id}` : p.id;
|
372
|
+
return p.contentOptions?.content && activeButtons[fullId];
|
373
|
+
});
|
374
|
+
const keys = visiblePanels.map(p => (parentPath ? `${parentPath}.${p.id}` : p.id));
|
375
|
+
const totalSize = keys.reduce((sum, key) => {
|
376
|
+
const entry = panelsState.get(key);
|
377
|
+
const size = parseFloat(direction === 'horizontal' ? entry?.width || '0' : entry?.height || '0');
|
378
|
+
return sum + size;
|
379
|
+
}, 0);
|
380
|
+
const sizeMap = Object.fromEntries(keys.map(key => {
|
381
|
+
const entry = panelsState.get(key);
|
382
|
+
const size = parseFloat(direction === 'horizontal' ? entry?.width || '0' : entry?.height || '0');
|
383
|
+
const percent = totalSize > 0 ? (size / totalSize) * 100 : 0;
|
384
|
+
return [key, `${percent}%`];
|
385
|
+
}));
|
386
|
+
return panels.map(panel => {
|
387
|
+
if (!panel.contentOptions)
|
388
|
+
return null;
|
389
|
+
const fullId = parentPath ? `${parentPath}.${panel.id}` : panel.id;
|
390
|
+
const isActive = activeButtons[fullId];
|
391
|
+
const hasChildren = isPanelArray(panel.contentOptions?.content);
|
392
|
+
const isLastVisible = keys.indexOf(fullId) === keys.length - 1;
|
393
|
+
const flexDirection = direction === 'horizontal' ? 'row' : 'column';
|
394
|
+
const panelStyle = {
|
395
|
+
display: 'flex',
|
396
|
+
flexDirection,
|
397
|
+
width: direction === 'horizontal' ? (isActive ? sizeMap[fullId] : '0%') : '100%',
|
398
|
+
height: direction === 'vertical' ? (isActive ? sizeMap[fullId] : '0%') : '100%',
|
399
|
+
visibility: isActive ? 'visible' : 'hidden',
|
400
|
+
overflow: 'hidden',
|
401
|
+
position: 'relative',
|
402
|
+
};
|
403
|
+
const contentStyle = {
|
404
|
+
flexGrow: 1,
|
405
|
+
overflow: 'auto',
|
406
|
+
};
|
407
|
+
return (_jsxs("div", { ref: el => {
|
408
|
+
panelRefs.current[fullId] = el;
|
409
|
+
}, style: panelStyle, children: [_jsx("div", { style: contentStyle, children: hasChildren
|
410
|
+
? renderPanels(panel.contentOptions.content, fullId, 'vertical')
|
411
|
+
: panel.contentOptions?.panelContainer ? _jsx(TMPanel, { title: panel.contentOptions.panelContainer.title, totalItems: panel.contentOptions.panelContainer.totalItems, displayedItemsCount: panel.contentOptions.panelContainer.displayedItemsCount, onClose: () => handleTogglePanel(fullId), onMaximize: () => handleToggleMaximize(fullId), allowMaximize: !isMobile, children: typeof panel.contentOptions.content === "function" ? panel.contentOptions.content(handleTogglePanel, handleToggleMaximize, handleVisibilityButton, handleDisableButton) : panel.contentOptions.content })
|
412
|
+
:
|
413
|
+
typeof panel.contentOptions.content === "function" ? panel.contentOptions.content(handleTogglePanel, handleToggleMaximize, handleVisibilityButton, handleDisableButton) : panel.contentOptions.content }), !isLastVisible && isActive && maximizedPanelPath.length === 0 && (_jsx("div", { style: {
|
414
|
+
cursor: direction === 'horizontal' ? 'col-resize' : 'row-resize',
|
415
|
+
userSelect: 'none',
|
416
|
+
flexShrink: 0,
|
417
|
+
backgroundColor: 'transparent',
|
418
|
+
width: direction === 'horizontal' ? '4px' : '100%',
|
419
|
+
height: direction === 'vertical' ? '4px' : '100%',
|
420
|
+
marginLeft: maximizedPanelPath.length === 0 && direction === 'horizontal' ? `${(gutters - 4) / 2}px` : undefined,
|
421
|
+
marginTop: maximizedPanelPath.length === 0 && direction === 'vertical' ? `${(gutters - 4) / 2}px` : undefined,
|
422
|
+
marginRight: maximizedPanelPath.length === 0 && direction === 'horizontal' ? `${(gutters - 4) / 2}px` : undefined,
|
423
|
+
marginBottom: maximizedPanelPath.length === 0 && direction === 'vertical' ? `${(gutters - 4) / 2}px` : undefined,
|
424
|
+
}, onMouseDown: () => {
|
425
|
+
if (direction === 'horizontal') {
|
426
|
+
onColumnMouseDown(fullId);
|
427
|
+
}
|
428
|
+
else {
|
429
|
+
onRowMouseDown(fullId);
|
430
|
+
}
|
431
|
+
} }))] }, fullId));
|
432
|
+
});
|
433
|
+
};
|
434
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: isMobile ? 'column' : 'row', height: '100%', width: '100%' }, children: [_jsx("div", { style: {
|
435
|
+
display: 'flex',
|
436
|
+
flexGrow: 1,
|
437
|
+
width: isAtLeastOnePanelVisible(activeButtons) ? `calc(100% - ${showToolbar ? (isMobile ? 0 : 60) : 0}px)` : '0%',
|
438
|
+
height: isAtLeastOnePanelVisible(activeButtons) ? `calc(100% - ${isMobile ? 60 : 0}px)` : '0%',
|
439
|
+
visibility: isAtLeastOnePanelVisible(activeButtons) ? 'visible' : 'hidden',
|
440
|
+
flexDirection: 'row',
|
441
|
+
}, children: renderPanels(panels, '', 'horizontal') }), !isAtLeastOnePanelVisible(activeButtons) && (_jsxs("div", { style: {
|
442
|
+
width: '100%',
|
443
|
+
height: isMobile ? 'calc(100% - 60px)' : '100%',
|
444
|
+
display: 'flex',
|
445
|
+
flexDirection: 'column',
|
446
|
+
alignItems: 'center',
|
447
|
+
justifyContent: 'center',
|
448
|
+
backgroundColor: '#fff',
|
449
|
+
borderRadius: '12px',
|
450
|
+
textAlign: 'center',
|
451
|
+
color: '#555',
|
452
|
+
fontSize: '18px',
|
453
|
+
}, children: [_jsx(IconInfo, { style: { fontSize: 50 } }), _jsx("div", { children: SDKUI_Localizator.NoPanelSelected })] })), showToolbar && _jsx("div", { style: {
|
454
|
+
width: isMobile ? '100%' : '60px',
|
455
|
+
height: isMobile ? '60px' : '100%',
|
456
|
+
borderLeft: '1px solid #ccc',
|
457
|
+
display: 'flex',
|
458
|
+
flexDirection: isMobile ? 'row' : 'column',
|
459
|
+
padding: '8px',
|
460
|
+
boxSizing: 'border-box',
|
461
|
+
gap: '6px',
|
462
|
+
backgroundColor: '#f9f9f9',
|
463
|
+
}, children: _jsx(TMPanelManagerToolbar, { allPanels: allPanels, activeButtons: activeButtons, handleTogglePanel: handleTogglePanel, updatedDisabledButton: updatedDisabledButton, updatedVisibleButton: updatedVisibleButton }) })] }));
|
464
|
+
};
|
465
|
+
export default TMPanelManager;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { TMPanelEntry } from './TMPanelManagerUtils';
|
2
|
+
interface TNPanelManagerToolbarProps {
|
3
|
+
allPanels: Array<TMPanelEntry>;
|
4
|
+
activeButtons: Record<string, boolean>;
|
5
|
+
handleTogglePanel: (id: string) => void;
|
6
|
+
updatedDisabledButton?: {
|
7
|
+
id: string;
|
8
|
+
value: boolean;
|
9
|
+
};
|
10
|
+
updatedVisibleButton?: {
|
11
|
+
id: string;
|
12
|
+
value: boolean;
|
13
|
+
};
|
14
|
+
}
|
15
|
+
declare const TMPanelManagerToolbar: (props: TNPanelManagerToolbarProps) => import("react/jsx-runtime").JSX.Element;
|
16
|
+
export default TMPanelManagerToolbar;
|