@topconsultnpm/sdkui-react-beta 6.13.49 → 6.13.51
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/assets/icomoon.svg +96 -96
- package/lib/assets/italy.svg +16 -16
- package/lib/assets/topmedia-six.svg +65 -65
- package/lib/assets/topmeida-six-bianco.svg +65 -65
- package/lib/components/forms/Login/TMLoginForm.js +3 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +9 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +212 -0
- package/lib/components/layout/panelManager/TMPanelManagerContext.d.ts +36 -0
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +234 -0
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.d.ts +11 -0
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +58 -0
- package/lib/components/layout/panelManager/TMPanelWrapper.d.ts +8 -0
- package/lib/components/layout/panelManager/TMPanelWrapper.js +35 -0
- package/lib/components/layout/panelManager/types.d.ts +36 -0
- package/lib/components/layout/panelManager/types.js +1 -0
- package/lib/components/layout/panelManager/utils.d.ts +21 -0
- package/lib/components/layout/panelManager/utils.js +208 -0
- package/lib/helper/SDKUI_Globals.js +2 -2
- package/lib/services/platform_services.d.ts +1 -1
- package/package.json +1 -1
@@ -0,0 +1,212 @@
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
+
import React, { useRef, useMemo } from 'react';
|
3
|
+
import styled from 'styled-components';
|
4
|
+
import { useTMPanelManagerContext } from './TMPanelManagerContext';
|
5
|
+
import TMPanelWrapper from './TMPanelWrapper';
|
6
|
+
import TMPanelManagerToolbar from './TMPanelManagerToolbar';
|
7
|
+
import { IconInfo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
|
8
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
9
|
+
// Styled Component per il contenitore principale di PanelContainer
|
10
|
+
const StyledPanelContainerWrapper = styled.div `
|
11
|
+
display: ${props => (props.$hasVisiblePanels ? 'flex' : 'none')};
|
12
|
+
flex-direction: ${props => (props.$direction === 'vertical' ? 'column' : 'row')};
|
13
|
+
height: 100%;
|
14
|
+
width: 100%;
|
15
|
+
// overflow: hidden;
|
16
|
+
`;
|
17
|
+
// Styled Component per il gutter (separatore)
|
18
|
+
const StyledResizerGutter = styled.div `
|
19
|
+
cursor: ${props => (props.$direction === 'horizontal' ? 'col-resize' : 'row-resize')};
|
20
|
+
${props => (props.$direction === 'horizontal' ? `width: ${SDKUI_Globals.userSettings.themeSettings.gutters}px;` : `height: ${SDKUI_Globals.userSettings.themeSettings.gutters}px;`)}
|
21
|
+
flex-shrink: 0;
|
22
|
+
background-color: "transparent";
|
23
|
+
z-index: 10;
|
24
|
+
`;
|
25
|
+
const TMPanelManagerContainer = (props) => {
|
26
|
+
const { panels, showToolbar, direction, parentId } = props;
|
27
|
+
const { panelVisibility, setPanelDimensionsById, hasVisiblePanels, maximizedPanels, updateIsResizingActive } = useTMPanelManagerContext();
|
28
|
+
// Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
|
29
|
+
const deviceType = useDeviceType();
|
30
|
+
// This avoids unnecessary re-renders by only recalculating when deviceType changes
|
31
|
+
let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
|
32
|
+
const gutters = SDKUI_Globals.userSettings.themeSettings.gutters;
|
33
|
+
// References to store information related to resizing
|
34
|
+
// startMousePos: stores the mouse position when resizing starts (x or y coordinate)
|
35
|
+
const startMousePos = useRef(0);
|
36
|
+
// initialPanelSizesOnDragStart: stores width/height percentages of panels at drag start
|
37
|
+
const initialPanelSizesOnDragStart = useRef({});
|
38
|
+
// resizingPanelId: ID of the panel currently being resized
|
39
|
+
const resizingPanelId = useRef(null);
|
40
|
+
// nextPanelId: ID of the adjacent panel that is resized in tandem
|
41
|
+
const nextPanelId = useRef(null);
|
42
|
+
// Variable to track if a visible panel has been encountered yet during rendering
|
43
|
+
let hasSeenVisiblePanel = false;
|
44
|
+
// Ref to the container DOM element that holds all the panels; used for size calculations
|
45
|
+
const containerRef = useRef(null);
|
46
|
+
// Count how many child panels are currently visible (based on panelVisibility)
|
47
|
+
const currentlyVisibleChildrenCount = panels.filter(panel => panelVisibility[panel.id]).length;
|
48
|
+
// If this component has a parent panel, and the parent panel is visible,
|
49
|
+
// but none of the current child panels are visible, return null to avoid rendering empty container
|
50
|
+
if (parentId && panelVisibility[parentId] && currentlyVisibleChildrenCount === 0) {
|
51
|
+
return null;
|
52
|
+
}
|
53
|
+
// Handles mouse movement during panel resizing
|
54
|
+
const onMouseMove = (e) => {
|
55
|
+
e.preventDefault();
|
56
|
+
// Get the IDs of the panels being resized and the initial sizes saved at drag start
|
57
|
+
const currentId = resizingPanelId.current;
|
58
|
+
const nextId = nextPanelId.current;
|
59
|
+
const initialSizes = initialPanelSizesOnDragStart.current;
|
60
|
+
// If any info is missing, exit early
|
61
|
+
if (!currentId || !nextId || !initialSizes[currentId] || !initialSizes[nextId])
|
62
|
+
return;
|
63
|
+
// Get the container size (width or height depending on direction)
|
64
|
+
const containerSize = direction === 'horizontal' ? containerRef.current?.offsetWidth || 1 : containerRef.current?.offsetHeight || 1;
|
65
|
+
// Calculate how much the mouse moved in pixels and as a percentage relative to container size
|
66
|
+
const deltaPx = (direction === 'horizontal' ? e.clientX : e.clientY) - startMousePos.current;
|
67
|
+
const deltaPercent = (deltaPx / containerSize) * 100;
|
68
|
+
// Parse initial panel sizes (width or height) in percentage
|
69
|
+
const initialCurrent = parseFloat(direction === 'horizontal' ? initialSizes[currentId].width : initialSizes[currentId].height);
|
70
|
+
const initialNext = parseFloat(direction === 'horizontal' ? initialSizes[nextId].width : initialSizes[nextId].height);
|
71
|
+
// Calculate new sizes by adding/subtracting deltaPercent
|
72
|
+
let newCurrent = initialCurrent + deltaPercent;
|
73
|
+
let newNext = initialNext - deltaPercent;
|
74
|
+
// Set a minimum size threshold (to prevent panels from collapsing)
|
75
|
+
const minPercent = (50 / containerSize) * 100;
|
76
|
+
// Correct sizes if they go below the minimum
|
77
|
+
if (newCurrent < minPercent) {
|
78
|
+
newCurrent = minPercent;
|
79
|
+
newNext = initialCurrent + initialNext - minPercent;
|
80
|
+
}
|
81
|
+
else if (newNext < minPercent) {
|
82
|
+
newNext = minPercent;
|
83
|
+
newCurrent = initialCurrent + initialNext - minPercent;
|
84
|
+
}
|
85
|
+
// Update panel sizes (width or height depending on direction)
|
86
|
+
if (direction === 'horizontal') {
|
87
|
+
setPanelDimensionsById(currentId, `${newCurrent}%`, `100%`);
|
88
|
+
setPanelDimensionsById(nextId, `${newNext}%`, `100%`);
|
89
|
+
}
|
90
|
+
else {
|
91
|
+
setPanelDimensionsById(currentId, `100%`, `${newCurrent}%`);
|
92
|
+
setPanelDimensionsById(nextId, `100%`, `${newNext}%`);
|
93
|
+
}
|
94
|
+
};
|
95
|
+
// Handles mouse release (end of resizing)
|
96
|
+
const onMouseUp = (e) => {
|
97
|
+
e.preventDefault();
|
98
|
+
// Disable resizing state
|
99
|
+
updateIsResizingActive(false);
|
100
|
+
// Reset the refs controlling resizing
|
101
|
+
resizingPanelId.current = null;
|
102
|
+
nextPanelId.current = null;
|
103
|
+
initialPanelSizesOnDragStart.current = {};
|
104
|
+
// Remove the event listeners added during resizing
|
105
|
+
window.removeEventListener('mousemove', onMouseMove);
|
106
|
+
window.removeEventListener('mouseup', onMouseUp);
|
107
|
+
// Reset cursor and text selection styles to defaults
|
108
|
+
document.body.style.cursor = '';
|
109
|
+
document.body.style.userSelect = '';
|
110
|
+
};
|
111
|
+
// Starts the resizing process when mouse is pressed on the gutter
|
112
|
+
const startResizing = (e, currentId, nextId) => {
|
113
|
+
e.preventDefault();
|
114
|
+
const container = containerRef.current;
|
115
|
+
if (!container)
|
116
|
+
return;
|
117
|
+
// Find the two panel elements in the DOM by their data-panel-id attributes
|
118
|
+
const currentEl = container.querySelector(`[data-panel-id="${currentId}"]`);
|
119
|
+
const nextEl = container.querySelector(`[data-panel-id="${nextId}"]`);
|
120
|
+
if (!currentEl || !nextEl)
|
121
|
+
return;
|
122
|
+
// Set the refs for the panels currently resizing
|
123
|
+
resizingPanelId.current = currentId;
|
124
|
+
nextPanelId.current = nextId;
|
125
|
+
// Save the initial mouse position (X or Y based on direction)
|
126
|
+
startMousePos.current = direction === 'horizontal' ? e.clientX : e.clientY;
|
127
|
+
// Enable resizing state (for UI feedback if needed)
|
128
|
+
updateIsResizingActive(true);
|
129
|
+
// Calculate and save the initial panel sizes as percentages of container size
|
130
|
+
const containerWidth = container.offsetWidth;
|
131
|
+
const containerHeight = container.offsetHeight;
|
132
|
+
initialPanelSizesOnDragStart.current = {
|
133
|
+
[currentId]: {
|
134
|
+
width: `${(currentEl.offsetWidth / containerWidth) * 100}%`,
|
135
|
+
height: `${(currentEl.offsetHeight / containerHeight) * 100}%`,
|
136
|
+
},
|
137
|
+
[nextId]: {
|
138
|
+
width: `${(nextEl.offsetWidth / containerWidth) * 100}%`,
|
139
|
+
height: `${(nextEl.offsetHeight / containerHeight) * 100}%`,
|
140
|
+
},
|
141
|
+
};
|
142
|
+
// Attach global mousemove and mouseup listeners to track dragging anywhere on screen
|
143
|
+
window.addEventListener('mousemove', onMouseMove);
|
144
|
+
window.addEventListener('mouseup', onMouseUp);
|
145
|
+
// Change the cursor to indicate resizing
|
146
|
+
document.body.style.cursor = direction === 'horizontal' ? 'col-resize' : 'row-resize';
|
147
|
+
document.body.style.userSelect = 'none';
|
148
|
+
};
|
149
|
+
return (_jsxs("div", { style: { display: 'flex', height: '100%', width: '100%', flexDirection: isMobile ? 'column' : 'row', gap: gutters }, children: [_jsx("div", { style: {
|
150
|
+
display: 'flex',
|
151
|
+
width: (isMobile || maximizedPanels.length > 0) ? '100%' : showToolbar ? `calc(100% - 70px)` : '100%',
|
152
|
+
height: isMobile ? (showToolbar ? `calc(100% - 55px)` : '100%') : '100%',
|
153
|
+
flexDirection: 'row'
|
154
|
+
}, children: _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx(StyledPanelContainerWrapper, { ref: containerRef, "$direction": direction, "$hasVisiblePanels": hasVisiblePanels(), children: panels.map((panel, index) => {
|
155
|
+
// Check if current panel is visible according to context state
|
156
|
+
const isVisible = panelVisibility[panel.id];
|
157
|
+
// Find the closest previous visible panel's ID to place the resizer gutter between
|
158
|
+
let prevPanelIdForGutter = null;
|
159
|
+
for (let i = index - 1; i >= 0; i--) {
|
160
|
+
if (panelVisibility[panels[i].id]) {
|
161
|
+
prevPanelIdForGutter = panels[i].id;
|
162
|
+
break;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
/*
|
166
|
+
Determine if we should show the gutter (resize handle) before this panel.
|
167
|
+
The gutter is shown only if:
|
168
|
+
- this panel is visible
|
169
|
+
- at least one visible panel was encountered before this one
|
170
|
+
*/
|
171
|
+
const showGutter = isVisible && hasSeenVisiblePanel && maximizedPanels.length === 0;
|
172
|
+
// Update the flag once a visible panel is found, so gutters after it are shown correctly
|
173
|
+
if (isVisible) {
|
174
|
+
hasSeenVisiblePanel = true;
|
175
|
+
}
|
176
|
+
// If the panel has child panels, render a nested TMPanelManagerContainer or a component
|
177
|
+
if (panel.children && panel.children.length > 0) {
|
178
|
+
const ComponentToRender = panel.contentOptions?.component;
|
179
|
+
return (_jsxs(React.Fragment, { children: [showGutter && (_jsx(StyledResizerGutter, { "$direction": direction, onMouseDown: (e) => startResizing(e, prevPanelIdForGutter, panel.id) })), _jsx(TMPanelWrapper, { panel: panel, children: ComponentToRender
|
180
|
+
? _jsx(ComponentToRender, { groupId: panel.id, mainDirection: panel.childrenGroupDirection || direction })
|
181
|
+
: _jsx(TMPanelManagerContainer, { panels: panel.children, direction: panel.childrenGroupDirection || direction, parentId: panel.id, showToolbar: false }) })] }, panel.id));
|
182
|
+
}
|
183
|
+
// If the panel has a component specified, render it with a possible gutter before it
|
184
|
+
if (panel.contentOptions && panel.contentOptions.component) {
|
185
|
+
return (_jsxs(React.Fragment, { children: [showGutter && (_jsx(StyledResizerGutter, { "$direction": direction, onMouseDown: (e) => startResizing(e, prevPanelIdForGutter, panel.id) })), _jsx(TMPanelWrapper, { panel: panel, children: _jsx(panel.contentOptions.component, {}) })] }, panel.id));
|
186
|
+
}
|
187
|
+
// If no children or components, render nothing for this panel
|
188
|
+
return null;
|
189
|
+
}) }), _jsxs("div", { style: {
|
190
|
+
display: hasVisiblePanels() ? 'none' : 'flex',
|
191
|
+
flexDirection: 'column',
|
192
|
+
width: '100%',
|
193
|
+
height: '100%',
|
194
|
+
justifyContent: 'center',
|
195
|
+
alignItems: 'center',
|
196
|
+
fontSize: '1.5rem',
|
197
|
+
color: 'rgb(37, 89, 165)',
|
198
|
+
backgroundColor: '#fff',
|
199
|
+
borderRadius: '10px'
|
200
|
+
}, children: [_jsx(IconInfo, { style: { fontSize: 50 } }), _jsx("div", { children: SDKUI_Localizator.NoPanelSelected })] })] }) }), (showToolbar && maximizedPanels.length === 0) && _jsx("div", { style: {
|
201
|
+
display: 'flex',
|
202
|
+
flexDirection: isMobile ? 'row' : 'column',
|
203
|
+
alignItems: 'center',
|
204
|
+
width: isMobile ? '100%' : '50px',
|
205
|
+
height: isMobile ? '50px' : 'max-content',
|
206
|
+
background: 'transparent linear-gradient(90deg, #CCE0F4 0%, #7EC1E7 14%, #39A6DB 28%, #1E9CD7 35%, #0075BE 78%, #005B97 99%) 0% 0% no-repeat padding-box',
|
207
|
+
borderRadius: isMobile ? '10px' : '10px 0px 0px 10px',
|
208
|
+
padding: '10px',
|
209
|
+
gap: '10px'
|
210
|
+
}, children: _jsx(TMPanelManagerToolbar, { panels: panels }) })] }));
|
211
|
+
};
|
212
|
+
export default TMPanelManagerContainer;
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { ReactNode } from 'react';
|
2
|
+
import type { TMPanelDefinition, TMPanelDimensionsMap } from './types';
|
3
|
+
interface TMPanelManagerContextType {
|
4
|
+
panelVisibility: {
|
5
|
+
[id: string]: boolean;
|
6
|
+
};
|
7
|
+
panelDimensions: TMPanelDimensionsMap;
|
8
|
+
togglePanelVisibility: (id: string) => void;
|
9
|
+
setPanelVisibilityById: (id: string, isVisible: boolean) => void;
|
10
|
+
setPanelDimensionsById: (id: string, width: string, height: string) => void;
|
11
|
+
hasVisiblePanels: () => boolean;
|
12
|
+
maximizedPanels: Array<string>;
|
13
|
+
toggleMaximize: (id: string) => void;
|
14
|
+
toolbarButtonsVisibility: {
|
15
|
+
[id: string]: boolean;
|
16
|
+
};
|
17
|
+
toolbarButtonsDisabled: {
|
18
|
+
[id: string]: boolean;
|
19
|
+
};
|
20
|
+
setToolbarButtonVisibility: (id: string, isVisible: boolean) => void;
|
21
|
+
setToolbarButtonDisabled: (id: string, isDisabled: boolean) => void;
|
22
|
+
isResizingActive: boolean;
|
23
|
+
updateIsResizingActive: (isActive: boolean) => void;
|
24
|
+
}
|
25
|
+
interface TMPanelManagerProviderProps {
|
26
|
+
children: ReactNode;
|
27
|
+
panels: Array<TMPanelDefinition>;
|
28
|
+
initialVisibility: {
|
29
|
+
[id: string]: boolean;
|
30
|
+
};
|
31
|
+
initialDimensions: TMPanelDimensionsMap;
|
32
|
+
initialMobilePanelId: string;
|
33
|
+
}
|
34
|
+
export declare const TMPanelManagerProvider: (props: TMPanelManagerProviderProps) => import("react/jsx-runtime").JSX.Element;
|
35
|
+
export declare const useTMPanelManagerContext: () => TMPanelManagerContextType;
|
36
|
+
export {};
|
@@ -0,0 +1,234 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
3
|
+
import { generatePanelHierarchyMap, hideParentRecursively, showParentRecursively, redistributeDimensionsOnHide, redistributeDimensionsOnShow, getToolbarStates } from './utils';
|
4
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
5
|
+
// Create a React context for the panel state and actions
|
6
|
+
const TMPanelManagerContext = createContext(undefined);
|
7
|
+
export const TMPanelManagerProvider = (props) => {
|
8
|
+
const { children, panels, initialDimensions, initialVisibility, initialMobilePanelId } = props;
|
9
|
+
// Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
|
10
|
+
const deviceType = useDeviceType();
|
11
|
+
// This avoids unnecessary re-renders by only recalculating when deviceType changes
|
12
|
+
let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
|
13
|
+
// Ref to persist the initial panel dimensions across renders without causing re-renders
|
14
|
+
const initialPanelDimensionsRef = useRef(initialDimensions);
|
15
|
+
// Memoize the panel hierarchy map to avoid re-generating it on every render unless `panels` change
|
16
|
+
const hierarchyMap = useMemo(() => generatePanelHierarchyMap(panels), [panels]);
|
17
|
+
// State to track the dimensions (width and height) of each panel, initialized with props
|
18
|
+
const [panelDimensions, setPanelDimensions] = useState(initialDimensions);
|
19
|
+
// IDs of maximized panels, including their parents if present
|
20
|
+
const [maximizedPanels, setMaximizedPanels] = useState([]);
|
21
|
+
// State to track the visibility of each panel, keyed by panel ID
|
22
|
+
const [panelVisibility, setPanelVisibility] = useState({});
|
23
|
+
// State to track visibility of toolbar buttons, keyed by panel ID
|
24
|
+
const [toolbarButtonsVisibility, setToolbarButtonsVisibility] = useState({});
|
25
|
+
// State to track disabled status of toolbar buttons, keyed by panel ID
|
26
|
+
const [toolbarButtonsDisabled, setToolbarButtonsDisabled] = useState({});
|
27
|
+
// State to track if a resizing operation is currently active (mouse drag on gutter)
|
28
|
+
const [isResizingActive, setIsResizingActive] = useState(false);
|
29
|
+
useEffect(() => {
|
30
|
+
const { visibilityMap, disabledMap } = getToolbarStates(panels);
|
31
|
+
setToolbarButtonsVisibility(visibilityMap);
|
32
|
+
setToolbarButtonsDisabled(disabledMap);
|
33
|
+
}, [panels]);
|
34
|
+
// Callback to update the visibility state of a specific panel and its related hierarchy
|
35
|
+
const updatePanelVisibility = useCallback((id, isVisible, prevVisibility) => {
|
36
|
+
// Clone previous visibility state to work with
|
37
|
+
let updated = { ...prevVisibility };
|
38
|
+
if (isMobile) {
|
39
|
+
if (isVisible) {
|
40
|
+
// On mobile, showing one panel hides all others first
|
41
|
+
updated = Object.keys(prevVisibility).reduce((acc, key) => { acc[key] = false; return acc; }, {});
|
42
|
+
// Recursively determine and show parent panels
|
43
|
+
const parentsToShow = showParentRecursively(id, hierarchyMap);
|
44
|
+
parentsToShow.forEach(([pid, visible]) => { updated[pid] = visible; });
|
45
|
+
// Show the target panel
|
46
|
+
updated[id] = true;
|
47
|
+
// Redistribute dimensions to account for newly shown panel
|
48
|
+
setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, initialPanelDimensionsRef.current, hierarchyMap, updated));
|
49
|
+
}
|
50
|
+
else {
|
51
|
+
// Hide the panel
|
52
|
+
updated[id] = false;
|
53
|
+
// Recursively hide parents
|
54
|
+
const parentsToHide = hideParentRecursively(id, hierarchyMap, updated);
|
55
|
+
parentsToHide.forEach(([pid, visible]) => { updated[pid] = visible; });
|
56
|
+
4;
|
57
|
+
// Redistribute dimensions after hiding
|
58
|
+
setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updated));
|
59
|
+
}
|
60
|
+
}
|
61
|
+
else {
|
62
|
+
if (isVisible) {
|
63
|
+
// On desktop, show panel and its parents without hiding others
|
64
|
+
const parentsToShow = showParentRecursively(id, hierarchyMap);
|
65
|
+
parentsToShow.forEach(([pid, visible]) => { updated[pid] = visible; });
|
66
|
+
// Show the panel
|
67
|
+
updated[id] = true;
|
68
|
+
// Adjust panel dimensions accordingly
|
69
|
+
setPanelDimensions(prev => redistributeDimensionsOnShow(id, prev, initialPanelDimensionsRef.current, hierarchyMap, updated));
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
// Hide the panel
|
73
|
+
updated[id] = false;
|
74
|
+
// Recursively hide parent panels
|
75
|
+
const parentsToHide = hideParentRecursively(id, hierarchyMap, updated);
|
76
|
+
parentsToHide.forEach(([pid, visible]) => { updated[pid] = visible; });
|
77
|
+
// Adjust dimensions after hiding
|
78
|
+
setPanelDimensions(prev => redistributeDimensionsOnHide(id, prev, hierarchyMap, updated));
|
79
|
+
}
|
80
|
+
}
|
81
|
+
return updated;
|
82
|
+
}, [hierarchyMap, isMobile]);
|
83
|
+
// On initial mount: initialize panel visibility using the provided `initialVisibility` config
|
84
|
+
// Each panel's visibility is processed through `updatePanelVisibility` to ensure hierarchy logic is applied
|
85
|
+
useEffect(() => {
|
86
|
+
let updated = { ...initialVisibility };
|
87
|
+
Object.entries(initialVisibility).forEach(([id, isVisible]) => {
|
88
|
+
updated = updatePanelVisibility(id, isVisible, updated);
|
89
|
+
});
|
90
|
+
setPanelVisibility(updated);
|
91
|
+
}, []);
|
92
|
+
// On mobile devices: automatically show the initial mobile panel when layout switches to mobile
|
93
|
+
// This ensures the correct panel is visible by default on smaller screens
|
94
|
+
useEffect(() => {
|
95
|
+
if (isMobile) {
|
96
|
+
setPanelVisibility(prev => {
|
97
|
+
return updatePanelVisibility(initialMobilePanelId, true, prev);
|
98
|
+
});
|
99
|
+
}
|
100
|
+
}, [isMobile, updatePanelVisibility]);
|
101
|
+
// Function to maximize a panel (and its parent panels)
|
102
|
+
const maximizePanel = useCallback((id) => {
|
103
|
+
// Get all parent panel IDs recursively for the given panel ID
|
104
|
+
const parents = showParentRecursively(id, hierarchyMap).map(([pid]) => pid);
|
105
|
+
4;
|
106
|
+
// Combine parents and the current panel ID into one array to maximize
|
107
|
+
const toMaximize = [...parents, id];
|
108
|
+
// Update state to track which panels are maximized
|
109
|
+
setMaximizedPanels(toMaximize);
|
110
|
+
// Update panel dimensions accordingly
|
111
|
+
setPanelDimensions(prev => {
|
112
|
+
const newDimensions = {};
|
113
|
+
Object.keys(prev).forEach(pid => {
|
114
|
+
if (toMaximize.includes(pid)) {
|
115
|
+
// For maximized panels (target + parents), set width and height to 100%
|
116
|
+
newDimensions[pid] = { width: '100%', height: '100%' };
|
117
|
+
}
|
118
|
+
else {
|
119
|
+
// For all other panels, collapse by setting dimensions to 0%
|
120
|
+
newDimensions[pid] = { width: '0%', height: '0%' };
|
121
|
+
}
|
122
|
+
});
|
123
|
+
return newDimensions;
|
124
|
+
});
|
125
|
+
}, [hierarchyMap]);
|
126
|
+
// Restore all panels to their original dimensions
|
127
|
+
const resetMaximization = useCallback(() => {
|
128
|
+
// Clear the list of maximized panels to exit maximize mode
|
129
|
+
setMaximizedPanels([]);
|
130
|
+
// Clone the current visibility state of panels
|
131
|
+
let updatedVisibility = { ...panelVisibility };
|
132
|
+
// Start with the initial panel dimensions as the base for restoration
|
133
|
+
let nextDimensions = { ...initialPanelDimensionsRef.current };
|
134
|
+
nextDimensions = Object.entries(updatedVisibility).reduce((acc, [id, isVisible]) => {
|
135
|
+
if (isVisible) {
|
136
|
+
// If the panel is visible, adjust dimensions to show it properly
|
137
|
+
acc = redistributeDimensionsOnShow(id, acc, initialPanelDimensionsRef.current, hierarchyMap, updatedVisibility);
|
138
|
+
}
|
139
|
+
else {
|
140
|
+
// If the panel is hidden, adjust dimensions to hide it
|
141
|
+
acc = redistributeDimensionsOnHide(id, acc, hierarchyMap, updatedVisibility);
|
142
|
+
}
|
143
|
+
return acc;
|
144
|
+
}, initialPanelDimensionsRef.current);
|
145
|
+
// Apply the recalculated dimensions to the panel state
|
146
|
+
setPanelDimensions(nextDimensions);
|
147
|
+
}, [initialVisibility, panelVisibility, hierarchyMap]);
|
148
|
+
// Toggle the maximized state of a panel by its ID
|
149
|
+
const toggleMaximize = useCallback((id) => {
|
150
|
+
if (maximizedPanels.includes(id)) {
|
151
|
+
// If already maximized, restore
|
152
|
+
resetMaximization();
|
153
|
+
}
|
154
|
+
else {
|
155
|
+
// Otherwise, maximize the selected panel and its parents
|
156
|
+
maximizePanel(id);
|
157
|
+
}
|
158
|
+
}, [maximizedPanels, maximizePanel, resetMaximization]);
|
159
|
+
// Toggles the visibility of a panel by its I
|
160
|
+
const togglePanelVisibility = useCallback((id) => {
|
161
|
+
// Check if the panel to toggle is currently maximized
|
162
|
+
if (maximizedPanels.length > 0) {
|
163
|
+
// If it is maximized, first reset all maximized panels to their original state
|
164
|
+
resetMaximization();
|
165
|
+
// Then update the visibility of the panel, toggling its current state
|
166
|
+
setPanelVisibility(prev => {
|
167
|
+
const isCurrentlyVisible = prev[id];
|
168
|
+
return updatePanelVisibility(id, !isCurrentlyVisible, prev);
|
169
|
+
});
|
170
|
+
}
|
171
|
+
else {
|
172
|
+
// If the panel is not maximized, simply toggle its visibility
|
173
|
+
setPanelVisibility(prev => {
|
174
|
+
const isCurrentlyVisible = prev[id];
|
175
|
+
return updatePanelVisibility(id, !isCurrentlyVisible, prev);
|
176
|
+
});
|
177
|
+
}
|
178
|
+
}, [maximizedPanels, resetMaximization, updatePanelVisibility]);
|
179
|
+
// Sets the visibility of a panel by its ID to a specific value (true = show, false = hide)
|
180
|
+
const setPanelVisibilityById = useCallback((id, isVisible) => {
|
181
|
+
// If we are trying to hide a panel that is currently maximized, first reset the maximization to restore normal view before hiding
|
182
|
+
if (maximizedPanels.length > 0) {
|
183
|
+
resetMaximization();
|
184
|
+
}
|
185
|
+
// Then update the visibility state of the panel to the given value
|
186
|
+
setPanelVisibility(prev => updatePanelVisibility(id, isVisible, prev));
|
187
|
+
}, [maximizedPanels, resetMaximization, updatePanelVisibility]);
|
188
|
+
// Sets the dimensions (width and height) of a specific panel by its ID
|
189
|
+
const setPanelDimensionsById = useCallback((id, width, height) => {
|
190
|
+
// Update the ref holding the initial dimensions
|
191
|
+
initialPanelDimensionsRef.current = { ...initialPanelDimensionsRef.current, [id]: { width, height } };
|
192
|
+
// Update the panel dimensions state
|
193
|
+
setPanelDimensions(prev => ({ ...prev, [id]: { width, height } }));
|
194
|
+
}, []);
|
195
|
+
// Checks if there is at least one panel currently visible
|
196
|
+
const hasVisiblePanels = useCallback(() => {
|
197
|
+
// Returns true if any value in the visibility map is `true`
|
198
|
+
return Object.values(panelVisibility).some(isVisible => isVisible);
|
199
|
+
}, [panelVisibility]);
|
200
|
+
// Callback to update the visibility of a toolbar button by panel ID
|
201
|
+
const setToolbarButtonVisibility = useCallback((id, isVisible) => {
|
202
|
+
setToolbarButtonsVisibility(prev => ({ ...prev, [id]: isVisible }));
|
203
|
+
}, []);
|
204
|
+
// Callback to update the disabled state of a toolbar button by panel ID
|
205
|
+
const setToolbarButtonDisabled = useCallback((id, isDisabled) => {
|
206
|
+
setToolbarButtonsDisabled(prev => ({ ...prev, [id]: isDisabled }));
|
207
|
+
}, []);
|
208
|
+
const updateIsResizingActive = useCallback((isActive) => {
|
209
|
+
setIsResizingActive(isActive);
|
210
|
+
}, []);
|
211
|
+
return (_jsx(TMPanelManagerContext.Provider, { value: {
|
212
|
+
panelVisibility,
|
213
|
+
panelDimensions,
|
214
|
+
togglePanelVisibility,
|
215
|
+
setPanelVisibilityById,
|
216
|
+
setPanelDimensionsById,
|
217
|
+
hasVisiblePanels,
|
218
|
+
maximizedPanels,
|
219
|
+
toggleMaximize,
|
220
|
+
toolbarButtonsVisibility,
|
221
|
+
toolbarButtonsDisabled,
|
222
|
+
setToolbarButtonVisibility,
|
223
|
+
setToolbarButtonDisabled,
|
224
|
+
isResizingActive,
|
225
|
+
updateIsResizingActive
|
226
|
+
}, children: children }));
|
227
|
+
};
|
228
|
+
export const useTMPanelManagerContext = () => {
|
229
|
+
const context = useContext(TMPanelManagerContext);
|
230
|
+
if (context === undefined) {
|
231
|
+
throw new Error('useTMPanelContext must be used within a TMPanelProvider');
|
232
|
+
}
|
233
|
+
return context;
|
234
|
+
};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { TMPanelDefinition } from './types';
|
3
|
+
export declare const StyledToolbarButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, {
|
4
|
+
$isActive?: boolean;
|
5
|
+
$isDisabled?: boolean;
|
6
|
+
}>> & string;
|
7
|
+
interface TMPanelManagerToolbarProps {
|
8
|
+
panels: Array<TMPanelDefinition>;
|
9
|
+
}
|
10
|
+
declare const TMPanelManagerToolbar: (props: TMPanelManagerToolbarProps) => import("react/jsx-runtime").JSX.Element;
|
11
|
+
export default TMPanelManagerToolbar;
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
3
|
+
import styled from 'styled-components';
|
4
|
+
import { useTMPanelManagerContext } from './TMPanelManagerContext';
|
5
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
6
|
+
import { flattenPanels } from './utils';
|
7
|
+
import TMTooltip from '../../base/TMTooltip';
|
8
|
+
export const StyledToolbarButton = styled.button `
|
9
|
+
display: flex;
|
10
|
+
align-items: center;
|
11
|
+
justify-content: center;
|
12
|
+
height: 32px;
|
13
|
+
width: 32px;
|
14
|
+
border: none;
|
15
|
+
border-radius: 8px;
|
16
|
+
font-size: 18px;
|
17
|
+
padding: 0px;
|
18
|
+
color: #fff;
|
19
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
20
|
+
cursor: ${({ $isDisabled }) => ($isDisabled ? 'not-allowed' : 'pointer')};
|
21
|
+
opacity: ${({ $isDisabled }) => ($isDisabled ? 0.6 : 1)};
|
22
|
+
background: ${({ $isActive }) => $isActive ? 'rgba(255,255,255,0.35)' : 'transparent'};
|
23
|
+
&:hover {
|
24
|
+
background: ${({ $isDisabled }) => !$isDisabled ? 'rgba(255,255,255,0.35)' : undefined};
|
25
|
+
}
|
26
|
+
`;
|
27
|
+
const TMPanelManagerToolbar = (props) => {
|
28
|
+
const { panels } = props;
|
29
|
+
// Get panel visibility toggling function and visibility state from context
|
30
|
+
const { togglePanelVisibility, panelVisibility, toolbarButtonsDisabled, toolbarButtonsVisibility } = useTMPanelManagerContext();
|
31
|
+
// Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
|
32
|
+
const deviceType = useDeviceType();
|
33
|
+
// This avoids unnecessary re-renders by only recalculating when deviceType changes
|
34
|
+
let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
|
35
|
+
const [visibleLeafPanels, setVisibleLeafPanels] = useState([]);
|
36
|
+
useEffect(() => {
|
37
|
+
// Flatten panels, filter visible leaf nodes, and sort by orderNumber
|
38
|
+
const visibleLeafPanelsSorted = flattenPanels(panels)
|
39
|
+
.filter(panel => panel.toolbarOptions?.visible && !panel.children?.length)
|
40
|
+
.sort((a, b) => (a.toolbarOptions?.orderNumber ?? 0) - (b.toolbarOptions?.orderNumber ?? 0));
|
41
|
+
// Update state with the filtered and sorted leaf panels for the toolbar
|
42
|
+
setVisibleLeafPanels(visibleLeafPanelsSorted);
|
43
|
+
}, [panels]);
|
44
|
+
return (_jsx("div", { style: {
|
45
|
+
display: 'flex',
|
46
|
+
flexDirection: isMobile ? 'row' : 'column',
|
47
|
+
gap: '6px',
|
48
|
+
alignItems: 'center',
|
49
|
+
justifyContent: isMobile ? 'center' : 'flex-start',
|
50
|
+
width: '100%',
|
51
|
+
height: '100%'
|
52
|
+
}, children: visibleLeafPanels.filter(panel => toolbarButtonsVisibility[panel.id]).map(visibleLeafPanel => {
|
53
|
+
const isActive = panelVisibility[visibleLeafPanel.id];
|
54
|
+
const isDisabled = toolbarButtonsDisabled[visibleLeafPanel.id];
|
55
|
+
return _jsx(TMTooltip, { content: visibleLeafPanel.name, position: isMobile ? 'top' : 'left', children: _jsx(StyledToolbarButton, { disabled: isDisabled, "$isDisabled": isDisabled, onClick: () => togglePanelVisibility(visibleLeafPanel.id), "$isActive": isActive || visibleLeafPanel.toolbarOptions?.alwaysActiveColor, children: typeof visibleLeafPanel.toolbarOptions?.icon === 'string' ? (_jsx("i", { className: `dx-icon dx-icon-${visibleLeafPanel.toolbarOptions?.icon}` })) : (visibleLeafPanel.toolbarOptions?.icon) }, visibleLeafPanel.id) }, visibleLeafPanel.id);
|
56
|
+
}) }));
|
57
|
+
};
|
58
|
+
export default TMPanelManagerToolbar;
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { ReactNode } from 'react';
|
2
|
+
import { TMPanelDefinition } from './types';
|
3
|
+
interface TMPanelWrapperProps {
|
4
|
+
panel: TMPanelDefinition;
|
5
|
+
children: ReactNode;
|
6
|
+
}
|
7
|
+
declare const TMPanelWrapper: (props: TMPanelWrapperProps) => import("react/jsx-runtime").JSX.Element;
|
8
|
+
export default TMPanelWrapper;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useMemo } from 'react';
|
3
|
+
import { useTMPanelManagerContext } from './TMPanelManagerContext';
|
4
|
+
import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
|
5
|
+
import TMPanel from '../../base/TMPanel';
|
6
|
+
const TMPanelWrapper = (props) => {
|
7
|
+
const { panel, children } = props;
|
8
|
+
const { panelVisibility, panelDimensions, togglePanelVisibility, maximizedPanels, toggleMaximize } = useTMPanelManagerContext();
|
9
|
+
// Get the current device type (e.g., mobile, tablet, desktop) using a custom hook
|
10
|
+
const deviceType = useDeviceType();
|
11
|
+
// This avoids unnecessary re-renders by only recalculating when deviceType changes
|
12
|
+
let isMobile = useMemo(() => { return deviceType === DeviceType.MOBILE; }, [deviceType]);
|
13
|
+
// Extract panel dimensions based on panel id
|
14
|
+
const width = panelDimensions[panel.id].width;
|
15
|
+
const height = panelDimensions[panel.id].height;
|
16
|
+
// Determine visibility:
|
17
|
+
// - If any panels are maximized, only show those maximized panels
|
18
|
+
// - Otherwise, rely on the normal panel visibility state
|
19
|
+
const isVisible = maximizedPanels.length > 0 ? maximizedPanels.includes(panel.id) : panelVisibility[panel.id];
|
20
|
+
const panelStyles = {
|
21
|
+
margin: '0',
|
22
|
+
// overflow: 'hidden',
|
23
|
+
boxSizing: 'border-box',
|
24
|
+
display: isVisible ? 'flex' : 'none',
|
25
|
+
flexDirection: 'column',
|
26
|
+
minWidth: '50px',
|
27
|
+
minHeight: '50px',
|
28
|
+
width: width,
|
29
|
+
height: height,
|
30
|
+
pointerEvents: 'auto',
|
31
|
+
};
|
32
|
+
return (_jsx("div", { "data-panel-id": panel.id, style: panelStyles, children: panel.contentOptions?.panelContainer ?
|
33
|
+
_jsx(TMPanel, { ...panel.contentOptions.panelContainer, allowMaximize: !isMobile, onHeaderDoubleClick: () => toggleMaximize(panel.id), onMaximize: () => toggleMaximize(panel.id), onClose: () => togglePanelVisibility(panel.id), children: children }) : children }));
|
34
|
+
};
|
35
|
+
export default TMPanelWrapper;
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { ITMPanelProps } from "../../base/TMPanel";
|
2
|
+
export type TMPanelDirection = 'horizontal' | 'vertical';
|
3
|
+
export interface TMPanelDefinition {
|
4
|
+
id: string;
|
5
|
+
name: string;
|
6
|
+
children?: Array<TMPanelDefinition>;
|
7
|
+
currentGroupDirection?: TMPanelDirection;
|
8
|
+
childrenGroupDirection?: TMPanelDirection;
|
9
|
+
contentOptions?: {
|
10
|
+
component: React.ComponentType<any>;
|
11
|
+
panelContainer?: ITMPanelProps;
|
12
|
+
};
|
13
|
+
toolbarOptions?: {
|
14
|
+
icon: string | JSX.Element;
|
15
|
+
visible: boolean;
|
16
|
+
isActive: boolean;
|
17
|
+
disabled?: boolean;
|
18
|
+
orderNumber?: number;
|
19
|
+
beginGroup?: boolean;
|
20
|
+
alwaysActiveColor?: boolean;
|
21
|
+
};
|
22
|
+
}
|
23
|
+
export interface TMPanelHierarchyInfo {
|
24
|
+
groupDirection: TMPanelDirection;
|
25
|
+
parentId: string | undefined;
|
26
|
+
childrenIds: Array<string>;
|
27
|
+
siblingIds: Array<string>;
|
28
|
+
}
|
29
|
+
export type TMPanelHierarchyMap = Map<string, TMPanelHierarchyInfo>;
|
30
|
+
export type TMPanelDimensions = {
|
31
|
+
width: string;
|
32
|
+
height: string;
|
33
|
+
};
|
34
|
+
export type TMPanelDimensionsMap = {
|
35
|
+
[id: string]: TMPanelDimensions;
|
36
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|