@topconsultnpm/sdkui-react 6.20.0-dev1.12 → 6.20.0-dev1.120
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 +322 -30
- 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 +159 -37
- package/lib/components/NewComponents/ContextMenu/types.d.ts +22 -3
- 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 +803 -109
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +25 -5
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +215 -59
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +12 -2
- package/lib/components/base/TMAccordionNew.js +35 -14
- package/lib/components/base/TMButton.js +6 -0
- package/lib/components/base/TMClosableList.js +4 -0
- 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 +21 -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 +3 -1
- package/lib/components/base/TMPopUp.js +6 -0
- package/lib/components/base/TMToolbarCard.js +2 -0
- package/lib/components/base/TMTreeView.d.ts +2 -1
- package/lib/components/base/TMTreeView.js +33 -26
- package/lib/components/choosers/TMDataListItemChooser.d.ts +2 -0
- package/lib/components/choosers/TMDataListItemChooser.js +8 -2
- package/lib/components/choosers/TMDcmtTypeChooser.d.ts +1 -0
- package/lib/components/choosers/TMDcmtTypeChooser.js +11 -3
- package/lib/components/choosers/TMDistinctValues.js +2 -2
- package/lib/components/choosers/TMDynDataListItemChooser.d.ts +2 -0
- package/lib/components/choosers/TMDynDataListItemChooser.js +8 -2
- package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.d.ts +2 -0
- package/lib/components/choosers/TMMetadataChooser.js +19 -4
- package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMUserChooser.d.ts +2 -5
- package/lib/components/choosers/TMUserChooser.js +33 -47
- package/lib/components/editors/TMCheckBox.js +2 -0
- package/lib/components/editors/TMDateBox.js +18 -9
- package/lib/components/editors/TMEditorStyled.js +7 -0
- package/lib/components/editors/TMLocalizedTextBox.d.ts +3 -1
- package/lib/components/editors/TMLocalizedTextBox.js +16 -14
- package/lib/components/editors/TMMetadataEditor.d.ts +1 -0
- package/lib/components/editors/TMMetadataEditor.js +4 -4
- package/lib/components/editors/TMMetadataTextBox.d.ts +9 -0
- package/lib/components/editors/TMMetadataTextBox.js +92 -0
- package/lib/components/editors/TMMetadataValues.d.ts +1 -0
- package/lib/components/editors/TMMetadataValues.js +25 -7
- package/lib/components/editors/TMRadioButton.js +2 -0
- package/lib/components/editors/TMTextArea.js +18 -30
- package/lib/components/editors/TMTextBox.d.ts +1 -1
- package/lib/components/editors/TMTextBox.js +29 -4
- 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 +462 -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/TMDcmtBlog.js +1 -1
- package/lib/components/features/documents/TMDcmtForm.d.ts +14 -2
- package/lib/components/features/documents/TMDcmtForm.js +459 -208
- package/lib/components/features/documents/TMDcmtPreview.js +42 -155
- 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/TMSearchQueryEditor.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryEditor.js +10 -10
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryPanel.js +40 -25
- package/lib/components/features/search/TMSearchResult.d.ts +3 -0
- package/lib/components/features/search/TMSearchResult.js +210 -250
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
- package/lib/components/features/search/TMSearchResultsMenuItems.js +227 -171
- 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 +75 -25
- 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 +57 -37
- 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 +14 -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 +35 -7
- 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/grids/TMValidationItemsList.js +6 -0
- package/lib/components/index.d.ts +6 -3
- package/lib/components/index.js +6 -3
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +13 -5
- package/lib/components/query/TMQueryEditor.d.ts +3 -1
- package/lib/components/query/TMQueryEditor.js +102 -100
- package/lib/components/settings/SettingsAppearance.d.ts +2 -1
- package/lib/components/settings/SettingsAppearance.js +99 -30
- package/lib/components/sidebar/TMHeader.js +11 -7
- package/lib/components/sidebar/TMSidebar.d.ts +0 -1
- package/lib/components/sidebar/TMSidebar.js +16 -44
- package/lib/components/sidebar/TMSidebarItem.js +36 -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 +20 -1
- package/lib/helper/SDKUI_Localizator.js +197 -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/TMToppyMessage.js +4 -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 +30 -2
- package/lib/helper/helpers.js +131 -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/useFloatingBarPinnedItems.d.ts +11 -0
- package/lib/hooks/useFloatingBarPinnedItems.js +54 -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,70 +1,204 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } 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, IconDelete, IconMenuVertical, IconPin, IconRotate, IconSave, IconSeparator, IconSettings, 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, hasContextMenu = true, pinnedItemIds: externalPinnedItemIds, onPinChange, }) => {
|
|
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,
|
|
134
|
+
verticalDirection: 'down',
|
|
37
135
|
items: [],
|
|
38
136
|
draggedItemIndex: null,
|
|
39
137
|
});
|
|
40
138
|
const floatingRef = useRef(null);
|
|
41
139
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
42
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
43
140
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
44
|
-
const [
|
|
45
|
-
|
|
141
|
+
const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
|
|
142
|
+
const [isOrientationChanging, setIsOrientationChanging] = useState(false); // Hide bar during orientation transition
|
|
143
|
+
const containerSizeRef = useRef({ width: 0, height: 0 });
|
|
144
|
+
const stateSnapshot = useRef(null);
|
|
145
|
+
const positionBeforeOrientationChange = useRef(null);
|
|
46
146
|
const floatingBarItemIds = useRef(new Set());
|
|
47
147
|
const floatingBarItemNames = useRef(new Set());
|
|
48
|
-
|
|
148
|
+
const isSyncingFromExternal = useRef(false);
|
|
149
|
+
const isExitingConfigMode = useRef(false);
|
|
150
|
+
const isLocalChange = useRef(false);
|
|
151
|
+
const dragStartPosition = useRef(null);
|
|
49
152
|
useEffect(() => {
|
|
50
153
|
floatingBarItemIds.current = new Set(state.items.map(i => i.id));
|
|
51
154
|
floatingBarItemNames.current = new Set(state.items.map(i => i.name));
|
|
52
155
|
}, [state.items]);
|
|
53
|
-
//
|
|
156
|
+
// Calculate pixel position from percentage when container size or position changes
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const updatePixelPosition = () => {
|
|
159
|
+
if (!containerRef.current || !floatingRef.current)
|
|
160
|
+
return;
|
|
161
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
162
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
163
|
+
const containerWidth = isConstrained ? container.width : window.innerWidth;
|
|
164
|
+
const containerHeight = isConstrained ? container.height : window.innerHeight;
|
|
165
|
+
containerSizeRef.current = { width: containerWidth, height: containerHeight };
|
|
166
|
+
let newX = percentToPixels(state.position.x, containerWidth);
|
|
167
|
+
let newY = percentToPixels(state.position.y, containerHeight);
|
|
168
|
+
newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
|
|
169
|
+
newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
|
|
170
|
+
setPixelPosition({ x: newX, y: newY });
|
|
171
|
+
};
|
|
172
|
+
updatePixelPosition();
|
|
173
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
174
|
+
updatePixelPosition();
|
|
175
|
+
});
|
|
176
|
+
if (containerRef.current) {
|
|
177
|
+
resizeObserver.observe(containerRef.current);
|
|
178
|
+
}
|
|
179
|
+
if (!isConstrained) {
|
|
180
|
+
window.addEventListener('resize', updatePixelPosition);
|
|
181
|
+
}
|
|
182
|
+
return () => {
|
|
183
|
+
resizeObserver.disconnect();
|
|
184
|
+
if (!isConstrained) {
|
|
185
|
+
window.removeEventListener('resize', updatePixelPosition);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}, [state.position, isConstrained]);
|
|
54
189
|
const flattenMenuItems = useCallback((items, parentPath = '') => {
|
|
55
190
|
const result = [];
|
|
56
191
|
items.forEach((item, index) => {
|
|
57
|
-
const itemId = `${parentPath}${item.name}-${index}`;
|
|
58
|
-
// Only add items that have onClick (final actions, not submenu parents)
|
|
192
|
+
const itemId = item.id || `${parentPath}${item.name}-${index}`;
|
|
59
193
|
if (item.onClick && !item.submenu) {
|
|
194
|
+
const isPinned = state.items.some(i => i.id === itemId);
|
|
60
195
|
result.push({
|
|
61
196
|
id: itemId,
|
|
62
197
|
name: item.name,
|
|
63
|
-
icon: item.icon || _jsx(
|
|
198
|
+
icon: item.icon || _jsx(IconPin, {}),
|
|
64
199
|
onClick: item.onClick,
|
|
65
200
|
disabled: item.disabled,
|
|
66
|
-
isPinned:
|
|
67
|
-
originalMenuItem: item,
|
|
201
|
+
isPinned: isPinned,
|
|
68
202
|
});
|
|
69
203
|
}
|
|
70
204
|
// Recursively process submenus
|
|
@@ -73,56 +207,120 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
73
207
|
}
|
|
74
208
|
});
|
|
75
209
|
return result;
|
|
76
|
-
}, [
|
|
210
|
+
}, [state.items]);
|
|
77
211
|
// Restore items on mount from savedItemIds
|
|
78
212
|
useEffect(() => {
|
|
79
|
-
if (contextMenuItems.length > 0) {
|
|
213
|
+
if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0 || defaultItems.length > 0) {
|
|
80
214
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
215
|
+
let itemsToSet = [];
|
|
216
|
+
// If disbaleConfigMode is true and defaultItems provided, use only defaultItems
|
|
217
|
+
if (disbaleConfigMode && defaultItems.length > 0) {
|
|
218
|
+
itemsToSet = defaultItems;
|
|
219
|
+
}
|
|
220
|
+
else if (!disbaleConfigMode && initialConfig.savedItemIds.length > 0) {
|
|
221
|
+
// Restore items in the saved order from localStorage (only if config mode is enabled)
|
|
222
|
+
const restoredItems = initialConfig.savedItemIds
|
|
223
|
+
.map((id) => {
|
|
224
|
+
if (id.startsWith('separator-')) {
|
|
225
|
+
return {
|
|
226
|
+
id,
|
|
227
|
+
name: 'Separator',
|
|
228
|
+
icon: _jsx(Separator, {}),
|
|
229
|
+
onClick: () => { },
|
|
230
|
+
isSeparator: true,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return flatItems.find(item => item.id === id);
|
|
234
|
+
})
|
|
235
|
+
.filter((item) => item !== undefined);
|
|
236
|
+
itemsToSet = restoredItems;
|
|
237
|
+
}
|
|
238
|
+
else if (contextMenuDefaultPinnedIds.length > 0) {
|
|
239
|
+
// First time: Use contextMenuDefaultPinnedIds from props to find items by ID
|
|
240
|
+
const defaultPinnedItems = contextMenuDefaultPinnedIds
|
|
241
|
+
.map((id) => flatItems.find(item => item.id === id))
|
|
242
|
+
.filter((item) => item !== undefined);
|
|
243
|
+
itemsToSet = defaultPinnedItems;
|
|
244
|
+
}
|
|
245
|
+
else if (defaultItems.length > 0) {
|
|
246
|
+
// Use defaultItems as fallback
|
|
247
|
+
itemsToSet = defaultItems;
|
|
248
|
+
}
|
|
249
|
+
if (itemsToSet.length > 0) {
|
|
250
|
+
setState(s => ({ ...s, items: itemsToSet }));
|
|
87
251
|
}
|
|
88
252
|
}
|
|
89
|
-
}, []); //
|
|
253
|
+
}, disbaleConfigMode ? [defaultItems] : []); // Update when defaultItems change if config mode is disabled
|
|
254
|
+
// Sync with external pinnedItemIds when they change (from parent component)
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
// Skip sync if exiting config mode - let save effect update external state first
|
|
257
|
+
if (isExitingConfigMode.current) {
|
|
258
|
+
isExitingConfigMode.current = false;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Skip sync if a local change was just made (e.g. right-click remove/add separator)
|
|
262
|
+
if (isLocalChange.current) {
|
|
263
|
+
isLocalChange.current = false;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (externalPinnedItemIds === undefined || disbaleConfigMode || state.isConfigMode)
|
|
267
|
+
return;
|
|
268
|
+
const flatItems = flattenMenuItems(contextMenuItems);
|
|
269
|
+
// Build items from external pinned IDs
|
|
270
|
+
const itemsFromExternal = externalPinnedItemIds
|
|
271
|
+
.map((id) => {
|
|
272
|
+
if (id.startsWith('separator-')) {
|
|
273
|
+
return {
|
|
274
|
+
id,
|
|
275
|
+
name: 'Separator',
|
|
276
|
+
icon: _jsx(Separator, {}),
|
|
277
|
+
onClick: () => { },
|
|
278
|
+
isSeparator: true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return flatItems.find(item => item.id === id);
|
|
282
|
+
})
|
|
283
|
+
.filter((item) => item !== undefined);
|
|
284
|
+
// Only update if different
|
|
285
|
+
const currentIds = state.items.map(i => i.id).join(',');
|
|
286
|
+
const newIds = itemsFromExternal.map(i => i.id).join(',');
|
|
287
|
+
if (currentIds !== newIds) {
|
|
288
|
+
isSyncingFromExternal.current = true; // Mark that we're syncing from external
|
|
289
|
+
setState(s => ({ ...s, items: itemsFromExternal }));
|
|
290
|
+
}
|
|
291
|
+
}, [externalPinnedItemIds, contextMenuItems, disbaleConfigMode, state.isConfigMode, flattenMenuItems]);
|
|
90
292
|
const togglePin = useCallback((item) => {
|
|
91
293
|
setState(s => {
|
|
92
|
-
const isInFloatingBar = s.items.some(i => i.id === item.id
|
|
294
|
+
const isInFloatingBar = s.items.some(i => i.id === item.id);
|
|
93
295
|
if (isInFloatingBar) {
|
|
94
296
|
// Remove from floating bar
|
|
95
|
-
const newItems = s.items.filter(i => i.id !== item.id
|
|
297
|
+
const newItems = s.items.filter(i => i.id !== item.id);
|
|
96
298
|
return { ...s, items: newItems };
|
|
97
299
|
}
|
|
98
300
|
else {
|
|
99
301
|
// Add to floating bar
|
|
100
302
|
if (s.items.length >= maxItems) {
|
|
101
|
-
|
|
102
|
-
|
|
303
|
+
ShowAlert({
|
|
304
|
+
mode: 'warning',
|
|
305
|
+
title: 'Limite Massimo Raggiunto',
|
|
306
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
307
|
+
duration: 4000,
|
|
308
|
+
});
|
|
103
309
|
return s;
|
|
104
310
|
}
|
|
105
311
|
return { ...s, items: [...s.items, item] };
|
|
106
312
|
}
|
|
107
313
|
});
|
|
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
314
|
}, [maxItems]);
|
|
120
315
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
121
|
-
const getCurrentItemState = useCallback((
|
|
316
|
+
const getCurrentItemState = useCallback((itemId) => {
|
|
122
317
|
const findInItems = (items) => {
|
|
123
|
-
for (
|
|
124
|
-
|
|
318
|
+
for (let i = 0; i < items.length; i++) {
|
|
319
|
+
const item = items[i];
|
|
320
|
+
// Match by ID if the item has one
|
|
321
|
+
if (item.id === itemId)
|
|
125
322
|
return item;
|
|
323
|
+
// Check in submenu
|
|
126
324
|
if (item.submenu) {
|
|
127
325
|
const found = findInItems(item.submenu);
|
|
128
326
|
if (found)
|
|
@@ -136,34 +334,101 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
136
334
|
disabled: foundItem?.disabled,
|
|
137
335
|
onClick: foundItem?.onClick
|
|
138
336
|
};
|
|
139
|
-
}, [contextMenuItems]);
|
|
140
|
-
|
|
337
|
+
}, [contextMenuItems]);
|
|
338
|
+
// Remove trailing separators from items array
|
|
339
|
+
const removeTrailingSeparators = useCallback((items) => {
|
|
340
|
+
const result = [...items];
|
|
341
|
+
while (result.length > 0 && result.at(-1)?.isSeparator) {
|
|
342
|
+
result.pop();
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}, []);
|
|
346
|
+
// Create a new separator item
|
|
347
|
+
const createSeparator = useCallback(() => {
|
|
348
|
+
const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
349
|
+
return {
|
|
350
|
+
id: separatorId,
|
|
351
|
+
name: 'Separator',
|
|
352
|
+
icon: _jsx(Separator, {}),
|
|
353
|
+
onClick: () => { },
|
|
354
|
+
isSeparator: true,
|
|
355
|
+
};
|
|
356
|
+
}, []);
|
|
357
|
+
// Add separator to items
|
|
358
|
+
const addSeparator = useCallback(() => {
|
|
359
|
+
if (state.items.length >= maxItems) {
|
|
360
|
+
ShowAlert({
|
|
361
|
+
mode: 'warning',
|
|
362
|
+
title: 'Limite Massimo Raggiunto',
|
|
363
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
364
|
+
duration: 4000,
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const separator = createSeparator();
|
|
369
|
+
setState(s => ({ ...s, items: [...s.items, separator] }));
|
|
370
|
+
}, [state.items.length, maxItems, createSeparator]);
|
|
371
|
+
const getPinContextMenuItems = useCallback(() => {
|
|
141
372
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
142
|
-
// Calculate current pinned items directly from state.items (not refs)
|
|
143
373
|
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
144
|
-
const
|
|
145
|
-
const enhanceItems = (items) => {
|
|
374
|
+
const createPinItems = (items) => {
|
|
146
375
|
return items.map(item => {
|
|
147
|
-
const flatItem = flatItems.find(fi => fi.
|
|
148
|
-
const itemId = flatItem?.id || '';
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const enhanced = {
|
|
376
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
377
|
+
const itemId = flatItem?.id || item.id || '';
|
|
378
|
+
const isAlreadyPinned = currentItemIds.has(itemId);
|
|
379
|
+
const pinItem = {
|
|
152
380
|
...item,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
381
|
+
// Remove rightIconProps in config mode - clicking the item itself pins it
|
|
382
|
+
rightIconProps: undefined,
|
|
383
|
+
onClick: item.onClick && !item.submenu ? () => {
|
|
384
|
+
if (flatItem && !isAlreadyPinned) {
|
|
156
385
|
togglePin(flatItem);
|
|
157
386
|
}
|
|
158
387
|
} : undefined,
|
|
388
|
+
disabled: isAlreadyPinned,
|
|
159
389
|
};
|
|
160
390
|
if (item.submenu) {
|
|
161
|
-
|
|
391
|
+
pinItem.submenu = createPinItems(item.submenu);
|
|
162
392
|
}
|
|
163
|
-
return
|
|
393
|
+
return pinItem;
|
|
164
394
|
});
|
|
165
395
|
};
|
|
166
|
-
|
|
396
|
+
const pinItems = createPinItems(contextMenuItems);
|
|
397
|
+
// Add separator option at the end
|
|
398
|
+
pinItems.push({
|
|
399
|
+
id: 'add-separator',
|
|
400
|
+
name: SDKUI_Localizator.Add + ' separatore',
|
|
401
|
+
icon: _jsx(IconSeparator, {}),
|
|
402
|
+
onClick: addSeparator,
|
|
403
|
+
beginGroup: true
|
|
404
|
+
});
|
|
405
|
+
return pinItems;
|
|
406
|
+
}, [contextMenuItems, flattenMenuItems, togglePin, state.items, addSeparator]);
|
|
407
|
+
const getContextMenuItemsWithPinIcons = useCallback(() => {
|
|
408
|
+
const flatItems = flattenMenuItems(contextMenuItems);
|
|
409
|
+
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
410
|
+
const addPinIcons = (items) => {
|
|
411
|
+
return items.map(item => {
|
|
412
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
413
|
+
const itemId = flatItem?.id || item.id || '';
|
|
414
|
+
const isPinned = currentItemIds.has(itemId);
|
|
415
|
+
const itemWithPin = {
|
|
416
|
+
...item,
|
|
417
|
+
rightIconProps: flatItem ? {
|
|
418
|
+
icon: _jsx(IconPin, {}),
|
|
419
|
+
activeColor: 'red',
|
|
420
|
+
inactiveColor: 'black',
|
|
421
|
+
isActive: isPinned,
|
|
422
|
+
onClick: () => togglePin(flatItem),
|
|
423
|
+
} : undefined,
|
|
424
|
+
};
|
|
425
|
+
if (item.submenu) {
|
|
426
|
+
itemWithPin.submenu = addPinIcons(item.submenu);
|
|
427
|
+
}
|
|
428
|
+
return itemWithPin;
|
|
429
|
+
});
|
|
430
|
+
};
|
|
431
|
+
return addPinIcons(contextMenuItems);
|
|
167
432
|
}, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
|
|
168
433
|
const handleMouseDown = (e) => {
|
|
169
434
|
if (state.isConfigMode)
|
|
@@ -174,20 +439,27 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
174
439
|
if (isConstrained) {
|
|
175
440
|
// For absolute positioning, offset is relative to container
|
|
176
441
|
dragOffset.current = {
|
|
177
|
-
x: e.clientX - containerRect.left -
|
|
178
|
-
y: e.clientY - containerRect.top -
|
|
442
|
+
x: e.clientX - containerRect.left - pixelPosition.x,
|
|
443
|
+
y: e.clientY - containerRect.top - pixelPosition.y,
|
|
179
444
|
};
|
|
180
445
|
}
|
|
181
446
|
else {
|
|
182
447
|
// For fixed positioning, offset is relative to viewport
|
|
183
448
|
dragOffset.current = {
|
|
184
|
-
x: e.clientX -
|
|
185
|
-
y: e.clientY -
|
|
449
|
+
x: e.clientX - pixelPosition.x,
|
|
450
|
+
y: e.clientY - pixelPosition.y,
|
|
186
451
|
};
|
|
187
452
|
}
|
|
188
453
|
}
|
|
454
|
+
// Save starting position to detect if user actually moved the bar
|
|
455
|
+
dragStartPosition.current = { ...pixelPosition };
|
|
189
456
|
setState(s => ({ ...s, isDragging: true }));
|
|
190
457
|
};
|
|
458
|
+
const handleGripDoubleClick = () => {
|
|
459
|
+
if (state.isConfigMode)
|
|
460
|
+
return;
|
|
461
|
+
toggleOrientation();
|
|
462
|
+
};
|
|
191
463
|
const handleMouseMove = useCallback((e) => {
|
|
192
464
|
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
193
465
|
return;
|
|
@@ -210,50 +482,366 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
210
482
|
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
211
483
|
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
212
484
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
position: { x: newX, y: newY },
|
|
216
|
-
}));
|
|
485
|
+
// Update pixel position directly during drag
|
|
486
|
+
setPixelPosition({ x: newX, y: newY });
|
|
217
487
|
}, [state.isDragging, containerRef, isConstrained]);
|
|
218
488
|
const handleMouseUp = useCallback(() => {
|
|
219
|
-
|
|
220
|
-
|
|
489
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
490
|
+
// Convert final pixel position to percentage before updating state
|
|
491
|
+
const percentagePosition = {
|
|
492
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
493
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
494
|
+
};
|
|
495
|
+
// Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
|
|
496
|
+
if (dragStartPosition.current) {
|
|
497
|
+
const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
|
|
498
|
+
const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
|
|
499
|
+
if (dx > 5 || dy > 5) {
|
|
500
|
+
positionBeforeOrientationChange.current = null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
dragStartPosition.current = null;
|
|
504
|
+
setState(s => ({
|
|
505
|
+
...s,
|
|
506
|
+
isDragging: false,
|
|
507
|
+
position: percentagePosition,
|
|
508
|
+
}));
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
512
|
+
}
|
|
513
|
+
}, [state.isDragging, pixelPosition]);
|
|
514
|
+
// Touch event handlers for tablet support
|
|
515
|
+
const handleTouchStart = (e) => {
|
|
516
|
+
if (state.isConfigMode)
|
|
517
|
+
return;
|
|
518
|
+
const touch = e.touches[0];
|
|
519
|
+
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
520
|
+
if (containerRect) {
|
|
521
|
+
if (isConstrained) {
|
|
522
|
+
dragOffset.current = {
|
|
523
|
+
x: touch.clientX - containerRect.left - pixelPosition.x,
|
|
524
|
+
y: touch.clientY - containerRect.top - pixelPosition.y,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
dragOffset.current = {
|
|
529
|
+
x: touch.clientX - pixelPosition.x,
|
|
530
|
+
y: touch.clientY - pixelPosition.y,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Save starting position to detect if user actually moved the bar
|
|
535
|
+
dragStartPosition.current = { ...pixelPosition };
|
|
536
|
+
setState(s => ({ ...s, isDragging: true }));
|
|
537
|
+
};
|
|
538
|
+
const handleTouchMove = useCallback((e) => {
|
|
539
|
+
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
540
|
+
return;
|
|
541
|
+
const touch = e.touches[0];
|
|
542
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
543
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
544
|
+
let newX, newY;
|
|
545
|
+
if (isConstrained) {
|
|
546
|
+
newX = touch.clientX - container.left - dragOffset.current.x;
|
|
547
|
+
newY = touch.clientY - container.top - dragOffset.current.y;
|
|
548
|
+
newX = Math.max(0, Math.min(newX, container.width - floating.width));
|
|
549
|
+
newY = Math.max(0, Math.min(newY, container.height - floating.height));
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
newX = touch.clientX - dragOffset.current.x;
|
|
553
|
+
newY = touch.clientY - dragOffset.current.y;
|
|
554
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
555
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
556
|
+
}
|
|
557
|
+
setPixelPosition({ x: newX, y: newY });
|
|
558
|
+
}, [state.isDragging, containerRef, isConstrained]);
|
|
559
|
+
const handleTouchEnd = useCallback(() => {
|
|
560
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
561
|
+
const percentagePosition = {
|
|
562
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
563
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
564
|
+
};
|
|
565
|
+
// Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
|
|
566
|
+
if (dragStartPosition.current) {
|
|
567
|
+
const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
|
|
568
|
+
const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
|
|
569
|
+
if (dx > 5 || dy > 5) {
|
|
570
|
+
positionBeforeOrientationChange.current = null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
dragStartPosition.current = null;
|
|
574
|
+
setState(s => ({
|
|
575
|
+
...s,
|
|
576
|
+
isDragging: false,
|
|
577
|
+
position: percentagePosition,
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
582
|
+
}
|
|
583
|
+
}, [state.isDragging, pixelPosition]);
|
|
221
584
|
useEffect(() => {
|
|
222
585
|
if (state.isDragging) {
|
|
223
586
|
document.addEventListener('mousemove', handleMouseMove);
|
|
224
587
|
document.addEventListener('mouseup', handleMouseUp);
|
|
588
|
+
document.addEventListener('touchmove', handleTouchMove);
|
|
589
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
225
590
|
return () => {
|
|
226
591
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
227
592
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
593
|
+
document.removeEventListener('touchmove', handleTouchMove);
|
|
594
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
228
595
|
};
|
|
229
596
|
}
|
|
230
597
|
return undefined;
|
|
231
|
-
}, [state.isDragging, handleMouseMove, handleMouseUp]);
|
|
232
|
-
// Save to
|
|
598
|
+
}, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
|
|
599
|
+
// Save to SDKUI_Globals.userSettings only when NOT in config mode (when applying changes)
|
|
233
600
|
useEffect(() => {
|
|
601
|
+
if (state.isConfigMode)
|
|
602
|
+
return; // Don't save during edit mode
|
|
603
|
+
if (disbaleConfigMode)
|
|
604
|
+
return; // Don't save if config mode is disabled
|
|
605
|
+
const pinnedIds = state.items.map(item => item.id);
|
|
234
606
|
try {
|
|
235
|
-
|
|
607
|
+
// Replace the entire object to trigger the Proxy
|
|
608
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
236
609
|
orientation: state.orientation,
|
|
237
|
-
|
|
238
|
-
|
|
610
|
+
itemIds: pinnedIds,
|
|
611
|
+
position: state.position,
|
|
612
|
+
positionFormat: 'percentage',
|
|
239
613
|
};
|
|
240
|
-
localStorage.setItem(storageKey, JSON.stringify(config));
|
|
241
614
|
}
|
|
242
615
|
catch (error) {
|
|
243
616
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
244
617
|
}
|
|
245
|
-
|
|
618
|
+
// Notify parent about pin changes only if not syncing from external
|
|
619
|
+
// This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
|
|
620
|
+
if (!isSyncingFromExternal.current) {
|
|
621
|
+
onPinChange?.(pinnedIds);
|
|
622
|
+
}
|
|
623
|
+
isSyncingFromExternal.current = false; // Reset the flag
|
|
624
|
+
}, [state.orientation, state.items, state.position, state.isConfigMode, disbaleConfigMode, onPinChange]);
|
|
246
625
|
const toggleConfigMode = () => {
|
|
247
|
-
setState(s =>
|
|
626
|
+
setState(s => {
|
|
627
|
+
if (!s.isConfigMode) {
|
|
628
|
+
stateSnapshot.current = {
|
|
629
|
+
items: [...s.items],
|
|
630
|
+
orientation: s.orientation,
|
|
631
|
+
verticalDirection: s.verticalDirection,
|
|
632
|
+
position: { ...s.position },
|
|
633
|
+
};
|
|
634
|
+
return { ...s, isConfigMode: true };
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
|
|
638
|
+
stateSnapshot.current = null;
|
|
639
|
+
isExitingConfigMode.current = true; // Prevent sync effect from overwriting changes
|
|
640
|
+
const cleanedItems = removeTrailingSeparators(s.items);
|
|
641
|
+
return { ...s, isConfigMode: false, items: cleanedItems };
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
// Auto-reposition when entering edit mode to ensure Apply/Undo buttons are visible
|
|
646
|
+
useEffect(() => {
|
|
647
|
+
if (!state.isConfigMode || !floatingRef.current)
|
|
648
|
+
return;
|
|
649
|
+
// Use double requestAnimationFrame to ensure the DOM has fully updated with new buttons
|
|
650
|
+
requestAnimationFrame(() => {
|
|
651
|
+
requestAnimationFrame(() => {
|
|
652
|
+
if (!floatingRef.current)
|
|
653
|
+
return;
|
|
654
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
655
|
+
const containerWidth = isConstrained && containerRef.current
|
|
656
|
+
? containerRef.current.getBoundingClientRect().width
|
|
657
|
+
: window.innerWidth;
|
|
658
|
+
const containerHeight = isConstrained && containerRef.current
|
|
659
|
+
? containerRef.current.getBoundingClientRect().height
|
|
660
|
+
: window.innerHeight;
|
|
661
|
+
// Use current pixel position
|
|
662
|
+
let newPixelX = pixelPosition.x;
|
|
663
|
+
let newPixelY = pixelPosition.y;
|
|
664
|
+
let needsUpdate = false;
|
|
665
|
+
// Check horizontal overflow
|
|
666
|
+
if (newPixelX + floating.width > containerWidth) {
|
|
667
|
+
newPixelX = Math.max(0, containerWidth - floating.width);
|
|
668
|
+
needsUpdate = true;
|
|
669
|
+
}
|
|
670
|
+
// Check vertical overflow
|
|
671
|
+
if (newPixelY + floating.height > containerHeight) {
|
|
672
|
+
newPixelY = Math.max(0, containerHeight - floating.height);
|
|
673
|
+
needsUpdate = true;
|
|
674
|
+
}
|
|
675
|
+
if (needsUpdate) {
|
|
676
|
+
// Update pixel position immediately
|
|
677
|
+
setPixelPosition({ x: newPixelX, y: newPixelY });
|
|
678
|
+
// Convert to percentage for state
|
|
679
|
+
const newPercentagePosition = {
|
|
680
|
+
x: pixelsToPercent(newPixelX, containerWidth),
|
|
681
|
+
y: pixelsToPercent(newPixelY, containerHeight),
|
|
682
|
+
};
|
|
683
|
+
setState(s => ({
|
|
684
|
+
...s,
|
|
685
|
+
position: newPercentagePosition,
|
|
686
|
+
}));
|
|
687
|
+
// Update snapshot position to the corrected position so Undo restores to visible position
|
|
688
|
+
if (stateSnapshot.current) {
|
|
689
|
+
stateSnapshot.current.position = newPercentagePosition;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
}, [state.isConfigMode, state.orientation, isConstrained, state.items, pixelPosition.x, pixelPosition.y]);
|
|
695
|
+
const handleUndo = () => {
|
|
696
|
+
if (stateSnapshot.current) {
|
|
697
|
+
setState(s => ({
|
|
698
|
+
...s,
|
|
699
|
+
items: [...stateSnapshot.current.items],
|
|
700
|
+
orientation: stateSnapshot.current.orientation,
|
|
701
|
+
verticalDirection: stateSnapshot.current.verticalDirection,
|
|
702
|
+
position: { ...stateSnapshot.current.position },
|
|
703
|
+
isConfigMode: true,
|
|
704
|
+
}));
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
// Check if current state has changed from snapshot
|
|
708
|
+
const hasChanges = () => {
|
|
709
|
+
if (!stateSnapshot.current)
|
|
710
|
+
return false;
|
|
711
|
+
// Check if items have changed (different IDs or different order)
|
|
712
|
+
const currentIds = state.items.map(i => i.id).join(',');
|
|
713
|
+
const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
|
|
714
|
+
return currentIds !== snapshotIds;
|
|
715
|
+
};
|
|
716
|
+
const handleClose = () => {
|
|
717
|
+
// If all items removed, exit without asking and restore last items
|
|
718
|
+
if (state.items.length === 0 && stateSnapshot.current) {
|
|
719
|
+
setState(s => ({
|
|
720
|
+
...s,
|
|
721
|
+
items: [...stateSnapshot.current.items],
|
|
722
|
+
orientation: stateSnapshot.current.orientation,
|
|
723
|
+
verticalDirection: stateSnapshot.current.verticalDirection,
|
|
724
|
+
position: { ...stateSnapshot.current.position },
|
|
725
|
+
isConfigMode: false,
|
|
726
|
+
}));
|
|
727
|
+
stateSnapshot.current = null;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// If no changes, simply exit config mode
|
|
731
|
+
if (!hasChanges()) {
|
|
732
|
+
stateSnapshot.current = null;
|
|
733
|
+
const cleanedItems = removeTrailingSeparators(state.items);
|
|
734
|
+
setState(s => ({ ...s, isConfigMode: false, items: cleanedItems }));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
// If there are changes, ask for confirmation
|
|
738
|
+
TMMessageBoxManager.show({
|
|
739
|
+
message: 'Perderai le tue modifiche, sei sicuro?',
|
|
740
|
+
buttons: [ButtonNames.YES, ButtonNames.NO],
|
|
741
|
+
onButtonClick: (buttonName) => {
|
|
742
|
+
if (buttonName === ButtonNames.YES && stateSnapshot.current) {
|
|
743
|
+
// Restore snapshot and exit config mode
|
|
744
|
+
setState(s => ({
|
|
745
|
+
...s,
|
|
746
|
+
items: [...stateSnapshot.current.items],
|
|
747
|
+
orientation: stateSnapshot.current.orientation,
|
|
748
|
+
verticalDirection: stateSnapshot.current.verticalDirection,
|
|
749
|
+
position: { ...stateSnapshot.current.position },
|
|
750
|
+
isConfigMode: false,
|
|
751
|
+
}));
|
|
752
|
+
stateSnapshot.current = null;
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
});
|
|
248
756
|
};
|
|
249
757
|
const toggleOrientation = () => {
|
|
250
758
|
const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
|
251
|
-
//
|
|
759
|
+
// When switching from vertical back to horizontal, restore the original position
|
|
760
|
+
// Use visibility hiding only for this case to prevent flicker during position restoration
|
|
761
|
+
if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
|
|
762
|
+
if (positionBeforeOrientationChange.current) {
|
|
763
|
+
setIsOrientationChanging(true); // Hide only when restoring position
|
|
764
|
+
const savedPosition = positionBeforeOrientationChange.current.position;
|
|
765
|
+
const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
|
|
766
|
+
setPixelPosition(savedPixelPosition);
|
|
767
|
+
setState(s => ({
|
|
768
|
+
...s,
|
|
769
|
+
orientation: newOrientation,
|
|
770
|
+
verticalDirection: 'down',
|
|
771
|
+
position: savedPosition,
|
|
772
|
+
}));
|
|
773
|
+
positionBeforeOrientationChange.current = null;
|
|
774
|
+
// Show the bar after the state has been applied
|
|
775
|
+
requestAnimationFrame(() => {
|
|
776
|
+
setIsOrientationChanging(false);
|
|
777
|
+
});
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// When switching to vertical, save current position and check if we need to expand upward
|
|
782
|
+
// Use opacity hiding (doesn't affect focus unlike visibility:hidden)
|
|
783
|
+
if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
|
|
784
|
+
setIsOrientationChanging(true);
|
|
785
|
+
// Save the current position before changing orientation
|
|
786
|
+
positionBeforeOrientationChange.current = {
|
|
787
|
+
position: { ...state.position },
|
|
788
|
+
pixelPosition: { ...pixelPosition },
|
|
789
|
+
};
|
|
790
|
+
if (floatingRef.current) {
|
|
791
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
792
|
+
const containerHeight = isConstrained && containerRef.current
|
|
793
|
+
? containerRef.current.getBoundingClientRect().height
|
|
794
|
+
: window.innerHeight;
|
|
795
|
+
// Estimate vertical height (horizontal width becomes vertical height)
|
|
796
|
+
const estimatedVerticalHeight = floating.width;
|
|
797
|
+
if (estimatedVerticalHeight > containerHeight - 70) {
|
|
798
|
+
ShowAlert({
|
|
799
|
+
mode: 'warning',
|
|
800
|
+
title: 'Troppi elementi',
|
|
801
|
+
message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
|
|
802
|
+
duration: 4000,
|
|
803
|
+
});
|
|
804
|
+
positionBeforeOrientationChange.current = null; // Clear saved position since we're not changing
|
|
805
|
+
setIsOrientationChanging(false); // Show bar again since we're not changing
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
// Check if we're in the bottom part of the screen and don't have enough space below
|
|
809
|
+
const spaceBelow = containerHeight - floating.bottom;
|
|
810
|
+
const spaceAbove = floating.top;
|
|
811
|
+
const needsVerticalSpace = estimatedVerticalHeight - floating.height; // Additional space needed
|
|
812
|
+
// If not enough space below but enough space above, expand upward
|
|
813
|
+
if (spaceBelow < needsVerticalSpace && spaceAbove >= needsVerticalSpace) {
|
|
814
|
+
// Calculate the new Y position so the bottom of the bar stays at the same place
|
|
815
|
+
const currentBottom = pixelPosition.y + floating.height;
|
|
816
|
+
const newPixelY = currentBottom - estimatedVerticalHeight;
|
|
817
|
+
setState(s => ({
|
|
818
|
+
...s,
|
|
819
|
+
orientation: newOrientation,
|
|
820
|
+
verticalDirection: 'up',
|
|
821
|
+
}));
|
|
822
|
+
// Update pixel position and percentage after orientation change
|
|
823
|
+
requestAnimationFrame(() => {
|
|
824
|
+
requestAnimationFrame(() => {
|
|
825
|
+
const newY = Math.max(0, newPixelY);
|
|
826
|
+
setPixelPosition(prev => ({ ...prev, y: newY }));
|
|
827
|
+
const newPercentY = pixelsToPercent(newY, containerHeight);
|
|
828
|
+
setState(s => ({
|
|
829
|
+
...s,
|
|
830
|
+
position: { ...s.position, y: newPercentY },
|
|
831
|
+
}));
|
|
832
|
+
setIsOrientationChanging(false);
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Default case: just change orientation without special positioning
|
|
252
840
|
setState(s => ({
|
|
253
841
|
...s,
|
|
254
842
|
orientation: newOrientation,
|
|
843
|
+
verticalDirection: 'down',
|
|
255
844
|
}));
|
|
256
|
-
// Then, after DOM updates, adjust position to stay in bounds
|
|
257
845
|
requestAnimationFrame(() => {
|
|
258
846
|
requestAnimationFrame(() => {
|
|
259
847
|
if (containerRef.current && floatingRef.current) {
|
|
@@ -278,21 +866,105 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
278
866
|
}));
|
|
279
867
|
}
|
|
280
868
|
}
|
|
869
|
+
setIsOrientationChanging(false);
|
|
281
870
|
});
|
|
282
871
|
});
|
|
283
872
|
};
|
|
284
873
|
const removeItem = (itemId) => {
|
|
874
|
+
isLocalChange.current = true;
|
|
285
875
|
setState(s => ({
|
|
286
876
|
...s,
|
|
287
877
|
items: s.items.filter(item => item.id !== itemId),
|
|
288
878
|
}));
|
|
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
879
|
};
|
|
880
|
+
const getItemRightClickMenuItems = useCallback((item, index) => {
|
|
881
|
+
const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
|
|
882
|
+
const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
|
|
883
|
+
return [
|
|
884
|
+
{
|
|
885
|
+
name: 'Rimuovi',
|
|
886
|
+
icon: _jsx(IconDelete, { fontSize: 16 }),
|
|
887
|
+
onClick: () => removeItem(item.id),
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
name: 'Aggiungi separatore a destra',
|
|
891
|
+
icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
|
|
892
|
+
disabled: hasSeparatorOnRight,
|
|
893
|
+
onClick: () => {
|
|
894
|
+
isLocalChange.current = true;
|
|
895
|
+
const separator = createSeparator();
|
|
896
|
+
setState(s => {
|
|
897
|
+
const newItems = [...s.items];
|
|
898
|
+
newItems.splice(index + 1, 0, separator);
|
|
899
|
+
return { ...s, items: newItems };
|
|
900
|
+
});
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: 'Aggiungi separatore a sinistra',
|
|
905
|
+
icon: _jsx(IconSeparator, { fontSize: 16 }),
|
|
906
|
+
disabled: hasSeparatorOnLeft,
|
|
907
|
+
onClick: () => {
|
|
908
|
+
isLocalChange.current = true;
|
|
909
|
+
const separator = createSeparator();
|
|
910
|
+
setState(s => {
|
|
911
|
+
const newItems = [...s.items];
|
|
912
|
+
newItems.splice(index, 0, separator);
|
|
913
|
+
return { ...s, items: newItems };
|
|
914
|
+
});
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
...(!disbaleConfigMode ? [{
|
|
918
|
+
beginGroup: true,
|
|
919
|
+
name: SDKUI_Localizator.Configure,
|
|
920
|
+
icon: _jsx(IconSettings, { fontSize: 16 }),
|
|
921
|
+
onClick: () => {
|
|
922
|
+
if (!state.isConfigMode) {
|
|
923
|
+
toggleConfigMode();
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
}] : []),
|
|
927
|
+
{
|
|
928
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
929
|
+
icon: _jsx(IconRotate, { fontSize: 16 }),
|
|
930
|
+
onClick: toggleOrientation,
|
|
931
|
+
},
|
|
932
|
+
];
|
|
933
|
+
}, [state.items, state.isConfigMode, state.orientation, removeItem, createSeparator, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
|
|
934
|
+
const getSeparatorRightClickMenuItems = useCallback((index) => {
|
|
935
|
+
return [
|
|
936
|
+
{
|
|
937
|
+
name: 'Rimuovi',
|
|
938
|
+
icon: _jsx(IconDelete, { fontSize: 16 }),
|
|
939
|
+
onClick: () => removeItem(state.items[index].id),
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: 'Aggiungi separatore a destra',
|
|
943
|
+
icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
|
|
944
|
+
disabled: true,
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: 'Aggiungi separatore a sinistra',
|
|
948
|
+
icon: _jsx(IconSeparator, { fontSize: 16 }),
|
|
949
|
+
disabled: true,
|
|
950
|
+
},
|
|
951
|
+
...(!disbaleConfigMode ? [{
|
|
952
|
+
beginGroup: true,
|
|
953
|
+
name: SDKUI_Localizator.Configure,
|
|
954
|
+
icon: _jsx(IconSettings, { fontSize: 16 }),
|
|
955
|
+
onClick: () => {
|
|
956
|
+
if (!state.isConfigMode) {
|
|
957
|
+
toggleConfigMode();
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
}] : []),
|
|
961
|
+
{
|
|
962
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
963
|
+
icon: _jsx(IconRotate, { fontSize: 16 }),
|
|
964
|
+
onClick: toggleOrientation,
|
|
965
|
+
},
|
|
966
|
+
];
|
|
967
|
+
}, [state.items, state.isConfigMode, state.orientation, removeItem, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
|
|
296
968
|
// Drag and drop for reordering
|
|
297
969
|
const handleDragStart = (e, index) => {
|
|
298
970
|
if (!state.isConfigMode)
|
|
@@ -347,24 +1019,46 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
347
1019
|
setState(s => ({ ...s, draggedItemIndex: null }));
|
|
348
1020
|
setDragOverIndex(null);
|
|
349
1021
|
};
|
|
350
|
-
return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x":
|
|
1022
|
+
return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: state.isConfigMode ? (e) => e.preventDefault() : undefined, children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
|
|
1023
|
+
...(!disbaleConfigMode ? [{
|
|
1024
|
+
name: SDKUI_Localizator.Configure,
|
|
1025
|
+
icon: _jsx(IconSettings, { fontSize: 16 }),
|
|
1026
|
+
onClick: () => {
|
|
1027
|
+
if (!state.isConfigMode) {
|
|
1028
|
+
toggleConfigMode();
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
}] : []),
|
|
1032
|
+
{
|
|
1033
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
1034
|
+
icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
|
|
1035
|
+
onClick: toggleOrientation,
|
|
1036
|
+
},
|
|
1037
|
+
], 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) => {
|
|
1038
|
+
// Handle separator items
|
|
1039
|
+
if (item.isSeparator) {
|
|
1040
|
+
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: state.isConfigMode ? (_jsxs(_Fragment, { children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (_jsx(ContextMenu, { items: getSeparatorRightClickMenuItems(index), trigger: "right", children: _jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }) })) }, item.id));
|
|
1041
|
+
}
|
|
351
1042
|
// Get current state (disabled and onClick) from contextMenuItems
|
|
352
|
-
const currentState = getCurrentItemState(item.
|
|
353
|
-
|
|
1043
|
+
const currentState = getCurrentItemState(item.id);
|
|
1044
|
+
// Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
|
|
1045
|
+
const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
|
|
1046
|
+
? currentState.disabled === true
|
|
1047
|
+
: item.disabled === true;
|
|
354
1048
|
const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
|
|
355
|
-
return (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}, disabled: isDisabled && !state.isConfigMode, children: item.icon })) : (_jsx(TMTooltip, { content: item.name, position: "top", children: _jsx(S.MenuButton, { onClick: () => {
|
|
362
|
-
if (state.isConfigMode || isDisabled)
|
|
1049
|
+
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: state.isConfigMode ? (
|
|
1050
|
+
// Config mode: show remove button, no right-click menu
|
|
1051
|
+
_jsxs(_Fragment, { children: [item.staticItem ? (item.staticItem) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => { }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (
|
|
1052
|
+
// Normal mode: wrap in right-click context menu
|
|
1053
|
+
_jsx(ContextMenu, { items: getItemRightClickMenuItems(item, index), trigger: "right", children: item.staticItem ? (_jsx("div", { children: item.staticItem })) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
|
|
1054
|
+
if (isDisabled)
|
|
363
1055
|
return;
|
|
364
1056
|
if (currentOnClick) {
|
|
365
1057
|
currentOnClick();
|
|
366
1058
|
}
|
|
367
|
-
}, disabled: isDisabled, children: item.icon }) }))
|
|
368
|
-
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items:
|
|
1059
|
+
}, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })) })) }, item.id));
|
|
1060
|
+
}), !state.isConfigMode && !disbaleConfigMode && hasContextMenu && 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
|
|
1061
|
+
? 'Devi aggiungere almeno un item'
|
|
1062
|
+
: 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
1063
|
};
|
|
370
1064
|
export default TMFloatingMenuBar;
|