@topconsultnpm/sdkui-react 6.20.0-dev1.11 → 6.20.0-dev1.110
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/Toppy-help-center.png +0 -0
- package/lib/assets/headergradient.svg +87 -0
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +285 -28
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +8 -1
- package/lib/components/NewComponents/ContextMenu/hooks.js +80 -8
- 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 +9 -1
- package/lib/components/NewComponents/ContextMenu/styles.js +157 -37
- package/lib/components/NewComponents/ContextMenu/types.d.ts +14 -1
- 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 +563 -112
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +21 -5
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +210 -58
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +8 -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 +153 -11
- package/lib/components/base/TMDropDownMenu.js +19 -18
- package/lib/components/base/TMFileManager.d.ts +4 -3
- package/lib/components/base/TMFileManager.js +32 -24
- package/lib/components/base/TMFileManagerDataGridView.d.ts +3 -2
- package/lib/components/base/TMFileManagerDataGridView.js +1 -11
- package/lib/components/base/TMFileManagerThumbnailItems.d.ts +7 -1
- package/lib/components/base/TMFileManagerThumbnailItems.js +5 -2
- package/lib/components/base/TMFileManagerThumbnailsView.d.ts +17 -4
- package/lib/components/base/TMFileManagerThumbnailsView.js +18 -6
- package/lib/components/base/TMFileManagerUtils.d.ts +0 -12
- package/lib/components/base/TMListView.js +33 -15
- package/lib/components/base/TMPanel.d.ts +1 -1
- package/lib/components/base/TMPanel.js +1 -1
- package/lib/components/choosers/TMDistinctValues.js +2 -2
- 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/TMDateBox.js +18 -9
- package/lib/components/editors/TMLocalizedTextBox.d.ts +3 -1
- package/lib/components/editors/TMLocalizedTextBox.js +16 -14
- package/lib/components/editors/TMMetadataTextBox.d.ts +9 -0
- package/lib/components/editors/TMMetadataTextBox.js +92 -0
- package/lib/components/editors/TMMetadataValues.js +23 -5
- package/lib/components/editors/TMTextArea.js +18 -30
- package/lib/components/editors/TMTextBox.d.ts +1 -1
- package/lib/components/editors/TMTextBox.js +6 -3
- package/lib/components/editors/TMTextExpression.js +6 -91
- package/lib/components/features/archive/TMArchive.js +2 -2
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.d.ts +15 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +460 -0
- package/lib/components/features/assistant/TMToppySpeechBubble.d.ts +11 -0
- package/lib/components/features/assistant/TMToppySpeechBubble.js +126 -0
- package/lib/components/features/documents/TMDcmtForm.d.ts +14 -2
- package/lib/components/features/documents/TMDcmtForm.js +457 -206
- package/lib/components/features/documents/TMDcmtPreview.js +44 -110
- package/lib/components/features/documents/TMDcmtTasks.js +9 -9
- package/lib/components/features/documents/TMMasterDetailDcmts.js +38 -53
- 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} +2 -2
- package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
- package/lib/components/features/search/TMSearch.d.ts +3 -0
- package/lib/components/features/search/TMSearch.js +50 -11
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryPanel.js +29 -21
- package/lib/components/features/search/TMSearchResult.d.ts +3 -0
- package/lib/components/features/search/TMSearchResult.js +208 -250
- 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 +2 -2
- package/lib/components/features/tasks/TMTaskForm.js +20 -1
- package/lib/components/features/tasks/TMTasksAgenda.d.ts +3 -1
- package/lib/components/features/tasks/TMTasksAgenda.js +48 -9
- package/lib/components/features/tasks/TMTasksCalendar.d.ts +2 -0
- package/lib/components/features/tasks/TMTasksCalendar.js +19 -7
- package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
- package/lib/components/features/tasks/TMTasksUtils.js +43 -36
- package/lib/components/features/tasks/TMTasksView.js +28 -19
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
- package/lib/components/features/workflow/TMWorkflowPopup.js +139 -34
- package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +2 -0
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +12 -7
- package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
- package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
- package/lib/components/features/workflow/diagram/WFDiagram.d.ts +4 -0
- package/lib/components/features/workflow/diagram/WFDiagram.js +164 -13
- 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 +56 -31
- package/lib/components/grids/TMRecentsManager.js +20 -10
- package/lib/components/index.d.ts +6 -3
- package/lib/components/index.js +6 -3
- package/lib/components/query/TMQueryEditor.d.ts +2 -1
- package/lib/components/query/TMQueryEditor.js +92 -92
- package/lib/components/settings/SettingsAppearance.d.ts +2 -1
- package/lib/components/settings/SettingsAppearance.js +99 -30
- package/lib/components/sidebar/TMHeader.js +7 -7
- package/lib/components/sidebar/TMSidebar.d.ts +0 -1
- package/lib/components/sidebar/TMSidebar.js +16 -44
- package/lib/components/sidebar/TMSidebarItem.js +34 -17
- 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 +22 -0
- package/lib/helper/SDKUI_Globals.js +10 -1
- package/lib/helper/SDKUI_Localizator.d.ts +17 -1
- package/lib/helper/SDKUI_Localizator.js +167 -1
- package/lib/helper/TMCommandsContextMenu.d.ts +4 -2
- package/lib/helper/TMCommandsContextMenu.js +15 -4
- package/lib/helper/TMIcons.d.ts +4 -0
- package/lib/helper/TMIcons.js +13 -3
- package/lib/helper/TMPdfViewer.d.ts +8 -0
- package/lib/helper/TMPdfViewer.js +373 -0
- package/lib/helper/checkinCheckoutManager.d.ts +31 -1
- package/lib/helper/checkinCheckoutManager.js +112 -30
- package/lib/helper/devextremeCustomMessages.d.ts +30 -0
- package/lib/helper/devextremeCustomMessages.js +30 -0
- package/lib/helper/helpers.d.ts +28 -1
- package/lib/helper/helpers.js +130 -3
- package/lib/helper/index.d.ts +2 -0
- package/lib/helper/index.js +2 -0
- package/lib/helper/queryHelper.d.ts +1 -1
- package/lib/helper/queryHelper.js +33 -3
- package/lib/helper/workItemsHelper.d.ts +6 -0
- package/lib/helper/workItemsHelper.js +230 -0
- 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 +132 -0
- package/lib/hooks/useDataUserIdItem.d.ts +10 -0
- package/lib/hooks/useDataUserIdItem.js +96 -0
- package/lib/hooks/useMetadataExpression.d.ts +19 -0
- package/lib/hooks/useMetadataExpression.js +99 -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 +3 -2
- package/lib/services/platform_services.d.ts +3 -3
- package/lib/ts/types.d.ts +61 -1
- package/lib/utils/theme.d.ts +1 -1
- package/lib/utils/theme.js +1 -1
- package/package.json +7 -4
- 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/base/TMContextMenu.d.ts +0 -25
- package/lib/components/base/TMContextMenu.js +0 -109
- package/lib/components/base/TMContextMenuOLD.d.ts +0 -26
- package/lib/components/base/TMContextMenuOLD.js +0 -56
- package/lib/components/base/TMFloatingToolbar.d.ts +0 -9
- package/lib/components/base/TMFloatingToolbar.js +0 -101
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +0 -30
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +0 -482
- package/lib/components/features/assistant/ToppySpeechBubble.d.ts +0 -9
- package/lib/components/features/assistant/ToppySpeechBubble.js +0 -117
- package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
|
@@ -1,36 +1,133 @@
|
|
|
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: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, contextMenuDefaultPinnedIds = [], defaultItems = [], disbaleConfigMode = false, bgColor = undefined, }) => {
|
|
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: defaultOrientation,
|
|
32
|
+
savedItemIds: contextMenuDefaultPinnedIds,
|
|
33
|
+
position: defaultPosition,
|
|
34
|
+
});
|
|
35
|
+
const resetFloatingBarSettings = () => {
|
|
36
|
+
// Reset the floatingMenuBar settings in SDKUI_Globals to empty
|
|
37
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
38
|
+
orientation: undefined,
|
|
39
|
+
itemIds: undefined,
|
|
40
|
+
position: undefined,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
10
43
|
const loadConfig = () => {
|
|
44
|
+
// If config mode is disabled, use default position and orientation
|
|
45
|
+
if (disbaleConfigMode) {
|
|
46
|
+
return {
|
|
47
|
+
orientation: defaultOrientation,
|
|
48
|
+
savedItemIds: contextMenuDefaultPinnedIds,
|
|
49
|
+
position: defaultPosition,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
11
52
|
try {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
53
|
+
const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
|
|
54
|
+
// If localStorage is empty (first time), use props as defaults
|
|
55
|
+
if (!settings || !settings.position || !settings.itemIds) {
|
|
56
|
+
return getDefaultConfig();
|
|
57
|
+
}
|
|
58
|
+
// Validate position
|
|
59
|
+
const hasValidPosition = settings.position &&
|
|
60
|
+
typeof settings.position.x === 'number' &&
|
|
61
|
+
typeof settings.position.y === 'number' &&
|
|
62
|
+
!Number.isNaN(settings.position.x) &&
|
|
63
|
+
!Number.isNaN(settings.position.y) &&
|
|
64
|
+
Number.isFinite(settings.position.x) &&
|
|
65
|
+
Number.isFinite(settings.position.y);
|
|
66
|
+
if (!hasValidPosition) {
|
|
67
|
+
console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
|
|
68
|
+
resetFloatingBarSettings();
|
|
69
|
+
return getDefaultConfig();
|
|
70
|
+
}
|
|
71
|
+
// Ensure position is within reasonable viewport bounds
|
|
72
|
+
const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
|
|
73
|
+
const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
|
|
74
|
+
if (settings.position.x < 0 || settings.position.x > maxX ||
|
|
75
|
+
settings.position.y < 0 || settings.position.y > maxY) {
|
|
76
|
+
console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
|
|
77
|
+
resetFloatingBarSettings();
|
|
78
|
+
return getDefaultConfig();
|
|
79
|
+
}
|
|
80
|
+
// Validate orientation
|
|
81
|
+
const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
|
|
82
|
+
? settings.orientation
|
|
83
|
+
: 'horizontal';
|
|
84
|
+
// Validate itemIds
|
|
85
|
+
const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
|
|
86
|
+
if (validItemIds.length > 0) {
|
|
87
|
+
// Check if any ID looks like the old format (contains language-specific text or is too long)
|
|
88
|
+
const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
|
|
89
|
+
!id.startsWith('separator-') &&
|
|
90
|
+
(id.length > 20 || id.includes(' ')));
|
|
91
|
+
if (hasOldFormatIds) {
|
|
92
|
+
console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
|
|
93
|
+
resetFloatingBarSettings();
|
|
94
|
+
return getDefaultConfig();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Migrate old pixel-based position to percentage-based
|
|
98
|
+
let finalPosition = settings.position;
|
|
99
|
+
if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
|
|
100
|
+
console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
|
|
101
|
+
finalPosition = migrateToPercentage(settings.position);
|
|
102
|
+
// Save migrated position immediately
|
|
103
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
104
|
+
orientation: validOrientation,
|
|
105
|
+
itemIds: validItemIds,
|
|
106
|
+
position: finalPosition,
|
|
107
|
+
positionFormat: 'percentage',
|
|
19
108
|
};
|
|
20
109
|
}
|
|
110
|
+
return {
|
|
111
|
+
orientation: validOrientation,
|
|
112
|
+
savedItemIds: validItemIds,
|
|
113
|
+
position: finalPosition,
|
|
114
|
+
};
|
|
21
115
|
}
|
|
22
116
|
catch (error) {
|
|
23
117
|
console.error('Failed to load FloatingMenuBar config:', error);
|
|
118
|
+
// Reset to defaults on any error
|
|
119
|
+
try {
|
|
120
|
+
resetFloatingBarSettings();
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
console.error('Failed to reset FloatingMenuBar settings:', e);
|
|
124
|
+
}
|
|
125
|
+
return getDefaultConfig();
|
|
24
126
|
}
|
|
25
|
-
return {
|
|
26
|
-
orientation: 'horizontal',
|
|
27
|
-
pinnedItemIds: new Set(),
|
|
28
|
-
savedItemIds: [],
|
|
29
|
-
};
|
|
30
127
|
};
|
|
31
128
|
const initialConfig = loadConfig();
|
|
32
129
|
const [state, setState] = useState({
|
|
33
|
-
position:
|
|
130
|
+
position: initialConfig.position, // Stored as percentage
|
|
34
131
|
isDragging: false,
|
|
35
132
|
isConfigMode: false,
|
|
36
133
|
orientation: initialConfig.orientation,
|
|
@@ -39,32 +136,62 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
39
136
|
});
|
|
40
137
|
const floatingRef = useRef(null);
|
|
41
138
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
42
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
43
139
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
44
|
-
const [
|
|
45
|
-
|
|
140
|
+
const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
|
|
141
|
+
const containerSizeRef = useRef({ width: 0, height: 0 });
|
|
142
|
+
const stateSnapshot = useRef(null);
|
|
46
143
|
const floatingBarItemIds = useRef(new Set());
|
|
47
144
|
const floatingBarItemNames = useRef(new Set());
|
|
48
|
-
// Update refs when items change, but don't trigger re-renders
|
|
49
145
|
useEffect(() => {
|
|
50
146
|
floatingBarItemIds.current = new Set(state.items.map(i => i.id));
|
|
51
147
|
floatingBarItemNames.current = new Set(state.items.map(i => i.name));
|
|
52
148
|
}, [state.items]);
|
|
53
|
-
//
|
|
149
|
+
// Calculate pixel position from percentage when container size or position changes
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const updatePixelPosition = () => {
|
|
152
|
+
if (!containerRef.current || !floatingRef.current)
|
|
153
|
+
return;
|
|
154
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
155
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
156
|
+
const containerWidth = isConstrained ? container.width : window.innerWidth;
|
|
157
|
+
const containerHeight = isConstrained ? container.height : window.innerHeight;
|
|
158
|
+
containerSizeRef.current = { width: containerWidth, height: containerHeight };
|
|
159
|
+
let newX = percentToPixels(state.position.x, containerWidth);
|
|
160
|
+
let newY = percentToPixels(state.position.y, containerHeight);
|
|
161
|
+
newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
|
|
162
|
+
newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
|
|
163
|
+
setPixelPosition({ x: newX, y: newY });
|
|
164
|
+
};
|
|
165
|
+
updatePixelPosition();
|
|
166
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
167
|
+
updatePixelPosition();
|
|
168
|
+
});
|
|
169
|
+
if (containerRef.current) {
|
|
170
|
+
resizeObserver.observe(containerRef.current);
|
|
171
|
+
}
|
|
172
|
+
if (!isConstrained) {
|
|
173
|
+
window.addEventListener('resize', updatePixelPosition);
|
|
174
|
+
}
|
|
175
|
+
return () => {
|
|
176
|
+
resizeObserver.disconnect();
|
|
177
|
+
if (!isConstrained) {
|
|
178
|
+
window.removeEventListener('resize', updatePixelPosition);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}, [state.position, isConstrained]);
|
|
54
182
|
const flattenMenuItems = useCallback((items, parentPath = '') => {
|
|
55
183
|
const result = [];
|
|
56
184
|
items.forEach((item, index) => {
|
|
57
|
-
const itemId = `${parentPath}${item.name}-${index}`;
|
|
58
|
-
// Only add items that have onClick (final actions, not submenu parents)
|
|
185
|
+
const itemId = item.id || `${parentPath}${item.name}-${index}`;
|
|
59
186
|
if (item.onClick && !item.submenu) {
|
|
187
|
+
const isPinned = state.items.some(i => i.id === itemId);
|
|
60
188
|
result.push({
|
|
61
189
|
id: itemId,
|
|
62
190
|
name: item.name,
|
|
63
|
-
icon: item.icon || _jsx(
|
|
191
|
+
icon: item.icon || _jsx(IconPin, {}),
|
|
64
192
|
onClick: item.onClick,
|
|
65
193
|
disabled: item.disabled,
|
|
66
|
-
isPinned:
|
|
67
|
-
originalMenuItem: item,
|
|
194
|
+
isPinned: isPinned,
|
|
68
195
|
});
|
|
69
196
|
}
|
|
70
197
|
// Recursively process submenus
|
|
@@ -73,56 +200,82 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
73
200
|
}
|
|
74
201
|
});
|
|
75
202
|
return result;
|
|
76
|
-
}, [
|
|
203
|
+
}, [state.items]);
|
|
77
204
|
// Restore items on mount from savedItemIds
|
|
78
205
|
useEffect(() => {
|
|
79
|
-
if (contextMenuItems.length > 0) {
|
|
206
|
+
if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0 || defaultItems.length > 0) {
|
|
80
207
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
208
|
+
let itemsToSet = [];
|
|
209
|
+
// If disbaleConfigMode is true and defaultItems provided, use only defaultItems
|
|
210
|
+
if (disbaleConfigMode && defaultItems.length > 0) {
|
|
211
|
+
itemsToSet = defaultItems;
|
|
212
|
+
}
|
|
213
|
+
else if (!disbaleConfigMode && initialConfig.savedItemIds.length > 0) {
|
|
214
|
+
// Restore items in the saved order from localStorage (only if config mode is enabled)
|
|
215
|
+
const restoredItems = initialConfig.savedItemIds
|
|
216
|
+
.map((id) => {
|
|
217
|
+
if (id.startsWith('separator-')) {
|
|
218
|
+
return {
|
|
219
|
+
id,
|
|
220
|
+
name: 'Separator',
|
|
221
|
+
icon: _jsx(Separator, {}),
|
|
222
|
+
onClick: () => { },
|
|
223
|
+
isSeparator: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return flatItems.find(item => item.id === id);
|
|
227
|
+
})
|
|
228
|
+
.filter((item) => item !== undefined);
|
|
229
|
+
itemsToSet = restoredItems;
|
|
230
|
+
}
|
|
231
|
+
else if (contextMenuDefaultPinnedIds.length > 0) {
|
|
232
|
+
// First time: Use contextMenuDefaultPinnedIds from props to find items by ID
|
|
233
|
+
const defaultPinnedItems = contextMenuDefaultPinnedIds
|
|
234
|
+
.map((id) => flatItems.find(item => item.id === id))
|
|
235
|
+
.filter((item) => item !== undefined);
|
|
236
|
+
itemsToSet = defaultPinnedItems;
|
|
237
|
+
}
|
|
238
|
+
else if (defaultItems.length > 0) {
|
|
239
|
+
// Use defaultItems as fallback
|
|
240
|
+
itemsToSet = defaultItems;
|
|
241
|
+
}
|
|
242
|
+
if (itemsToSet.length > 0) {
|
|
243
|
+
setState(s => ({ ...s, items: itemsToSet }));
|
|
87
244
|
}
|
|
88
245
|
}
|
|
89
|
-
}, []); //
|
|
246
|
+
}, disbaleConfigMode ? [defaultItems] : []); // Update when defaultItems change if config mode is disabled
|
|
90
247
|
const togglePin = useCallback((item) => {
|
|
91
248
|
setState(s => {
|
|
92
|
-
const isInFloatingBar = s.items.some(i => i.id === item.id
|
|
249
|
+
const isInFloatingBar = s.items.some(i => i.id === item.id);
|
|
93
250
|
if (isInFloatingBar) {
|
|
94
251
|
// Remove from floating bar
|
|
95
|
-
const newItems = s.items.filter(i => i.id !== item.id
|
|
252
|
+
const newItems = s.items.filter(i => i.id !== item.id);
|
|
96
253
|
return { ...s, items: newItems };
|
|
97
254
|
}
|
|
98
255
|
else {
|
|
99
256
|
// Add to floating bar
|
|
100
257
|
if (s.items.length >= maxItems) {
|
|
101
|
-
|
|
102
|
-
|
|
258
|
+
ShowAlert({
|
|
259
|
+
mode: 'warning',
|
|
260
|
+
title: 'Limite Massimo Raggiunto',
|
|
261
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
262
|
+
duration: 4000,
|
|
263
|
+
});
|
|
103
264
|
return s;
|
|
104
265
|
}
|
|
105
266
|
return { ...s, items: [...s.items, item] };
|
|
106
267
|
}
|
|
107
268
|
});
|
|
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
269
|
}, [maxItems]);
|
|
120
270
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
121
|
-
const getCurrentItemState = useCallback((
|
|
271
|
+
const getCurrentItemState = useCallback((itemId) => {
|
|
122
272
|
const findInItems = (items) => {
|
|
123
|
-
for (
|
|
124
|
-
|
|
273
|
+
for (let i = 0; i < items.length; i++) {
|
|
274
|
+
const item = items[i];
|
|
275
|
+
// Match by ID if the item has one
|
|
276
|
+
if (item.id === itemId)
|
|
125
277
|
return item;
|
|
278
|
+
// Check in submenu
|
|
126
279
|
if (item.submenu) {
|
|
127
280
|
const found = findInItems(item.submenu);
|
|
128
281
|
if (found)
|
|
@@ -136,34 +289,96 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
136
289
|
disabled: foundItem?.disabled,
|
|
137
290
|
onClick: foundItem?.onClick
|
|
138
291
|
};
|
|
139
|
-
}, [contextMenuItems]);
|
|
140
|
-
|
|
292
|
+
}, [contextMenuItems]);
|
|
293
|
+
// Remove trailing separators from items array
|
|
294
|
+
const removeTrailingSeparators = useCallback((items) => {
|
|
295
|
+
const result = [...items];
|
|
296
|
+
while (result.length > 0 && result.at(-1)?.isSeparator) {
|
|
297
|
+
result.pop();
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}, []);
|
|
301
|
+
// Create a new separator item
|
|
302
|
+
const createSeparator = useCallback(() => {
|
|
303
|
+
const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
304
|
+
return {
|
|
305
|
+
id: separatorId,
|
|
306
|
+
name: 'Separator',
|
|
307
|
+
icon: _jsx(Separator, {}),
|
|
308
|
+
onClick: () => { },
|
|
309
|
+
isSeparator: true,
|
|
310
|
+
};
|
|
311
|
+
}, []);
|
|
312
|
+
// Add separator to items
|
|
313
|
+
const addSeparator = useCallback(() => {
|
|
314
|
+
if (state.items.length >= maxItems) {
|
|
315
|
+
ShowAlert({
|
|
316
|
+
mode: 'warning',
|
|
317
|
+
title: 'Limite Massimo Raggiunto',
|
|
318
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
319
|
+
duration: 4000,
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const separator = createSeparator();
|
|
324
|
+
setState(s => ({ ...s, items: [...s.items, separator] }));
|
|
325
|
+
}, [state.items.length, maxItems, createSeparator]);
|
|
326
|
+
const getPinContextMenuItems = useCallback(() => {
|
|
141
327
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
142
|
-
// Calculate current pinned items directly from state.items (not refs)
|
|
143
328
|
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
144
|
-
const
|
|
145
|
-
const enhanceItems = (items) => {
|
|
329
|
+
const createPinItems = (items) => {
|
|
146
330
|
return items.map(item => {
|
|
147
|
-
const flatItem = flatItems.find(fi => fi.
|
|
148
|
-
const itemId = flatItem?.id || '';
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const enhanced = {
|
|
331
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
332
|
+
const itemId = flatItem?.id || item.id || '';
|
|
333
|
+
const isAlreadyPinned = currentItemIds.has(itemId);
|
|
334
|
+
const pinItem = {
|
|
152
335
|
...item,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (flatItem) {
|
|
336
|
+
onClick: item.onClick && !item.submenu ? () => {
|
|
337
|
+
if (flatItem && !isAlreadyPinned) {
|
|
156
338
|
togglePin(flatItem);
|
|
157
339
|
}
|
|
158
340
|
} : undefined,
|
|
341
|
+
disabled: isAlreadyPinned,
|
|
342
|
+
};
|
|
343
|
+
if (item.submenu) {
|
|
344
|
+
pinItem.submenu = createPinItems(item.submenu);
|
|
345
|
+
}
|
|
346
|
+
return pinItem;
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
const pinItems = createPinItems(contextMenuItems);
|
|
350
|
+
// Add separator option at the end
|
|
351
|
+
pinItems.push({
|
|
352
|
+
id: 'add-separator',
|
|
353
|
+
name: SDKUI_Localizator.Add + ' separatore',
|
|
354
|
+
icon: _jsx(IconSeparator, {}),
|
|
355
|
+
onClick: addSeparator,
|
|
356
|
+
beginGroup: true
|
|
357
|
+
});
|
|
358
|
+
return pinItems;
|
|
359
|
+
}, [contextMenuItems, flattenMenuItems, togglePin, state.items, addSeparator]);
|
|
360
|
+
const getContextMenuItemsWithPinIcons = useCallback(() => {
|
|
361
|
+
const flatItems = flattenMenuItems(contextMenuItems);
|
|
362
|
+
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
363
|
+
const addPinIcons = (items) => {
|
|
364
|
+
return items.map(item => {
|
|
365
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
366
|
+
const itemId = flatItem?.id || item.id || '';
|
|
367
|
+
const isPinned = currentItemIds.has(itemId);
|
|
368
|
+
const itemWithPin = {
|
|
369
|
+
...item,
|
|
370
|
+
rightIcon: flatItem ? _jsx(IconPin, { color: isPinned ? 'red' : 'black' }) : undefined,
|
|
371
|
+
onRightIconClick: flatItem ? () => {
|
|
372
|
+
togglePin(flatItem);
|
|
373
|
+
} : undefined,
|
|
159
374
|
};
|
|
160
375
|
if (item.submenu) {
|
|
161
|
-
|
|
376
|
+
itemWithPin.submenu = addPinIcons(item.submenu);
|
|
162
377
|
}
|
|
163
|
-
return
|
|
378
|
+
return itemWithPin;
|
|
164
379
|
});
|
|
165
380
|
};
|
|
166
|
-
return
|
|
381
|
+
return addPinIcons(contextMenuItems);
|
|
167
382
|
}, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
|
|
168
383
|
const handleMouseDown = (e) => {
|
|
169
384
|
if (state.isConfigMode)
|
|
@@ -174,20 +389,25 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
174
389
|
if (isConstrained) {
|
|
175
390
|
// For absolute positioning, offset is relative to container
|
|
176
391
|
dragOffset.current = {
|
|
177
|
-
x: e.clientX - containerRect.left -
|
|
178
|
-
y: e.clientY - containerRect.top -
|
|
392
|
+
x: e.clientX - containerRect.left - pixelPosition.x,
|
|
393
|
+
y: e.clientY - containerRect.top - pixelPosition.y,
|
|
179
394
|
};
|
|
180
395
|
}
|
|
181
396
|
else {
|
|
182
397
|
// For fixed positioning, offset is relative to viewport
|
|
183
398
|
dragOffset.current = {
|
|
184
|
-
x: e.clientX -
|
|
185
|
-
y: e.clientY -
|
|
399
|
+
x: e.clientX - pixelPosition.x,
|
|
400
|
+
y: e.clientY - pixelPosition.y,
|
|
186
401
|
};
|
|
187
402
|
}
|
|
188
403
|
}
|
|
189
404
|
setState(s => ({ ...s, isDragging: true }));
|
|
190
405
|
};
|
|
406
|
+
const handleGripDoubleClick = () => {
|
|
407
|
+
if (state.isConfigMode)
|
|
408
|
+
return;
|
|
409
|
+
toggleOrientation();
|
|
410
|
+
};
|
|
191
411
|
const handleMouseMove = useCallback((e) => {
|
|
192
412
|
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
193
413
|
return;
|
|
@@ -210,50 +430,267 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
210
430
|
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
211
431
|
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
212
432
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
position: { x: newX, y: newY },
|
|
216
|
-
}));
|
|
433
|
+
// Update pixel position directly during drag
|
|
434
|
+
setPixelPosition({ x: newX, y: newY });
|
|
217
435
|
}, [state.isDragging, containerRef, isConstrained]);
|
|
218
436
|
const handleMouseUp = useCallback(() => {
|
|
219
|
-
|
|
220
|
-
|
|
437
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
438
|
+
// Convert final pixel position to percentage before updating state
|
|
439
|
+
const percentagePosition = {
|
|
440
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
441
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
442
|
+
};
|
|
443
|
+
setState(s => ({
|
|
444
|
+
...s,
|
|
445
|
+
isDragging: false,
|
|
446
|
+
position: percentagePosition,
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
451
|
+
}
|
|
452
|
+
}, [state.isDragging, pixelPosition]);
|
|
453
|
+
// Touch event handlers for tablet support
|
|
454
|
+
const handleTouchStart = (e) => {
|
|
455
|
+
if (state.isConfigMode)
|
|
456
|
+
return;
|
|
457
|
+
const touch = e.touches[0];
|
|
458
|
+
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
459
|
+
if (containerRect) {
|
|
460
|
+
if (isConstrained) {
|
|
461
|
+
dragOffset.current = {
|
|
462
|
+
x: touch.clientX - containerRect.left - pixelPosition.x,
|
|
463
|
+
y: touch.clientY - containerRect.top - pixelPosition.y,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
dragOffset.current = {
|
|
468
|
+
x: touch.clientX - pixelPosition.x,
|
|
469
|
+
y: touch.clientY - pixelPosition.y,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
setState(s => ({ ...s, isDragging: true }));
|
|
474
|
+
};
|
|
475
|
+
const handleTouchMove = useCallback((e) => {
|
|
476
|
+
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
477
|
+
return;
|
|
478
|
+
const touch = e.touches[0];
|
|
479
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
480
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
481
|
+
let newX, newY;
|
|
482
|
+
if (isConstrained) {
|
|
483
|
+
newX = touch.clientX - container.left - dragOffset.current.x;
|
|
484
|
+
newY = touch.clientY - container.top - dragOffset.current.y;
|
|
485
|
+
newX = Math.max(0, Math.min(newX, container.width - floating.width));
|
|
486
|
+
newY = Math.max(0, Math.min(newY, container.height - floating.height));
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
newX = touch.clientX - dragOffset.current.x;
|
|
490
|
+
newY = touch.clientY - dragOffset.current.y;
|
|
491
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
492
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
493
|
+
}
|
|
494
|
+
setPixelPosition({ x: newX, y: newY });
|
|
495
|
+
}, [state.isDragging, containerRef, isConstrained]);
|
|
496
|
+
const handleTouchEnd = useCallback(() => {
|
|
497
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
498
|
+
const percentagePosition = {
|
|
499
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
500
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
501
|
+
};
|
|
502
|
+
setState(s => ({
|
|
503
|
+
...s,
|
|
504
|
+
isDragging: false,
|
|
505
|
+
position: percentagePosition,
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
510
|
+
}
|
|
511
|
+
}, [state.isDragging, pixelPosition]);
|
|
221
512
|
useEffect(() => {
|
|
222
513
|
if (state.isDragging) {
|
|
223
514
|
document.addEventListener('mousemove', handleMouseMove);
|
|
224
515
|
document.addEventListener('mouseup', handleMouseUp);
|
|
516
|
+
document.addEventListener('touchmove', handleTouchMove);
|
|
517
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
225
518
|
return () => {
|
|
226
519
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
227
520
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
521
|
+
document.removeEventListener('touchmove', handleTouchMove);
|
|
522
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
228
523
|
};
|
|
229
524
|
}
|
|
230
525
|
return undefined;
|
|
231
|
-
}, [state.isDragging, handleMouseMove, handleMouseUp]);
|
|
232
|
-
// Save to
|
|
526
|
+
}, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
|
|
527
|
+
// Save to SDKUI_Globals.userSettings only when NOT in config mode (when applying changes)
|
|
233
528
|
useEffect(() => {
|
|
529
|
+
if (state.isConfigMode)
|
|
530
|
+
return; // Don't save during edit mode
|
|
531
|
+
if (disbaleConfigMode)
|
|
532
|
+
return; // Don't save if config mode is disabled
|
|
234
533
|
try {
|
|
235
|
-
|
|
534
|
+
// Replace the entire object to trigger the Proxy
|
|
535
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
236
536
|
orientation: state.orientation,
|
|
237
|
-
|
|
238
|
-
|
|
537
|
+
itemIds: state.items.map(item => item.id),
|
|
538
|
+
position: state.position,
|
|
539
|
+
positionFormat: 'percentage',
|
|
239
540
|
};
|
|
240
|
-
localStorage.setItem(storageKey, JSON.stringify(config));
|
|
241
541
|
}
|
|
242
542
|
catch (error) {
|
|
243
543
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
244
544
|
}
|
|
245
|
-
}, [state.orientation, state.items,
|
|
545
|
+
}, [state.orientation, state.items, state.position, state.isConfigMode, disbaleConfigMode]);
|
|
246
546
|
const toggleConfigMode = () => {
|
|
247
|
-
setState(s =>
|
|
547
|
+
setState(s => {
|
|
548
|
+
if (!s.isConfigMode) {
|
|
549
|
+
stateSnapshot.current = {
|
|
550
|
+
items: [...s.items],
|
|
551
|
+
orientation: s.orientation,
|
|
552
|
+
position: { ...s.position },
|
|
553
|
+
};
|
|
554
|
+
return { ...s, isConfigMode: true };
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
|
|
558
|
+
stateSnapshot.current = null;
|
|
559
|
+
const cleanedItems = removeTrailingSeparators(s.items);
|
|
560
|
+
return { ...s, isConfigMode: false, items: cleanedItems };
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
};
|
|
564
|
+
// Auto-reposition when entering edit mode to ensure Apply/Undo buttons are visible
|
|
565
|
+
useEffect(() => {
|
|
566
|
+
if (!state.isConfigMode || !floatingRef.current)
|
|
567
|
+
return;
|
|
568
|
+
// Use double requestAnimationFrame to ensure the DOM has fully updated with new buttons
|
|
569
|
+
requestAnimationFrame(() => {
|
|
570
|
+
requestAnimationFrame(() => {
|
|
571
|
+
if (!floatingRef.current)
|
|
572
|
+
return;
|
|
573
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
574
|
+
const containerWidth = isConstrained && containerRef.current
|
|
575
|
+
? containerRef.current.getBoundingClientRect().width
|
|
576
|
+
: window.innerWidth;
|
|
577
|
+
const containerHeight = isConstrained && containerRef.current
|
|
578
|
+
? containerRef.current.getBoundingClientRect().height
|
|
579
|
+
: window.innerHeight;
|
|
580
|
+
// Use current pixel position
|
|
581
|
+
let newPixelX = pixelPosition.x;
|
|
582
|
+
let newPixelY = pixelPosition.y;
|
|
583
|
+
let needsUpdate = false;
|
|
584
|
+
// Check horizontal overflow
|
|
585
|
+
if (newPixelX + floating.width > containerWidth) {
|
|
586
|
+
newPixelX = Math.max(0, containerWidth - floating.width);
|
|
587
|
+
needsUpdate = true;
|
|
588
|
+
}
|
|
589
|
+
// Check vertical overflow
|
|
590
|
+
if (newPixelY + floating.height > containerHeight) {
|
|
591
|
+
newPixelY = Math.max(0, containerHeight - floating.height);
|
|
592
|
+
needsUpdate = true;
|
|
593
|
+
}
|
|
594
|
+
if (needsUpdate) {
|
|
595
|
+
// Update pixel position immediately
|
|
596
|
+
setPixelPosition({ x: newPixelX, y: newPixelY });
|
|
597
|
+
// Convert to percentage for state
|
|
598
|
+
const newPercentagePosition = {
|
|
599
|
+
x: pixelsToPercent(newPixelX, containerWidth),
|
|
600
|
+
y: pixelsToPercent(newPixelY, containerHeight),
|
|
601
|
+
};
|
|
602
|
+
setState(s => ({
|
|
603
|
+
...s,
|
|
604
|
+
position: newPercentagePosition,
|
|
605
|
+
}));
|
|
606
|
+
// Update snapshot position to the corrected position so Undo restores to visible position
|
|
607
|
+
if (stateSnapshot.current) {
|
|
608
|
+
stateSnapshot.current.position = newPercentagePosition;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
}, [state.isConfigMode, state.orientation, isConstrained, state.items, pixelPosition.x, pixelPosition.y]);
|
|
614
|
+
const handleUndo = () => {
|
|
615
|
+
if (stateSnapshot.current) {
|
|
616
|
+
setState(s => ({
|
|
617
|
+
...s,
|
|
618
|
+
items: [...stateSnapshot.current.items],
|
|
619
|
+
orientation: stateSnapshot.current.orientation,
|
|
620
|
+
position: { ...stateSnapshot.current.position },
|
|
621
|
+
isConfigMode: true,
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
// Check if current state has changed from snapshot
|
|
626
|
+
const hasChanges = () => {
|
|
627
|
+
if (!stateSnapshot.current)
|
|
628
|
+
return false;
|
|
629
|
+
// Check if items have changed (different IDs or different order)
|
|
630
|
+
const currentIds = state.items.map(i => i.id).join(',');
|
|
631
|
+
const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
|
|
632
|
+
return currentIds !== snapshotIds;
|
|
633
|
+
};
|
|
634
|
+
const handleClose = () => {
|
|
635
|
+
// If all items removed, exit without asking and restore last items
|
|
636
|
+
if (state.items.length === 0 && stateSnapshot.current) {
|
|
637
|
+
setState(s => ({
|
|
638
|
+
...s,
|
|
639
|
+
items: [...stateSnapshot.current.items],
|
|
640
|
+
orientation: stateSnapshot.current.orientation,
|
|
641
|
+
position: { ...stateSnapshot.current.position },
|
|
642
|
+
isConfigMode: false,
|
|
643
|
+
}));
|
|
644
|
+
stateSnapshot.current = null;
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
// If no changes, simply exit config mode
|
|
648
|
+
if (!hasChanges()) {
|
|
649
|
+
stateSnapshot.current = null;
|
|
650
|
+
const cleanedItems = removeTrailingSeparators(state.items);
|
|
651
|
+
setState(s => ({ ...s, isConfigMode: false, items: cleanedItems }));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// If there are changes, ask for confirmation
|
|
655
|
+
TMMessageBoxManager.show({
|
|
656
|
+
message: 'Perderai le tue modifiche, sei sicuro?',
|
|
657
|
+
buttons: [ButtonNames.YES, ButtonNames.NO],
|
|
658
|
+
onButtonClick: (buttonName) => {
|
|
659
|
+
if (buttonName === ButtonNames.YES && stateSnapshot.current) {
|
|
660
|
+
// Restore snapshot and exit config mode
|
|
661
|
+
setState(s => ({
|
|
662
|
+
...s,
|
|
663
|
+
items: [...stateSnapshot.current.items],
|
|
664
|
+
orientation: stateSnapshot.current.orientation,
|
|
665
|
+
position: { ...stateSnapshot.current.position },
|
|
666
|
+
isConfigMode: false,
|
|
667
|
+
}));
|
|
668
|
+
stateSnapshot.current = null;
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
});
|
|
248
672
|
};
|
|
249
673
|
const toggleOrientation = () => {
|
|
250
674
|
const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
|
251
|
-
|
|
675
|
+
if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
|
|
676
|
+
if (floatingRef.current) {
|
|
677
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
678
|
+
const screenHeight = window.innerHeight;
|
|
679
|
+
if (floating.width > screenHeight - 70) {
|
|
680
|
+
ShowAlert({
|
|
681
|
+
mode: 'warning',
|
|
682
|
+
title: 'Troppi elementi',
|
|
683
|
+
message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
|
|
684
|
+
duration: 4000,
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
252
690
|
setState(s => ({
|
|
253
691
|
...s,
|
|
254
692
|
orientation: newOrientation,
|
|
255
693
|
}));
|
|
256
|
-
// Then, after DOM updates, adjust position to stay in bounds
|
|
257
694
|
requestAnimationFrame(() => {
|
|
258
695
|
requestAnimationFrame(() => {
|
|
259
696
|
if (containerRef.current && floatingRef.current) {
|
|
@@ -286,12 +723,6 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
286
723
|
...s,
|
|
287
724
|
items: s.items.filter(item => item.id !== itemId),
|
|
288
725
|
}));
|
|
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
726
|
};
|
|
296
727
|
// Drag and drop for reordering
|
|
297
728
|
const handleDragStart = (e, index) => {
|
|
@@ -347,24 +778,44 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
347
778
|
setState(s => ({ ...s, draggedItemIndex: null }));
|
|
348
779
|
setDragOverIndex(null);
|
|
349
780
|
};
|
|
350
|
-
return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x":
|
|
781
|
+
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, "$bgColor": bgColor, onContextMenu: (e) => e.preventDefault(), children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
|
|
782
|
+
...(!disbaleConfigMode ? [{
|
|
783
|
+
name: SDKUI_Localizator.Configure,
|
|
784
|
+
onClick: () => {
|
|
785
|
+
if (!state.isConfigMode) {
|
|
786
|
+
toggleConfigMode();
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
}] : []),
|
|
790
|
+
{
|
|
791
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
792
|
+
onClick: toggleOrientation,
|
|
793
|
+
},
|
|
794
|
+
], 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) => {
|
|
795
|
+
// Handle separator items
|
|
796
|
+
if (item.isSeparator) {
|
|
797
|
+
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));
|
|
798
|
+
}
|
|
351
799
|
// Get current state (disabled and onClick) from contextMenuItems
|
|
352
|
-
const currentState = getCurrentItemState(item.
|
|
353
|
-
|
|
800
|
+
const currentState = getCurrentItemState(item.id);
|
|
801
|
+
// Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
|
|
802
|
+
const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
|
|
803
|
+
? currentState.disabled === true
|
|
804
|
+
: item.disabled === true;
|
|
354
805
|
const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
|
|
355
|
-
return (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
806
|
+
return (_jsx(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: item.staticItem ? (
|
|
807
|
+
// Visualizza l'elemento statico personalizzato
|
|
808
|
+
_jsxs(_Fragment, { children: [item.staticItem, state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] })) : (
|
|
809
|
+
// Visualizza il pulsante standard
|
|
810
|
+
_jsxs(_Fragment, { children: [_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
|
|
811
|
+
if (state.isConfigMode || isDisabled)
|
|
812
|
+
return;
|
|
813
|
+
if (currentOnClick) {
|
|
814
|
+
currentOnClick();
|
|
815
|
+
}
|
|
816
|
+
}, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] })) }, item.id));
|
|
817
|
+
}), !state.isConfigMode && !disbaleConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, 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
|
|
818
|
+
? 'Devi aggiungere almeno un item'
|
|
819
|
+
: 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
820
|
};
|
|
370
821
|
export default TMFloatingMenuBar;
|