@topconsultnpm/sdkui-react 6.20.0-dev1.7 → 6.20.0-dev1.70
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/NewComponents/ContextMenu/TMContextMenu.js +258 -17
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +2 -0
- package/lib/components/NewComponents/ContextMenu/hooks.js +17 -4
- package/lib/components/NewComponents/ContextMenu/index.d.ts +3 -0
- package/lib/components/NewComponents/ContextMenu/index.js +2 -0
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +5 -1
- package/lib/components/NewComponents/ContextMenu/styles.js +59 -31
- package/lib/components/NewComponents/ContextMenu/types.d.ts +13 -0
- package/lib/components/NewComponents/ContextMenu/useLongPress.d.ts +21 -0
- package/lib/components/NewComponents/ContextMenu/useLongPress.js +112 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +517 -100
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +19 -5
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +206 -54
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +1 -2
- package/lib/components/base/TMAccordionNew.js +35 -14
- package/lib/components/base/TMCustomButton.js +61 -17
- package/lib/components/base/TMDataGrid.d.ts +7 -4
- package/lib/components/base/TMDataGrid.js +142 -11
- package/lib/components/base/TMDropDownMenu.js +19 -18
- package/lib/components/base/TMPanel.js +1 -1
- package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.js +8 -1
- package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMUserChooser.d.ts +0 -5
- package/lib/components/choosers/TMUserChooser.js +25 -45
- package/lib/components/editors/TMMetadataValues.js +23 -5
- package/lib/components/editors/TMTextBox.js +6 -3
- package/lib/components/features/documents/TMDcmtForm.d.ts +13 -1
- package/lib/components/features/documents/TMDcmtForm.js +386 -194
- package/lib/components/features/documents/TMDcmtPreview.js +41 -105
- package/lib/components/features/documents/TMMasterDetailDcmts.js +37 -52
- package/lib/components/features/documents/TMRelationViewer.d.ts +1 -1
- package/lib/components/features/documents/TMRelationViewer.js +2 -2
- package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
- package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +5 -10
- package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
- package/lib/components/features/search/TMSearch.js +41 -9
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryPanel.js +19 -18
- package/lib/components/features/search/TMSearchResult.js +118 -242
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
- package/lib/components/features/search/TMSearchResultsMenuItems.js +205 -169
- package/lib/components/features/search/TMSignSettingsForm.js +1 -1
- package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
- package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
- package/lib/components/features/search/TMViewHistoryDcmt.js +1 -1
- package/lib/components/features/tasks/TMTaskForm.js +20 -1
- package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
- package/lib/components/features/tasks/TMTasksUtils.js +62 -52
- package/lib/components/features/tasks/TMTasksView.js +6 -6
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
- package/lib/components/features/workflow/TMWorkflowPopup.js +134 -24
- package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +1 -0
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +2 -3
- package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
- package/lib/components/features/workflow/diagram/WFDiagram.d.ts +2 -0
- package/lib/components/features/workflow/diagram/WFDiagram.js +21 -4
- package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
- package/lib/components/forms/Login/LoginValidatorService.js +7 -2
- package/lib/components/forms/Login/TMLoginForm.js +34 -6
- package/lib/components/forms/TMChooserForm.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +55 -30
- package/lib/components/grids/TMRecentsManager.js +20 -10
- package/lib/components/index.d.ts +4 -0
- package/lib/components/index.js +4 -0
- package/lib/components/settings/SettingsAppearance.js +92 -29
- package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
- package/lib/components/viewers/TMDataListItemViewer.js +35 -71
- package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +8 -0
- package/lib/components/viewers/TMDataUserIdItemViewer.js +39 -0
- package/lib/css/tm-sdkui.css +1 -1
- package/lib/helper/SDKUI_Globals.d.ts +19 -0
- package/lib/helper/SDKUI_Globals.js +11 -0
- package/lib/helper/SDKUI_Localizator.d.ts +15 -1
- package/lib/helper/SDKUI_Localizator.js +147 -1
- package/lib/helper/TMIcons.d.ts +2 -0
- package/lib/helper/TMIcons.js +6 -0
- package/lib/helper/TMPdfViewer.d.ts +8 -0
- package/lib/helper/TMPdfViewer.js +373 -0
- package/lib/helper/checkinCheckoutManager.d.ts +32 -2
- package/lib/helper/checkinCheckoutManager.js +115 -38
- package/lib/helper/devextremeCustomMessages.d.ts +30 -0
- package/lib/helper/devextremeCustomMessages.js +30 -0
- package/lib/helper/helpers.d.ts +2 -1
- package/lib/helper/helpers.js +14 -3
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/helper/queryHelper.d.ts +1 -1
- package/lib/helper/queryHelper.js +33 -3
- package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
- package/lib/hooks/useCheckInOutOperations.js +223 -0
- package/lib/hooks/useDataListItem.d.ts +12 -0
- package/lib/hooks/useDataListItem.js +131 -0
- package/lib/hooks/useDataUserIdItem.d.ts +10 -0
- package/lib/hooks/useDataUserIdItem.js +96 -0
- package/lib/hooks/useSettingsFeedback.d.ts +11 -0
- package/lib/hooks/useSettingsFeedback.js +38 -0
- package/lib/hooks/useWorkflowApprove.d.ts +4 -0
- package/lib/hooks/useWorkflowApprove.js +14 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/ts/types.d.ts +58 -1
- package/lib/utils/theme.d.ts +1 -1
- package/lib/utils/theme.js +1 -1
- package/package.json +5 -2
- package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
- package/lib/components/NewComponents/Notification/Notification.js +0 -60
- package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
- package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
- package/lib/components/NewComponents/Notification/index.d.ts +0 -2
- package/lib/components/NewComponents/Notification/index.js +0 -2
- package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
- package/lib/components/NewComponents/Notification/styles.js +0 -180
- package/lib/components/NewComponents/Notification/types.d.ts +0 -18
- package/lib/components/NewComponents/Notification/types.js +0 -1
- package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
|
@@ -1,36 +1,127 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { ContextMenu } from '../ContextMenu';
|
|
4
|
-
import
|
|
4
|
+
import ShowAlert from '../../base/TMAlert';
|
|
5
5
|
import TMTooltip from '../../base/TMTooltip';
|
|
6
6
|
import * as S from './styles';
|
|
7
|
-
import {
|
|
7
|
+
import { IconAdd, IconCloseOutline, IconMenuVertical, IconPin, IconSave, IconSeparator, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
|
|
8
|
+
import { ButtonNames, TMMessageBoxManager } from '../../base/TMPopUp';
|
|
9
|
+
const Separator = (props) => (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M12 2v20", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" }) }));
|
|
8
10
|
const IconDraggableDots = (props) => (_jsx("svg", { fontSize: 18, viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M9 3a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm10-18a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0z" }) }));
|
|
9
|
-
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [],
|
|
11
|
+
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 100, }) => {
|
|
12
|
+
const percentToPixels = (percent, containerSize) => {
|
|
13
|
+
return (percent / 100) * containerSize;
|
|
14
|
+
};
|
|
15
|
+
const pixelsToPercent = (pixels, containerSize) => {
|
|
16
|
+
return (pixels / containerSize) * 100;
|
|
17
|
+
};
|
|
18
|
+
const isPixelFormat = (pos) => {
|
|
19
|
+
return pos.x > 100 || pos.y > 100;
|
|
20
|
+
};
|
|
21
|
+
const migrateToPercentage = (pixelPos) => {
|
|
22
|
+
const container = containerRef.current?.getBoundingClientRect();
|
|
23
|
+
const containerWidth = isConstrained && container ? container.width : window.innerWidth;
|
|
24
|
+
const containerHeight = isConstrained && container ? container.height : window.innerHeight;
|
|
25
|
+
return {
|
|
26
|
+
x: pixelsToPercent(pixelPos.x, containerWidth),
|
|
27
|
+
y: pixelsToPercent(pixelPos.y, containerHeight),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const getDefaultConfig = () => ({
|
|
31
|
+
orientation: 'horizontal',
|
|
32
|
+
savedItemIds: [],
|
|
33
|
+
position: defaultPosition,
|
|
34
|
+
});
|
|
35
|
+
const resetFloatingBarSettings = () => {
|
|
36
|
+
// Reset the floatingMenuBar settings in SDKUI_Globals to trigger save to localStorage
|
|
37
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
38
|
+
orientation: 'horizontal',
|
|
39
|
+
itemIds: [],
|
|
40
|
+
position: defaultPosition,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
10
43
|
const loadConfig = () => {
|
|
11
44
|
try {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
45
|
+
const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
|
|
46
|
+
// Validate that settings object exists and has required properties with correct types
|
|
47
|
+
if (!settings || typeof settings !== 'object') {
|
|
48
|
+
console.warn('FloatingMenuBar: Invalid settings object, resetting to defaults');
|
|
49
|
+
resetFloatingBarSettings();
|
|
50
|
+
return getDefaultConfig();
|
|
51
|
+
}
|
|
52
|
+
// Validate position
|
|
53
|
+
const hasValidPosition = settings.position &&
|
|
54
|
+
typeof settings.position.x === 'number' &&
|
|
55
|
+
typeof settings.position.y === 'number' &&
|
|
56
|
+
!Number.isNaN(settings.position.x) &&
|
|
57
|
+
!Number.isNaN(settings.position.y) &&
|
|
58
|
+
Number.isFinite(settings.position.x) &&
|
|
59
|
+
Number.isFinite(settings.position.y);
|
|
60
|
+
if (!hasValidPosition) {
|
|
61
|
+
console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
|
|
62
|
+
resetFloatingBarSettings();
|
|
63
|
+
return getDefaultConfig();
|
|
64
|
+
}
|
|
65
|
+
// Ensure position is within reasonable viewport bounds
|
|
66
|
+
const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
|
|
67
|
+
const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
|
|
68
|
+
if (settings.position.x < 0 || settings.position.x > maxX ||
|
|
69
|
+
settings.position.y < 0 || settings.position.y > maxY) {
|
|
70
|
+
console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
|
|
71
|
+
resetFloatingBarSettings();
|
|
72
|
+
return getDefaultConfig();
|
|
73
|
+
}
|
|
74
|
+
// Validate orientation
|
|
75
|
+
const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
|
|
76
|
+
? settings.orientation
|
|
77
|
+
: 'horizontal';
|
|
78
|
+
// Validate itemIds
|
|
79
|
+
const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
|
|
80
|
+
if (validItemIds.length > 0) {
|
|
81
|
+
// Check if any ID looks like the old format (contains language-specific text or is too long)
|
|
82
|
+
const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
|
|
83
|
+
!id.startsWith('separator-') &&
|
|
84
|
+
(id.length > 20 || id.includes(' ')));
|
|
85
|
+
if (hasOldFormatIds) {
|
|
86
|
+
console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
|
|
87
|
+
resetFloatingBarSettings();
|
|
88
|
+
return getDefaultConfig();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Migrate old pixel-based position to percentage-based
|
|
92
|
+
let finalPosition = settings.position;
|
|
93
|
+
if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
|
|
94
|
+
console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
|
|
95
|
+
finalPosition = migrateToPercentage(settings.position);
|
|
96
|
+
// Save migrated position immediately
|
|
97
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
98
|
+
orientation: validOrientation,
|
|
99
|
+
itemIds: validItemIds,
|
|
100
|
+
position: finalPosition,
|
|
101
|
+
positionFormat: 'percentage',
|
|
19
102
|
};
|
|
20
103
|
}
|
|
104
|
+
return {
|
|
105
|
+
orientation: validOrientation,
|
|
106
|
+
savedItemIds: validItemIds,
|
|
107
|
+
position: finalPosition,
|
|
108
|
+
};
|
|
21
109
|
}
|
|
22
110
|
catch (error) {
|
|
23
111
|
console.error('Failed to load FloatingMenuBar config:', error);
|
|
112
|
+
// Reset to defaults on any error
|
|
113
|
+
try {
|
|
114
|
+
resetFloatingBarSettings();
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
console.error('Failed to reset FloatingMenuBar settings:', e);
|
|
118
|
+
}
|
|
119
|
+
return getDefaultConfig();
|
|
24
120
|
}
|
|
25
|
-
return {
|
|
26
|
-
orientation: 'horizontal',
|
|
27
|
-
pinnedItemIds: new Set(),
|
|
28
|
-
savedItemIds: [],
|
|
29
|
-
};
|
|
30
121
|
};
|
|
31
122
|
const initialConfig = loadConfig();
|
|
32
123
|
const [state, setState] = useState({
|
|
33
|
-
position:
|
|
124
|
+
position: initialConfig.position, // Stored as percentage
|
|
34
125
|
isDragging: false,
|
|
35
126
|
isConfigMode: false,
|
|
36
127
|
orientation: initialConfig.orientation,
|
|
@@ -39,32 +130,62 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
39
130
|
});
|
|
40
131
|
const floatingRef = useRef(null);
|
|
41
132
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
42
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
43
133
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
44
|
-
const [
|
|
45
|
-
|
|
134
|
+
const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
|
|
135
|
+
const containerSizeRef = useRef({ width: 0, height: 0 });
|
|
136
|
+
const stateSnapshot = useRef(null);
|
|
46
137
|
const floatingBarItemIds = useRef(new Set());
|
|
47
138
|
const floatingBarItemNames = useRef(new Set());
|
|
48
|
-
// Update refs when items change, but don't trigger re-renders
|
|
49
139
|
useEffect(() => {
|
|
50
140
|
floatingBarItemIds.current = new Set(state.items.map(i => i.id));
|
|
51
141
|
floatingBarItemNames.current = new Set(state.items.map(i => i.name));
|
|
52
142
|
}, [state.items]);
|
|
53
|
-
//
|
|
143
|
+
// Calculate pixel position from percentage when container size or position changes
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const updatePixelPosition = () => {
|
|
146
|
+
if (!containerRef.current || !floatingRef.current)
|
|
147
|
+
return;
|
|
148
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
149
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
150
|
+
const containerWidth = isConstrained ? container.width : window.innerWidth;
|
|
151
|
+
const containerHeight = isConstrained ? container.height : window.innerHeight;
|
|
152
|
+
containerSizeRef.current = { width: containerWidth, height: containerHeight };
|
|
153
|
+
let newX = percentToPixels(state.position.x, containerWidth);
|
|
154
|
+
let newY = percentToPixels(state.position.y, containerHeight);
|
|
155
|
+
newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
|
|
156
|
+
newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
|
|
157
|
+
setPixelPosition({ x: newX, y: newY });
|
|
158
|
+
};
|
|
159
|
+
updatePixelPosition();
|
|
160
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
161
|
+
updatePixelPosition();
|
|
162
|
+
});
|
|
163
|
+
if (containerRef.current) {
|
|
164
|
+
resizeObserver.observe(containerRef.current);
|
|
165
|
+
}
|
|
166
|
+
if (!isConstrained) {
|
|
167
|
+
window.addEventListener('resize', updatePixelPosition);
|
|
168
|
+
}
|
|
169
|
+
return () => {
|
|
170
|
+
resizeObserver.disconnect();
|
|
171
|
+
if (!isConstrained) {
|
|
172
|
+
window.removeEventListener('resize', updatePixelPosition);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}, [state.position, isConstrained]);
|
|
54
176
|
const flattenMenuItems = useCallback((items, parentPath = '') => {
|
|
55
177
|
const result = [];
|
|
56
178
|
items.forEach((item, index) => {
|
|
57
|
-
const itemId = `${parentPath}${item.name}-${index}`;
|
|
58
|
-
// Only add items that have onClick (final actions, not submenu parents)
|
|
179
|
+
const itemId = item.id || `${parentPath}${item.name}-${index}`;
|
|
59
180
|
if (item.onClick && !item.submenu) {
|
|
181
|
+
const isPinned = state.items.some(i => i.id === itemId);
|
|
60
182
|
result.push({
|
|
61
183
|
id: itemId,
|
|
62
184
|
name: item.name,
|
|
63
|
-
icon: item.icon || _jsx(
|
|
185
|
+
icon: item.icon || _jsx(IconPin, {}),
|
|
64
186
|
onClick: item.onClick,
|
|
65
187
|
disabled: item.disabled,
|
|
66
|
-
isPinned:
|
|
67
|
-
originalMenuItem: item,
|
|
188
|
+
isPinned: isPinned,
|
|
68
189
|
});
|
|
69
190
|
}
|
|
70
191
|
// Recursively process submenus
|
|
@@ -73,14 +194,25 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
73
194
|
}
|
|
74
195
|
});
|
|
75
196
|
return result;
|
|
76
|
-
}, [
|
|
197
|
+
}, [state.items]);
|
|
77
198
|
// Restore items on mount from savedItemIds
|
|
78
199
|
useEffect(() => {
|
|
79
|
-
if (contextMenuItems.length > 0) {
|
|
200
|
+
if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0) {
|
|
80
201
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
81
202
|
// Restore items in the saved order from localStorage
|
|
82
203
|
const restoredItems = initialConfig.savedItemIds
|
|
83
|
-
.map((id) =>
|
|
204
|
+
.map((id) => {
|
|
205
|
+
if (id.startsWith('separator-')) {
|
|
206
|
+
return {
|
|
207
|
+
id,
|
|
208
|
+
name: 'Separator',
|
|
209
|
+
icon: _jsx(Separator, {}),
|
|
210
|
+
onClick: () => { },
|
|
211
|
+
isSeparator: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return flatItems.find(item => item.id === id);
|
|
215
|
+
})
|
|
84
216
|
.filter((item) => item !== undefined);
|
|
85
217
|
if (restoredItems.length > 0) {
|
|
86
218
|
setState(s => ({ ...s, items: restoredItems }));
|
|
@@ -89,40 +221,36 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
89
221
|
}, []); // Only run once on mount
|
|
90
222
|
const togglePin = useCallback((item) => {
|
|
91
223
|
setState(s => {
|
|
92
|
-
const isInFloatingBar = s.items.some(i => i.id === item.id
|
|
224
|
+
const isInFloatingBar = s.items.some(i => i.id === item.id);
|
|
93
225
|
if (isInFloatingBar) {
|
|
94
226
|
// Remove from floating bar
|
|
95
|
-
const newItems = s.items.filter(i => i.id !== item.id
|
|
227
|
+
const newItems = s.items.filter(i => i.id !== item.id);
|
|
96
228
|
return { ...s, items: newItems };
|
|
97
229
|
}
|
|
98
230
|
else {
|
|
99
231
|
// Add to floating bar
|
|
100
232
|
if (s.items.length >= maxItems) {
|
|
101
|
-
|
|
102
|
-
|
|
233
|
+
ShowAlert({
|
|
234
|
+
mode: 'warning',
|
|
235
|
+
title: 'Limite Massimo Raggiunto',
|
|
236
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
237
|
+
duration: 4000,
|
|
238
|
+
});
|
|
103
239
|
return s;
|
|
104
240
|
}
|
|
105
241
|
return { ...s, items: [...s.items, item] };
|
|
106
242
|
}
|
|
107
243
|
});
|
|
108
|
-
// Update pinned IDs for context menu items
|
|
109
|
-
setPinnedItemIds(prev => {
|
|
110
|
-
const newSet = new Set(prev);
|
|
111
|
-
if (newSet.has(item.id)) {
|
|
112
|
-
newSet.delete(item.id);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
newSet.add(item.id);
|
|
116
|
-
}
|
|
117
|
-
return newSet;
|
|
118
|
-
});
|
|
119
244
|
}, [maxItems]);
|
|
120
245
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
121
|
-
const getCurrentItemState = useCallback((
|
|
246
|
+
const getCurrentItemState = useCallback((itemId) => {
|
|
122
247
|
const findInItems = (items) => {
|
|
123
|
-
for (
|
|
124
|
-
|
|
248
|
+
for (let i = 0; i < items.length; i++) {
|
|
249
|
+
const item = items[i];
|
|
250
|
+
// Match by ID if the item has one
|
|
251
|
+
if (item.id === itemId)
|
|
125
252
|
return item;
|
|
253
|
+
// Check in submenu
|
|
126
254
|
if (item.submenu) {
|
|
127
255
|
const found = findInItems(item.submenu);
|
|
128
256
|
if (found)
|
|
@@ -136,34 +264,96 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
136
264
|
disabled: foundItem?.disabled,
|
|
137
265
|
onClick: foundItem?.onClick
|
|
138
266
|
};
|
|
139
|
-
}, [contextMenuItems]);
|
|
140
|
-
|
|
267
|
+
}, [contextMenuItems]);
|
|
268
|
+
// Remove trailing separators from items array
|
|
269
|
+
const removeTrailingSeparators = useCallback((items) => {
|
|
270
|
+
const result = [...items];
|
|
271
|
+
while (result.length > 0 && result.at(-1)?.isSeparator) {
|
|
272
|
+
result.pop();
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}, []);
|
|
276
|
+
// Create a new separator item
|
|
277
|
+
const createSeparator = useCallback(() => {
|
|
278
|
+
const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
279
|
+
return {
|
|
280
|
+
id: separatorId,
|
|
281
|
+
name: 'Separator',
|
|
282
|
+
icon: _jsx(Separator, {}),
|
|
283
|
+
onClick: () => { },
|
|
284
|
+
isSeparator: true,
|
|
285
|
+
};
|
|
286
|
+
}, []);
|
|
287
|
+
// Add separator to items
|
|
288
|
+
const addSeparator = useCallback(() => {
|
|
289
|
+
if (state.items.length >= maxItems) {
|
|
290
|
+
ShowAlert({
|
|
291
|
+
mode: 'warning',
|
|
292
|
+
title: 'Limite Massimo Raggiunto',
|
|
293
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
294
|
+
duration: 4000,
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const separator = createSeparator();
|
|
299
|
+
setState(s => ({ ...s, items: [...s.items, separator] }));
|
|
300
|
+
}, [state.items.length, maxItems, createSeparator]);
|
|
301
|
+
const getPinContextMenuItems = useCallback(() => {
|
|
141
302
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
142
|
-
// Calculate current pinned items directly from state.items (not refs)
|
|
143
303
|
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
144
|
-
const
|
|
145
|
-
const enhanceItems = (items) => {
|
|
304
|
+
const createPinItems = (items) => {
|
|
146
305
|
return items.map(item => {
|
|
147
|
-
const flatItem = flatItems.find(fi => fi.
|
|
148
|
-
const itemId = flatItem?.id || '';
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const enhanced = {
|
|
306
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
307
|
+
const itemId = flatItem?.id || item.id || '';
|
|
308
|
+
const isAlreadyPinned = currentItemIds.has(itemId);
|
|
309
|
+
const pinItem = {
|
|
152
310
|
...item,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (flatItem) {
|
|
311
|
+
onClick: item.onClick && !item.submenu ? () => {
|
|
312
|
+
if (flatItem && !isAlreadyPinned) {
|
|
156
313
|
togglePin(flatItem);
|
|
157
314
|
}
|
|
158
315
|
} : undefined,
|
|
316
|
+
disabled: isAlreadyPinned,
|
|
159
317
|
};
|
|
160
318
|
if (item.submenu) {
|
|
161
|
-
|
|
319
|
+
pinItem.submenu = createPinItems(item.submenu);
|
|
162
320
|
}
|
|
163
|
-
return
|
|
321
|
+
return pinItem;
|
|
164
322
|
});
|
|
165
323
|
};
|
|
166
|
-
|
|
324
|
+
const pinItems = createPinItems(contextMenuItems);
|
|
325
|
+
// Add separator option at the end
|
|
326
|
+
pinItems.push({
|
|
327
|
+
id: 'add-separator',
|
|
328
|
+
name: SDKUI_Localizator.Add + ' separatore',
|
|
329
|
+
icon: _jsx(IconSeparator, {}),
|
|
330
|
+
onClick: addSeparator,
|
|
331
|
+
beginGroup: true
|
|
332
|
+
});
|
|
333
|
+
return pinItems;
|
|
334
|
+
}, [contextMenuItems, flattenMenuItems, togglePin, state.items, addSeparator]);
|
|
335
|
+
const getContextMenuItemsWithPinIcons = useCallback(() => {
|
|
336
|
+
const flatItems = flattenMenuItems(contextMenuItems);
|
|
337
|
+
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
338
|
+
const addPinIcons = (items) => {
|
|
339
|
+
return items.map(item => {
|
|
340
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
341
|
+
const itemId = flatItem?.id || item.id || '';
|
|
342
|
+
const isPinned = currentItemIds.has(itemId);
|
|
343
|
+
const itemWithPin = {
|
|
344
|
+
...item,
|
|
345
|
+
rightIcon: flatItem ? _jsx(IconPin, { color: isPinned ? 'red' : 'black' }) : undefined,
|
|
346
|
+
onRightIconClick: flatItem ? () => {
|
|
347
|
+
togglePin(flatItem);
|
|
348
|
+
} : undefined,
|
|
349
|
+
};
|
|
350
|
+
if (item.submenu) {
|
|
351
|
+
itemWithPin.submenu = addPinIcons(item.submenu);
|
|
352
|
+
}
|
|
353
|
+
return itemWithPin;
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
return addPinIcons(contextMenuItems);
|
|
167
357
|
}, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
|
|
168
358
|
const handleMouseDown = (e) => {
|
|
169
359
|
if (state.isConfigMode)
|
|
@@ -174,20 +364,25 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
174
364
|
if (isConstrained) {
|
|
175
365
|
// For absolute positioning, offset is relative to container
|
|
176
366
|
dragOffset.current = {
|
|
177
|
-
x: e.clientX - containerRect.left -
|
|
178
|
-
y: e.clientY - containerRect.top -
|
|
367
|
+
x: e.clientX - containerRect.left - pixelPosition.x,
|
|
368
|
+
y: e.clientY - containerRect.top - pixelPosition.y,
|
|
179
369
|
};
|
|
180
370
|
}
|
|
181
371
|
else {
|
|
182
372
|
// For fixed positioning, offset is relative to viewport
|
|
183
373
|
dragOffset.current = {
|
|
184
|
-
x: e.clientX -
|
|
185
|
-
y: e.clientY -
|
|
374
|
+
x: e.clientX - pixelPosition.x,
|
|
375
|
+
y: e.clientY - pixelPosition.y,
|
|
186
376
|
};
|
|
187
377
|
}
|
|
188
378
|
}
|
|
189
379
|
setState(s => ({ ...s, isDragging: true }));
|
|
190
380
|
};
|
|
381
|
+
const handleGripDoubleClick = () => {
|
|
382
|
+
if (state.isConfigMode)
|
|
383
|
+
return;
|
|
384
|
+
toggleOrientation();
|
|
385
|
+
};
|
|
191
386
|
const handleMouseMove = useCallback((e) => {
|
|
192
387
|
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
193
388
|
return;
|
|
@@ -210,50 +405,265 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
210
405
|
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
211
406
|
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
212
407
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
position: { x: newX, y: newY },
|
|
216
|
-
}));
|
|
408
|
+
// Update pixel position directly during drag
|
|
409
|
+
setPixelPosition({ x: newX, y: newY });
|
|
217
410
|
}, [state.isDragging, containerRef, isConstrained]);
|
|
218
411
|
const handleMouseUp = useCallback(() => {
|
|
219
|
-
|
|
220
|
-
|
|
412
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
413
|
+
// Convert final pixel position to percentage before updating state
|
|
414
|
+
const percentagePosition = {
|
|
415
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
416
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
417
|
+
};
|
|
418
|
+
setState(s => ({
|
|
419
|
+
...s,
|
|
420
|
+
isDragging: false,
|
|
421
|
+
position: percentagePosition,
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
426
|
+
}
|
|
427
|
+
}, [state.isDragging, pixelPosition]);
|
|
428
|
+
// Touch event handlers for tablet support
|
|
429
|
+
const handleTouchStart = (e) => {
|
|
430
|
+
if (state.isConfigMode)
|
|
431
|
+
return;
|
|
432
|
+
const touch = e.touches[0];
|
|
433
|
+
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
434
|
+
if (containerRect) {
|
|
435
|
+
if (isConstrained) {
|
|
436
|
+
dragOffset.current = {
|
|
437
|
+
x: touch.clientX - containerRect.left - pixelPosition.x,
|
|
438
|
+
y: touch.clientY - containerRect.top - pixelPosition.y,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
dragOffset.current = {
|
|
443
|
+
x: touch.clientX - pixelPosition.x,
|
|
444
|
+
y: touch.clientY - pixelPosition.y,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
setState(s => ({ ...s, isDragging: true }));
|
|
449
|
+
};
|
|
450
|
+
const handleTouchMove = useCallback((e) => {
|
|
451
|
+
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
452
|
+
return;
|
|
453
|
+
const touch = e.touches[0];
|
|
454
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
455
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
456
|
+
let newX, newY;
|
|
457
|
+
if (isConstrained) {
|
|
458
|
+
newX = touch.clientX - container.left - dragOffset.current.x;
|
|
459
|
+
newY = touch.clientY - container.top - dragOffset.current.y;
|
|
460
|
+
newX = Math.max(0, Math.min(newX, container.width - floating.width));
|
|
461
|
+
newY = Math.max(0, Math.min(newY, container.height - floating.height));
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
newX = touch.clientX - dragOffset.current.x;
|
|
465
|
+
newY = touch.clientY - dragOffset.current.y;
|
|
466
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
467
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
468
|
+
}
|
|
469
|
+
setPixelPosition({ x: newX, y: newY });
|
|
470
|
+
}, [state.isDragging, containerRef, isConstrained]);
|
|
471
|
+
const handleTouchEnd = useCallback(() => {
|
|
472
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
473
|
+
const percentagePosition = {
|
|
474
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
475
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
476
|
+
};
|
|
477
|
+
setState(s => ({
|
|
478
|
+
...s,
|
|
479
|
+
isDragging: false,
|
|
480
|
+
position: percentagePosition,
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
485
|
+
}
|
|
486
|
+
}, [state.isDragging, pixelPosition]);
|
|
221
487
|
useEffect(() => {
|
|
222
488
|
if (state.isDragging) {
|
|
223
489
|
document.addEventListener('mousemove', handleMouseMove);
|
|
224
490
|
document.addEventListener('mouseup', handleMouseUp);
|
|
491
|
+
document.addEventListener('touchmove', handleTouchMove);
|
|
492
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
225
493
|
return () => {
|
|
226
494
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
227
495
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
496
|
+
document.removeEventListener('touchmove', handleTouchMove);
|
|
497
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
228
498
|
};
|
|
229
499
|
}
|
|
230
500
|
return undefined;
|
|
231
|
-
}, [state.isDragging, handleMouseMove, handleMouseUp]);
|
|
232
|
-
// Save to
|
|
501
|
+
}, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
|
|
502
|
+
// Save to SDKUI_Globals.userSettings only when NOT in config mode (when applying changes)
|
|
233
503
|
useEffect(() => {
|
|
504
|
+
if (state.isConfigMode)
|
|
505
|
+
return; // Don't save during edit mode
|
|
234
506
|
try {
|
|
235
|
-
|
|
507
|
+
// Replace the entire object to trigger the Proxy
|
|
508
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
236
509
|
orientation: state.orientation,
|
|
237
|
-
|
|
238
|
-
|
|
510
|
+
itemIds: state.items.map(item => item.id),
|
|
511
|
+
position: state.position,
|
|
512
|
+
positionFormat: 'percentage',
|
|
239
513
|
};
|
|
240
|
-
localStorage.setItem(storageKey, JSON.stringify(config));
|
|
241
514
|
}
|
|
242
515
|
catch (error) {
|
|
243
516
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
244
517
|
}
|
|
245
|
-
}, [state.orientation, state.items,
|
|
518
|
+
}, [state.orientation, state.items, state.position, state.isConfigMode]);
|
|
246
519
|
const toggleConfigMode = () => {
|
|
247
|
-
setState(s =>
|
|
520
|
+
setState(s => {
|
|
521
|
+
if (!s.isConfigMode) {
|
|
522
|
+
stateSnapshot.current = {
|
|
523
|
+
items: [...s.items],
|
|
524
|
+
orientation: s.orientation,
|
|
525
|
+
position: { ...s.position },
|
|
526
|
+
};
|
|
527
|
+
return { ...s, isConfigMode: true };
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
// Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
|
|
531
|
+
stateSnapshot.current = null;
|
|
532
|
+
const cleanedItems = removeTrailingSeparators(s.items);
|
|
533
|
+
return { ...s, isConfigMode: false, items: cleanedItems };
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
// Auto-reposition when entering edit mode to ensure Apply/Undo buttons are visible
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
if (!state.isConfigMode || !floatingRef.current)
|
|
540
|
+
return;
|
|
541
|
+
// Use double requestAnimationFrame to ensure the DOM has fully updated with new buttons
|
|
542
|
+
requestAnimationFrame(() => {
|
|
543
|
+
requestAnimationFrame(() => {
|
|
544
|
+
if (!floatingRef.current)
|
|
545
|
+
return;
|
|
546
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
547
|
+
const containerWidth = isConstrained && containerRef.current
|
|
548
|
+
? containerRef.current.getBoundingClientRect().width
|
|
549
|
+
: window.innerWidth;
|
|
550
|
+
const containerHeight = isConstrained && containerRef.current
|
|
551
|
+
? containerRef.current.getBoundingClientRect().height
|
|
552
|
+
: window.innerHeight;
|
|
553
|
+
// Use current pixel position
|
|
554
|
+
let newPixelX = pixelPosition.x;
|
|
555
|
+
let newPixelY = pixelPosition.y;
|
|
556
|
+
let needsUpdate = false;
|
|
557
|
+
// Check horizontal overflow
|
|
558
|
+
if (newPixelX + floating.width > containerWidth) {
|
|
559
|
+
newPixelX = Math.max(0, containerWidth - floating.width);
|
|
560
|
+
needsUpdate = true;
|
|
561
|
+
}
|
|
562
|
+
// Check vertical overflow
|
|
563
|
+
if (newPixelY + floating.height > containerHeight) {
|
|
564
|
+
newPixelY = Math.max(0, containerHeight - floating.height);
|
|
565
|
+
needsUpdate = true;
|
|
566
|
+
}
|
|
567
|
+
if (needsUpdate) {
|
|
568
|
+
// Update pixel position immediately
|
|
569
|
+
setPixelPosition({ x: newPixelX, y: newPixelY });
|
|
570
|
+
// Convert to percentage for state
|
|
571
|
+
const newPercentagePosition = {
|
|
572
|
+
x: pixelsToPercent(newPixelX, containerWidth),
|
|
573
|
+
y: pixelsToPercent(newPixelY, containerHeight),
|
|
574
|
+
};
|
|
575
|
+
setState(s => ({
|
|
576
|
+
...s,
|
|
577
|
+
position: newPercentagePosition,
|
|
578
|
+
}));
|
|
579
|
+
// Update snapshot position to the corrected position so Undo restores to visible position
|
|
580
|
+
if (stateSnapshot.current) {
|
|
581
|
+
stateSnapshot.current.position = newPercentagePosition;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}, [state.isConfigMode, state.orientation, isConstrained, state.items, pixelPosition.x, pixelPosition.y]);
|
|
587
|
+
const handleUndo = () => {
|
|
588
|
+
if (stateSnapshot.current) {
|
|
589
|
+
setState(s => ({
|
|
590
|
+
...s,
|
|
591
|
+
items: [...stateSnapshot.current.items],
|
|
592
|
+
orientation: stateSnapshot.current.orientation,
|
|
593
|
+
position: { ...stateSnapshot.current.position },
|
|
594
|
+
isConfigMode: true,
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
// Check if current state has changed from snapshot
|
|
599
|
+
const hasChanges = () => {
|
|
600
|
+
if (!stateSnapshot.current)
|
|
601
|
+
return false;
|
|
602
|
+
// Check if items have changed (different IDs or different order)
|
|
603
|
+
const currentIds = state.items.map(i => i.id).join(',');
|
|
604
|
+
const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
|
|
605
|
+
return currentIds !== snapshotIds;
|
|
606
|
+
};
|
|
607
|
+
const handleClose = () => {
|
|
608
|
+
// If all items removed, exit without asking and restore last items
|
|
609
|
+
if (state.items.length === 0 && stateSnapshot.current) {
|
|
610
|
+
setState(s => ({
|
|
611
|
+
...s,
|
|
612
|
+
items: [...stateSnapshot.current.items],
|
|
613
|
+
orientation: stateSnapshot.current.orientation,
|
|
614
|
+
position: { ...stateSnapshot.current.position },
|
|
615
|
+
isConfigMode: false,
|
|
616
|
+
}));
|
|
617
|
+
stateSnapshot.current = null;
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
// If no changes, simply exit config mode
|
|
621
|
+
if (!hasChanges()) {
|
|
622
|
+
stateSnapshot.current = null;
|
|
623
|
+
const cleanedItems = removeTrailingSeparators(state.items);
|
|
624
|
+
setState(s => ({ ...s, isConfigMode: false, items: cleanedItems }));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
// If there are changes, ask for confirmation
|
|
628
|
+
TMMessageBoxManager.show({
|
|
629
|
+
message: 'Perderai le tue modifiche, sei sicuro?',
|
|
630
|
+
buttons: [ButtonNames.YES, ButtonNames.NO],
|
|
631
|
+
onButtonClick: (buttonName) => {
|
|
632
|
+
if (buttonName === ButtonNames.YES && stateSnapshot.current) {
|
|
633
|
+
// Restore snapshot and exit config mode
|
|
634
|
+
setState(s => ({
|
|
635
|
+
...s,
|
|
636
|
+
items: [...stateSnapshot.current.items],
|
|
637
|
+
orientation: stateSnapshot.current.orientation,
|
|
638
|
+
position: { ...stateSnapshot.current.position },
|
|
639
|
+
isConfigMode: false,
|
|
640
|
+
}));
|
|
641
|
+
stateSnapshot.current = null;
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
});
|
|
248
645
|
};
|
|
249
646
|
const toggleOrientation = () => {
|
|
250
647
|
const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
|
251
|
-
|
|
648
|
+
if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
|
|
649
|
+
if (floatingRef.current) {
|
|
650
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
651
|
+
const screenHeight = window.innerHeight;
|
|
652
|
+
if (floating.width > screenHeight - 70) {
|
|
653
|
+
ShowAlert({
|
|
654
|
+
mode: 'warning',
|
|
655
|
+
title: 'Troppi elementi',
|
|
656
|
+
message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
|
|
657
|
+
duration: 4000,
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
252
663
|
setState(s => ({
|
|
253
664
|
...s,
|
|
254
665
|
orientation: newOrientation,
|
|
255
666
|
}));
|
|
256
|
-
// Then, after DOM updates, adjust position to stay in bounds
|
|
257
667
|
requestAnimationFrame(() => {
|
|
258
668
|
requestAnimationFrame(() => {
|
|
259
669
|
if (containerRef.current && floatingRef.current) {
|
|
@@ -286,12 +696,6 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
286
696
|
...s,
|
|
287
697
|
items: s.items.filter(item => item.id !== itemId),
|
|
288
698
|
}));
|
|
289
|
-
// Also remove from pinned items if it was pinned
|
|
290
|
-
setPinnedItemIds(prev => {
|
|
291
|
-
const newSet = new Set(prev);
|
|
292
|
-
newSet.delete(itemId);
|
|
293
|
-
return newSet;
|
|
294
|
-
});
|
|
295
699
|
};
|
|
296
700
|
// Drag and drop for reordering
|
|
297
701
|
const handleDragStart = (e, index) => {
|
|
@@ -347,24 +751,37 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
347
751
|
setState(s => ({ ...s, draggedItemIndex: null }));
|
|
348
752
|
setDragOverIndex(null);
|
|
349
753
|
};
|
|
350
|
-
return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x":
|
|
754
|
+
return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, onContextMenu: (e) => e.preventDefault(), children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
|
|
755
|
+
{
|
|
756
|
+
name: SDKUI_Localizator.Configure,
|
|
757
|
+
onClick: () => {
|
|
758
|
+
if (!state.isConfigMode) {
|
|
759
|
+
toggleConfigMode();
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
765
|
+
onClick: toggleOrientation,
|
|
766
|
+
},
|
|
767
|
+
], trigger: "right", children: _jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) }) })) : (_jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) })), _jsx(S.Separator, { "$orientation": state.orientation }), state.items.map((item, index) => {
|
|
768
|
+
// Handle separator items
|
|
769
|
+
if (item.isSeparator) {
|
|
770
|
+
return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
|
|
771
|
+
}
|
|
351
772
|
// Get current state (disabled and onClick) from contextMenuItems
|
|
352
|
-
const currentState = getCurrentItemState(item.
|
|
773
|
+
const currentState = getCurrentItemState(item.id);
|
|
353
774
|
const isDisabled = currentState.disabled || false;
|
|
354
775
|
const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
|
|
355
|
-
return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [
|
|
356
|
-
if (state.isConfigMode || isDisabled)
|
|
357
|
-
return;
|
|
358
|
-
if (currentOnClick) {
|
|
359
|
-
currentOnClick();
|
|
360
|
-
}
|
|
361
|
-
}, disabled: isDisabled && !state.isConfigMode, children: item.icon })) : (_jsx(TMTooltip, { content: item.name, position: "top", children: _jsx(S.MenuButton, { onClick: () => {
|
|
776
|
+
return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
|
|
362
777
|
if (state.isConfigMode || isDisabled)
|
|
363
778
|
return;
|
|
364
779
|
if (currentOnClick) {
|
|
365
780
|
currentOnClick();
|
|
366
781
|
}
|
|
367
|
-
}, disabled: isDisabled, children: item.icon }) })
|
|
368
|
-
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items:
|
|
782
|
+
}, disabled: state.isConfigMode ? isDisabled && !state.isConfigMode : isDisabled, children: item.icon }) }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
|
|
783
|
+
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: true, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0
|
|
784
|
+
? 'Devi aggiungere almeno un item'
|
|
785
|
+
: SDKUI_Localizator.Save, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0 || !hasChanges(), children: _jsx(IconSave, { fontSize: 20 }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Close, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.CloseButton, { onClick: handleClose, children: _jsx(IconCloseOutline, { fontSize: 20 }) }) })] })] }))] })] }));
|
|
369
786
|
};
|
|
370
787
|
export default TMFloatingMenuBar;
|