@topconsultnpm/sdkui-react 6.20.0-dev1.12 → 6.20.0-dev1.121
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 +610 -127
- 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 -3
- 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,200 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, 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 { IconDelete, IconMenuVertical, IconPin, IconRotate, IconSeparator, SDKUI_Globals } from '../../../helper';
|
|
8
|
+
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
9
|
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 = [],
|
|
10
|
+
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, contextMenuDefaultPinnedIds = [], defaultItems = [], enableConfigMode = true, bgColor = undefined, hasContextMenu = true, pinnedItemIds: externalPinnedItemIds, onPinChange, }) => {
|
|
11
|
+
const percentToPixels = (percent, containerSize) => {
|
|
12
|
+
return (percent / 100) * containerSize;
|
|
13
|
+
};
|
|
14
|
+
const pixelsToPercent = (pixels, containerSize) => {
|
|
15
|
+
return (pixels / containerSize) * 100;
|
|
16
|
+
};
|
|
17
|
+
const isPixelFormat = (pos) => {
|
|
18
|
+
return pos.x > 100 || pos.y > 100;
|
|
19
|
+
};
|
|
20
|
+
const migrateToPercentage = (pixelPos) => {
|
|
21
|
+
const container = containerRef.current?.getBoundingClientRect();
|
|
22
|
+
const containerWidth = isConstrained && container ? container.width : window.innerWidth;
|
|
23
|
+
const containerHeight = isConstrained && container ? container.height : window.innerHeight;
|
|
24
|
+
return {
|
|
25
|
+
x: pixelsToPercent(pixelPos.x, containerWidth),
|
|
26
|
+
y: pixelsToPercent(pixelPos.y, containerHeight),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const getDefaultConfig = () => ({
|
|
30
|
+
orientation: defaultOrientation,
|
|
31
|
+
savedItemIds: contextMenuDefaultPinnedIds,
|
|
32
|
+
position: defaultPosition,
|
|
33
|
+
});
|
|
34
|
+
const resetFloatingBarSettings = () => {
|
|
35
|
+
// Reset the floatingMenuBar settings in SDKUI_Globals to empty
|
|
36
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
37
|
+
orientation: undefined,
|
|
38
|
+
itemIds: undefined,
|
|
39
|
+
position: undefined,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
10
42
|
const loadConfig = () => {
|
|
43
|
+
// If config mode is disabled, use default position and orientation
|
|
44
|
+
if (!enableConfigMode) {
|
|
45
|
+
return {
|
|
46
|
+
orientation: defaultOrientation,
|
|
47
|
+
savedItemIds: contextMenuDefaultPinnedIds,
|
|
48
|
+
position: defaultPosition,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
11
51
|
try {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
52
|
+
const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
|
|
53
|
+
// If localStorage is empty (first time), use props as defaults
|
|
54
|
+
if (!settings || !settings.position || !settings.itemIds) {
|
|
55
|
+
return getDefaultConfig();
|
|
56
|
+
}
|
|
57
|
+
// Validate position
|
|
58
|
+
const hasValidPosition = settings.position &&
|
|
59
|
+
typeof settings.position.x === 'number' &&
|
|
60
|
+
typeof settings.position.y === 'number' &&
|
|
61
|
+
!Number.isNaN(settings.position.x) &&
|
|
62
|
+
!Number.isNaN(settings.position.y) &&
|
|
63
|
+
Number.isFinite(settings.position.x) &&
|
|
64
|
+
Number.isFinite(settings.position.y);
|
|
65
|
+
if (!hasValidPosition) {
|
|
66
|
+
console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
|
|
67
|
+
resetFloatingBarSettings();
|
|
68
|
+
return getDefaultConfig();
|
|
69
|
+
}
|
|
70
|
+
// Ensure position is within reasonable viewport bounds
|
|
71
|
+
const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
|
|
72
|
+
const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
|
|
73
|
+
if (settings.position.x < 0 || settings.position.x > maxX ||
|
|
74
|
+
settings.position.y < 0 || settings.position.y > maxY) {
|
|
75
|
+
console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
|
|
76
|
+
resetFloatingBarSettings();
|
|
77
|
+
return getDefaultConfig();
|
|
78
|
+
}
|
|
79
|
+
// Validate orientation
|
|
80
|
+
const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
|
|
81
|
+
? settings.orientation
|
|
82
|
+
: 'horizontal';
|
|
83
|
+
// Validate itemIds
|
|
84
|
+
const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
|
|
85
|
+
if (validItemIds.length > 0) {
|
|
86
|
+
// Check if any ID looks like the old format (contains language-specific text or is too long)
|
|
87
|
+
const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
|
|
88
|
+
!id.startsWith('separator-') &&
|
|
89
|
+
(id.length > 20 || id.includes(' ')));
|
|
90
|
+
if (hasOldFormatIds) {
|
|
91
|
+
console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
|
|
92
|
+
resetFloatingBarSettings();
|
|
93
|
+
return getDefaultConfig();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Migrate old pixel-based position to percentage-based
|
|
97
|
+
let finalPosition = settings.position;
|
|
98
|
+
if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
|
|
99
|
+
console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
|
|
100
|
+
finalPosition = migrateToPercentage(settings.position);
|
|
101
|
+
// Save migrated position immediately
|
|
102
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
103
|
+
orientation: validOrientation,
|
|
104
|
+
itemIds: validItemIds,
|
|
105
|
+
position: finalPosition,
|
|
106
|
+
positionFormat: 'percentage',
|
|
19
107
|
};
|
|
20
108
|
}
|
|
109
|
+
return {
|
|
110
|
+
orientation: validOrientation,
|
|
111
|
+
savedItemIds: validItemIds,
|
|
112
|
+
position: finalPosition,
|
|
113
|
+
};
|
|
21
114
|
}
|
|
22
115
|
catch (error) {
|
|
23
116
|
console.error('Failed to load FloatingMenuBar config:', error);
|
|
117
|
+
// Reset to defaults on any error
|
|
118
|
+
try {
|
|
119
|
+
resetFloatingBarSettings();
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
console.error('Failed to reset FloatingMenuBar settings:', e);
|
|
123
|
+
}
|
|
124
|
+
return getDefaultConfig();
|
|
24
125
|
}
|
|
25
|
-
return {
|
|
26
|
-
orientation: 'horizontal',
|
|
27
|
-
pinnedItemIds: new Set(),
|
|
28
|
-
savedItemIds: [],
|
|
29
|
-
};
|
|
30
126
|
};
|
|
31
127
|
const initialConfig = loadConfig();
|
|
32
128
|
const [state, setState] = useState({
|
|
33
|
-
position:
|
|
129
|
+
position: initialConfig.position, // Stored as percentage
|
|
34
130
|
isDragging: false,
|
|
35
|
-
isConfigMode: false,
|
|
36
131
|
orientation: initialConfig.orientation,
|
|
132
|
+
verticalDirection: 'down',
|
|
37
133
|
items: [],
|
|
38
134
|
draggedItemIndex: null,
|
|
39
135
|
});
|
|
40
136
|
const floatingRef = useRef(null);
|
|
41
137
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
42
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
43
138
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
44
|
-
const [
|
|
45
|
-
|
|
139
|
+
const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
|
|
140
|
+
const [isOrientationChanging, setIsOrientationChanging] = useState(false); // Hide bar during orientation transition
|
|
141
|
+
const containerSizeRef = useRef({ width: 0, height: 0 });
|
|
142
|
+
const positionBeforeOrientationChange = useRef(null);
|
|
46
143
|
const floatingBarItemIds = useRef(new Set());
|
|
47
144
|
const floatingBarItemNames = useRef(new Set());
|
|
48
|
-
|
|
145
|
+
const isSyncingFromExternal = useRef(false);
|
|
146
|
+
const isLocalChange = useRef(false);
|
|
147
|
+
const dragStartPosition = useRef(null);
|
|
49
148
|
useEffect(() => {
|
|
50
149
|
floatingBarItemIds.current = new Set(state.items.map(i => i.id));
|
|
51
150
|
floatingBarItemNames.current = new Set(state.items.map(i => i.name));
|
|
52
151
|
}, [state.items]);
|
|
53
|
-
//
|
|
152
|
+
// Calculate pixel position from percentage when container size or position changes
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
const updatePixelPosition = () => {
|
|
155
|
+
if (!containerRef.current || !floatingRef.current)
|
|
156
|
+
return;
|
|
157
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
158
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
159
|
+
const containerWidth = isConstrained ? container.width : window.innerWidth;
|
|
160
|
+
const containerHeight = isConstrained ? container.height : window.innerHeight;
|
|
161
|
+
containerSizeRef.current = { width: containerWidth, height: containerHeight };
|
|
162
|
+
let newX = percentToPixels(state.position.x, containerWidth);
|
|
163
|
+
let newY = percentToPixels(state.position.y, containerHeight);
|
|
164
|
+
newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
|
|
165
|
+
newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
|
|
166
|
+
setPixelPosition({ x: newX, y: newY });
|
|
167
|
+
};
|
|
168
|
+
updatePixelPosition();
|
|
169
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
170
|
+
updatePixelPosition();
|
|
171
|
+
});
|
|
172
|
+
if (containerRef.current) {
|
|
173
|
+
resizeObserver.observe(containerRef.current);
|
|
174
|
+
}
|
|
175
|
+
if (!isConstrained) {
|
|
176
|
+
window.addEventListener('resize', updatePixelPosition);
|
|
177
|
+
}
|
|
178
|
+
return () => {
|
|
179
|
+
resizeObserver.disconnect();
|
|
180
|
+
if (!isConstrained) {
|
|
181
|
+
window.removeEventListener('resize', updatePixelPosition);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}, [state.position, isConstrained]);
|
|
54
185
|
const flattenMenuItems = useCallback((items, parentPath = '') => {
|
|
55
186
|
const result = [];
|
|
56
187
|
items.forEach((item, index) => {
|
|
57
|
-
const itemId = `${parentPath}${item.name}-${index}`;
|
|
58
|
-
// Only add items that have onClick (final actions, not submenu parents)
|
|
188
|
+
const itemId = item.id || `${parentPath}${item.name}-${index}`;
|
|
59
189
|
if (item.onClick && !item.submenu) {
|
|
190
|
+
const isPinned = state.items.some(i => i.id === itemId);
|
|
60
191
|
result.push({
|
|
61
192
|
id: itemId,
|
|
62
193
|
name: item.name,
|
|
63
|
-
icon: item.icon || _jsx(
|
|
194
|
+
icon: item.icon || _jsx(IconPin, {}),
|
|
64
195
|
onClick: item.onClick,
|
|
65
196
|
disabled: item.disabled,
|
|
66
|
-
isPinned:
|
|
67
|
-
originalMenuItem: item,
|
|
197
|
+
isPinned: isPinned,
|
|
68
198
|
});
|
|
69
199
|
}
|
|
70
200
|
// Recursively process submenus
|
|
@@ -73,56 +203,115 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
73
203
|
}
|
|
74
204
|
});
|
|
75
205
|
return result;
|
|
76
|
-
}, [
|
|
206
|
+
}, [state.items]);
|
|
77
207
|
// Restore items on mount from savedItemIds
|
|
78
208
|
useEffect(() => {
|
|
79
|
-
if (contextMenuItems.length > 0) {
|
|
209
|
+
if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0 || defaultItems.length > 0) {
|
|
80
210
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
211
|
+
let itemsToSet = [];
|
|
212
|
+
// If enableConfigMode is false and defaultItems provided, use only defaultItems
|
|
213
|
+
if (!enableConfigMode && defaultItems.length > 0) {
|
|
214
|
+
itemsToSet = defaultItems;
|
|
215
|
+
}
|
|
216
|
+
else if (enableConfigMode && initialConfig.savedItemIds.length > 0) {
|
|
217
|
+
// Restore items in the saved order from localStorage (only if config mode is enabled)
|
|
218
|
+
const restoredItems = initialConfig.savedItemIds
|
|
219
|
+
.map((id) => {
|
|
220
|
+
if (id.startsWith('separator-')) {
|
|
221
|
+
return {
|
|
222
|
+
id,
|
|
223
|
+
name: 'Separator',
|
|
224
|
+
icon: _jsx(Separator, {}),
|
|
225
|
+
onClick: () => { },
|
|
226
|
+
isSeparator: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return flatItems.find(item => item.id === id);
|
|
230
|
+
})
|
|
231
|
+
.filter((item) => item !== undefined);
|
|
232
|
+
itemsToSet = restoredItems;
|
|
233
|
+
}
|
|
234
|
+
else if (contextMenuDefaultPinnedIds.length > 0) {
|
|
235
|
+
// First time: Use contextMenuDefaultPinnedIds from props to find items by ID
|
|
236
|
+
const defaultPinnedItems = contextMenuDefaultPinnedIds
|
|
237
|
+
.map((id) => flatItems.find(item => item.id === id))
|
|
238
|
+
.filter((item) => item !== undefined);
|
|
239
|
+
itemsToSet = defaultPinnedItems;
|
|
240
|
+
}
|
|
241
|
+
else if (defaultItems.length > 0) {
|
|
242
|
+
// Use defaultItems as fallback
|
|
243
|
+
itemsToSet = defaultItems;
|
|
244
|
+
}
|
|
245
|
+
if (itemsToSet.length > 0) {
|
|
246
|
+
setState(s => ({ ...s, items: itemsToSet }));
|
|
87
247
|
}
|
|
88
248
|
}
|
|
89
|
-
}, []); //
|
|
249
|
+
}, enableConfigMode ? [] : [defaultItems]); // Update when defaultItems change if config mode is disabled
|
|
250
|
+
// Sync with external pinnedItemIds when they change (from parent component)
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
// Skip sync if a local change was just made (e.g. right-click remove/add separator)
|
|
253
|
+
if (isLocalChange.current) {
|
|
254
|
+
isLocalChange.current = false;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (externalPinnedItemIds === undefined || !enableConfigMode)
|
|
258
|
+
return;
|
|
259
|
+
const flatItems = flattenMenuItems(contextMenuItems);
|
|
260
|
+
// Build items from external pinned IDs
|
|
261
|
+
const itemsFromExternal = externalPinnedItemIds
|
|
262
|
+
.map((id) => {
|
|
263
|
+
if (id.startsWith('separator-')) {
|
|
264
|
+
return {
|
|
265
|
+
id,
|
|
266
|
+
name: 'Separator',
|
|
267
|
+
icon: _jsx(Separator, {}),
|
|
268
|
+
onClick: () => { },
|
|
269
|
+
isSeparator: true,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return flatItems.find(item => item.id === id);
|
|
273
|
+
})
|
|
274
|
+
.filter((item) => item !== undefined);
|
|
275
|
+
// Only update if different
|
|
276
|
+
const currentIds = state.items.map(i => i.id).join(',');
|
|
277
|
+
const newIds = itemsFromExternal.map(i => i.id).join(',');
|
|
278
|
+
if (currentIds !== newIds) {
|
|
279
|
+
isSyncingFromExternal.current = true; // Mark that we're syncing from external
|
|
280
|
+
setState(s => ({ ...s, items: itemsFromExternal }));
|
|
281
|
+
}
|
|
282
|
+
}, [externalPinnedItemIds, contextMenuItems, enableConfigMode, flattenMenuItems]);
|
|
90
283
|
const togglePin = useCallback((item) => {
|
|
91
284
|
setState(s => {
|
|
92
|
-
const isInFloatingBar = s.items.some(i => i.id === item.id
|
|
285
|
+
const isInFloatingBar = s.items.some(i => i.id === item.id);
|
|
93
286
|
if (isInFloatingBar) {
|
|
94
287
|
// Remove from floating bar
|
|
95
|
-
const newItems = s.items.filter(i => i.id !== item.id
|
|
288
|
+
const newItems = s.items.filter(i => i.id !== item.id);
|
|
96
289
|
return { ...s, items: newItems };
|
|
97
290
|
}
|
|
98
291
|
else {
|
|
99
292
|
// Add to floating bar
|
|
100
293
|
if (s.items.length >= maxItems) {
|
|
101
|
-
|
|
102
|
-
|
|
294
|
+
ShowAlert({
|
|
295
|
+
mode: 'warning',
|
|
296
|
+
title: 'Limite Massimo Raggiunto',
|
|
297
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
298
|
+
duration: 4000,
|
|
299
|
+
});
|
|
103
300
|
return s;
|
|
104
301
|
}
|
|
105
302
|
return { ...s, items: [...s.items, item] };
|
|
106
303
|
}
|
|
107
304
|
});
|
|
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
305
|
}, [maxItems]);
|
|
120
306
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
121
|
-
const getCurrentItemState = useCallback((
|
|
307
|
+
const getCurrentItemState = useCallback((itemId) => {
|
|
122
308
|
const findInItems = (items) => {
|
|
123
|
-
for (
|
|
124
|
-
|
|
309
|
+
for (let i = 0; i < items.length; i++) {
|
|
310
|
+
const item = items[i];
|
|
311
|
+
// Match by ID if the item has one
|
|
312
|
+
if (item.id === itemId)
|
|
125
313
|
return item;
|
|
314
|
+
// Check in submenu
|
|
126
315
|
if (item.submenu) {
|
|
127
316
|
const found = findInItems(item.submenu);
|
|
128
317
|
if (found)
|
|
@@ -136,58 +325,92 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
136
325
|
disabled: foundItem?.disabled,
|
|
137
326
|
onClick: foundItem?.onClick
|
|
138
327
|
};
|
|
139
|
-
}, [contextMenuItems]);
|
|
140
|
-
|
|
328
|
+
}, [contextMenuItems]);
|
|
329
|
+
// Remove trailing separators from items array
|
|
330
|
+
const removeTrailingSeparators = useCallback((items) => {
|
|
331
|
+
const result = [...items];
|
|
332
|
+
while (result.length > 0 && result.at(-1)?.isSeparator) {
|
|
333
|
+
result.pop();
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}, []);
|
|
337
|
+
// Create a new separator item
|
|
338
|
+
const createSeparator = useCallback(() => {
|
|
339
|
+
const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
340
|
+
return {
|
|
341
|
+
id: separatorId,
|
|
342
|
+
name: 'Separator',
|
|
343
|
+
icon: _jsx(Separator, {}),
|
|
344
|
+
onClick: () => { },
|
|
345
|
+
isSeparator: true,
|
|
346
|
+
};
|
|
347
|
+
}, []);
|
|
348
|
+
// Add separator to items
|
|
349
|
+
const addSeparator = useCallback(() => {
|
|
350
|
+
if (state.items.length >= maxItems) {
|
|
351
|
+
ShowAlert({
|
|
352
|
+
mode: 'warning',
|
|
353
|
+
title: 'Limite Massimo Raggiunto',
|
|
354
|
+
message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
|
|
355
|
+
duration: 4000,
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const separator = createSeparator();
|
|
360
|
+
setState(s => ({ ...s, items: [...s.items, separator] }));
|
|
361
|
+
}, [state.items.length, maxItems, createSeparator]);
|
|
362
|
+
const getContextMenuItemsWithPinIcons = useCallback(() => {
|
|
141
363
|
const flatItems = flattenMenuItems(contextMenuItems);
|
|
142
|
-
// Calculate current pinned items directly from state.items (not refs)
|
|
143
364
|
const currentItemIds = new Set(state.items.map(i => i.id));
|
|
144
|
-
const
|
|
145
|
-
const enhanceItems = (items) => {
|
|
365
|
+
const addPinIcons = (items) => {
|
|
146
366
|
return items.map(item => {
|
|
147
|
-
const flatItem = flatItems.find(fi => fi.
|
|
148
|
-
const itemId = flatItem?.id || '';
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const enhanced = {
|
|
367
|
+
const flatItem = flatItems.find(fi => fi.id === item.id);
|
|
368
|
+
const itemId = flatItem?.id || item.id || '';
|
|
369
|
+
const isPinned = currentItemIds.has(itemId);
|
|
370
|
+
const itemWithPin = {
|
|
152
371
|
...item,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
372
|
+
rightIconProps: flatItem ? {
|
|
373
|
+
icon: _jsx(IconPin, {}),
|
|
374
|
+
activeColor: 'red',
|
|
375
|
+
inactiveColor: 'black',
|
|
376
|
+
isActive: isPinned,
|
|
377
|
+
onClick: () => togglePin(flatItem),
|
|
158
378
|
} : undefined,
|
|
159
379
|
};
|
|
160
380
|
if (item.submenu) {
|
|
161
|
-
|
|
381
|
+
itemWithPin.submenu = addPinIcons(item.submenu);
|
|
162
382
|
}
|
|
163
|
-
return
|
|
383
|
+
return itemWithPin;
|
|
164
384
|
});
|
|
165
385
|
};
|
|
166
|
-
return
|
|
386
|
+
return addPinIcons(contextMenuItems);
|
|
167
387
|
}, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
|
|
168
388
|
const handleMouseDown = (e) => {
|
|
169
|
-
if (state.isConfigMode)
|
|
170
|
-
return;
|
|
171
389
|
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
172
390
|
if (containerRect) {
|
|
173
391
|
// Calculate drag offset based on positioning mode
|
|
174
392
|
if (isConstrained) {
|
|
175
393
|
// For absolute positioning, offset is relative to container
|
|
176
394
|
dragOffset.current = {
|
|
177
|
-
x: e.clientX - containerRect.left -
|
|
178
|
-
y: e.clientY - containerRect.top -
|
|
395
|
+
x: e.clientX - containerRect.left - pixelPosition.x,
|
|
396
|
+
y: e.clientY - containerRect.top - pixelPosition.y,
|
|
179
397
|
};
|
|
180
398
|
}
|
|
181
399
|
else {
|
|
182
400
|
// For fixed positioning, offset is relative to viewport
|
|
183
401
|
dragOffset.current = {
|
|
184
|
-
x: e.clientX -
|
|
185
|
-
y: e.clientY -
|
|
402
|
+
x: e.clientX - pixelPosition.x,
|
|
403
|
+
y: e.clientY - pixelPosition.y,
|
|
186
404
|
};
|
|
187
405
|
}
|
|
188
406
|
}
|
|
407
|
+
// Save starting position to detect if user actually moved the bar
|
|
408
|
+
dragStartPosition.current = { ...pixelPosition };
|
|
189
409
|
setState(s => ({ ...s, isDragging: true }));
|
|
190
410
|
};
|
|
411
|
+
const handleGripDoubleClick = () => {
|
|
412
|
+
toggleOrientation();
|
|
413
|
+
};
|
|
191
414
|
const handleMouseMove = useCallback((e) => {
|
|
192
415
|
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
193
416
|
return;
|
|
@@ -210,50 +433,230 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
210
433
|
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
211
434
|
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
212
435
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
position: { x: newX, y: newY },
|
|
216
|
-
}));
|
|
436
|
+
// Update pixel position directly during drag
|
|
437
|
+
setPixelPosition({ x: newX, y: newY });
|
|
217
438
|
}, [state.isDragging, containerRef, isConstrained]);
|
|
218
439
|
const handleMouseUp = useCallback(() => {
|
|
219
|
-
|
|
220
|
-
|
|
440
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
441
|
+
// Convert final pixel position to percentage before updating state
|
|
442
|
+
const percentagePosition = {
|
|
443
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
444
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
445
|
+
};
|
|
446
|
+
// Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
|
|
447
|
+
if (dragStartPosition.current) {
|
|
448
|
+
const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
|
|
449
|
+
const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
|
|
450
|
+
if (dx > 5 || dy > 5) {
|
|
451
|
+
positionBeforeOrientationChange.current = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
dragStartPosition.current = null;
|
|
455
|
+
setState(s => ({
|
|
456
|
+
...s,
|
|
457
|
+
isDragging: false,
|
|
458
|
+
position: percentagePosition,
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
463
|
+
}
|
|
464
|
+
}, [state.isDragging, pixelPosition]);
|
|
465
|
+
// Touch event handlers for tablet support
|
|
466
|
+
const handleTouchStart = (e) => {
|
|
467
|
+
const touch = e.touches[0];
|
|
468
|
+
const containerRect = containerRef.current?.getBoundingClientRect();
|
|
469
|
+
if (containerRect) {
|
|
470
|
+
if (isConstrained) {
|
|
471
|
+
dragOffset.current = {
|
|
472
|
+
x: touch.clientX - containerRect.left - pixelPosition.x,
|
|
473
|
+
y: touch.clientY - containerRect.top - pixelPosition.y,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
dragOffset.current = {
|
|
478
|
+
x: touch.clientX - pixelPosition.x,
|
|
479
|
+
y: touch.clientY - pixelPosition.y,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Save starting position to detect if user actually moved the bar
|
|
484
|
+
dragStartPosition.current = { ...pixelPosition };
|
|
485
|
+
setState(s => ({ ...s, isDragging: true }));
|
|
486
|
+
};
|
|
487
|
+
const handleTouchMove = useCallback((e) => {
|
|
488
|
+
if (!state.isDragging || !containerRef.current || !floatingRef.current)
|
|
489
|
+
return;
|
|
490
|
+
const touch = e.touches[0];
|
|
491
|
+
const container = containerRef.current.getBoundingClientRect();
|
|
492
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
493
|
+
let newX, newY;
|
|
494
|
+
if (isConstrained) {
|
|
495
|
+
newX = touch.clientX - container.left - dragOffset.current.x;
|
|
496
|
+
newY = touch.clientY - container.top - dragOffset.current.y;
|
|
497
|
+
newX = Math.max(0, Math.min(newX, container.width - floating.width));
|
|
498
|
+
newY = Math.max(0, Math.min(newY, container.height - floating.height));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
newX = touch.clientX - dragOffset.current.x;
|
|
502
|
+
newY = touch.clientY - dragOffset.current.y;
|
|
503
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
|
|
504
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
|
|
505
|
+
}
|
|
506
|
+
setPixelPosition({ x: newX, y: newY });
|
|
507
|
+
}, [state.isDragging, containerRef, isConstrained]);
|
|
508
|
+
const handleTouchEnd = useCallback(() => {
|
|
509
|
+
if (state.isDragging && containerSizeRef.current.width > 0) {
|
|
510
|
+
const percentagePosition = {
|
|
511
|
+
x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
|
|
512
|
+
y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
|
|
513
|
+
};
|
|
514
|
+
// Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
|
|
515
|
+
if (dragStartPosition.current) {
|
|
516
|
+
const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
|
|
517
|
+
const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
|
|
518
|
+
if (dx > 5 || dy > 5) {
|
|
519
|
+
positionBeforeOrientationChange.current = null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
dragStartPosition.current = null;
|
|
523
|
+
setState(s => ({
|
|
524
|
+
...s,
|
|
525
|
+
isDragging: false,
|
|
526
|
+
position: percentagePosition,
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
setState(s => ({ ...s, isDragging: false }));
|
|
531
|
+
}
|
|
532
|
+
}, [state.isDragging, pixelPosition]);
|
|
221
533
|
useEffect(() => {
|
|
222
534
|
if (state.isDragging) {
|
|
223
535
|
document.addEventListener('mousemove', handleMouseMove);
|
|
224
536
|
document.addEventListener('mouseup', handleMouseUp);
|
|
537
|
+
document.addEventListener('touchmove', handleTouchMove);
|
|
538
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
225
539
|
return () => {
|
|
226
540
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
227
541
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
542
|
+
document.removeEventListener('touchmove', handleTouchMove);
|
|
543
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
228
544
|
};
|
|
229
545
|
}
|
|
230
546
|
return undefined;
|
|
231
|
-
}, [state.isDragging, handleMouseMove, handleMouseUp]);
|
|
232
|
-
// Save to
|
|
547
|
+
}, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
|
|
548
|
+
// Save to SDKUI_Globals.userSettings
|
|
233
549
|
useEffect(() => {
|
|
550
|
+
if (!enableConfigMode)
|
|
551
|
+
return; // Don't save if config mode is disabled
|
|
552
|
+
const pinnedIds = state.items.map(item => item.id);
|
|
234
553
|
try {
|
|
235
|
-
|
|
554
|
+
// Replace the entire object to trigger the Proxy
|
|
555
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
236
556
|
orientation: state.orientation,
|
|
237
|
-
|
|
238
|
-
|
|
557
|
+
itemIds: pinnedIds,
|
|
558
|
+
position: state.position,
|
|
559
|
+
positionFormat: 'percentage',
|
|
239
560
|
};
|
|
240
|
-
localStorage.setItem(storageKey, JSON.stringify(config));
|
|
241
561
|
}
|
|
242
562
|
catch (error) {
|
|
243
563
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
244
564
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
565
|
+
// Notify parent about pin changes only if not syncing from external
|
|
566
|
+
// This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
|
|
567
|
+
if (!isSyncingFromExternal.current) {
|
|
568
|
+
onPinChange?.(pinnedIds);
|
|
569
|
+
}
|
|
570
|
+
isSyncingFromExternal.current = false; // Reset the flag
|
|
571
|
+
}, [state.orientation, state.items, state.position, enableConfigMode, onPinChange]);
|
|
249
572
|
const toggleOrientation = () => {
|
|
250
573
|
const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
|
|
251
|
-
//
|
|
574
|
+
// When switching from vertical back to horizontal, restore the original position
|
|
575
|
+
// Use visibility hiding only for this case to prevent flicker during position restoration
|
|
576
|
+
if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
|
|
577
|
+
if (positionBeforeOrientationChange.current) {
|
|
578
|
+
setIsOrientationChanging(true); // Hide only when restoring position
|
|
579
|
+
const savedPosition = positionBeforeOrientationChange.current.position;
|
|
580
|
+
const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
|
|
581
|
+
setPixelPosition(savedPixelPosition);
|
|
582
|
+
setState(s => ({
|
|
583
|
+
...s,
|
|
584
|
+
orientation: newOrientation,
|
|
585
|
+
verticalDirection: 'down',
|
|
586
|
+
position: savedPosition,
|
|
587
|
+
}));
|
|
588
|
+
positionBeforeOrientationChange.current = null;
|
|
589
|
+
// Show the bar after the state has been applied
|
|
590
|
+
requestAnimationFrame(() => {
|
|
591
|
+
setIsOrientationChanging(false);
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// When switching to vertical, save current position and check if we need to expand upward
|
|
597
|
+
// Use opacity hiding (doesn't affect focus unlike visibility:hidden)
|
|
598
|
+
if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
|
|
599
|
+
setIsOrientationChanging(true);
|
|
600
|
+
// Save the current position before changing orientation
|
|
601
|
+
positionBeforeOrientationChange.current = {
|
|
602
|
+
position: { ...state.position },
|
|
603
|
+
pixelPosition: { ...pixelPosition },
|
|
604
|
+
};
|
|
605
|
+
if (floatingRef.current) {
|
|
606
|
+
const floating = floatingRef.current.getBoundingClientRect();
|
|
607
|
+
const containerHeight = isConstrained && containerRef.current
|
|
608
|
+
? containerRef.current.getBoundingClientRect().height
|
|
609
|
+
: window.innerHeight;
|
|
610
|
+
// Estimate vertical height (horizontal width becomes vertical height)
|
|
611
|
+
const estimatedVerticalHeight = floating.width;
|
|
612
|
+
if (estimatedVerticalHeight > containerHeight - 70) {
|
|
613
|
+
ShowAlert({
|
|
614
|
+
mode: 'warning',
|
|
615
|
+
title: 'Troppi elementi',
|
|
616
|
+
message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
|
|
617
|
+
duration: 4000,
|
|
618
|
+
});
|
|
619
|
+
positionBeforeOrientationChange.current = null; // Clear saved position since we're not changing
|
|
620
|
+
setIsOrientationChanging(false); // Show bar again since we're not changing
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
// Check if we're in the bottom part of the screen and don't have enough space below
|
|
624
|
+
const spaceBelow = containerHeight - floating.bottom;
|
|
625
|
+
const spaceAbove = floating.top;
|
|
626
|
+
const needsVerticalSpace = estimatedVerticalHeight - floating.height; // Additional space needed
|
|
627
|
+
// If not enough space below but enough space above, expand upward
|
|
628
|
+
if (spaceBelow < needsVerticalSpace && spaceAbove >= needsVerticalSpace) {
|
|
629
|
+
// Calculate the new Y position so the bottom of the bar stays at the same place
|
|
630
|
+
const currentBottom = pixelPosition.y + floating.height;
|
|
631
|
+
const newPixelY = currentBottom - estimatedVerticalHeight;
|
|
632
|
+
setState(s => ({
|
|
633
|
+
...s,
|
|
634
|
+
orientation: newOrientation,
|
|
635
|
+
verticalDirection: 'up',
|
|
636
|
+
}));
|
|
637
|
+
// Update pixel position and percentage after orientation change
|
|
638
|
+
requestAnimationFrame(() => {
|
|
639
|
+
requestAnimationFrame(() => {
|
|
640
|
+
const newY = Math.max(0, newPixelY);
|
|
641
|
+
setPixelPosition(prev => ({ ...prev, y: newY }));
|
|
642
|
+
const newPercentY = pixelsToPercent(newY, containerHeight);
|
|
643
|
+
setState(s => ({
|
|
644
|
+
...s,
|
|
645
|
+
position: { ...s.position, y: newPercentY },
|
|
646
|
+
}));
|
|
647
|
+
setIsOrientationChanging(false);
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Default case: just change orientation without special positioning
|
|
252
655
|
setState(s => ({
|
|
253
656
|
...s,
|
|
254
657
|
orientation: newOrientation,
|
|
658
|
+
verticalDirection: 'down',
|
|
255
659
|
}));
|
|
256
|
-
// Then, after DOM updates, adjust position to stay in bounds
|
|
257
660
|
requestAnimationFrame(() => {
|
|
258
661
|
requestAnimationFrame(() => {
|
|
259
662
|
if (containerRef.current && floatingRef.current) {
|
|
@@ -278,31 +681,97 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
278
681
|
}));
|
|
279
682
|
}
|
|
280
683
|
}
|
|
684
|
+
setIsOrientationChanging(false);
|
|
281
685
|
});
|
|
282
686
|
});
|
|
283
687
|
};
|
|
284
688
|
const removeItem = (itemId) => {
|
|
689
|
+
isLocalChange.current = true;
|
|
285
690
|
setState(s => ({
|
|
286
691
|
...s,
|
|
287
692
|
items: s.items.filter(item => item.id !== itemId),
|
|
288
693
|
}));
|
|
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
694
|
};
|
|
695
|
+
const getItemRightClickMenuItems = useCallback((item, index) => {
|
|
696
|
+
const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
|
|
697
|
+
const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
|
|
698
|
+
return [
|
|
699
|
+
{
|
|
700
|
+
name: 'Rimuovi',
|
|
701
|
+
icon: _jsx(IconDelete, { fontSize: 16 }),
|
|
702
|
+
onClick: () => removeItem(item.id),
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
name: 'Aggiungi separatore a sinistra',
|
|
706
|
+
icon: _jsx(IconSeparator, { fontSize: 16 }),
|
|
707
|
+
disabled: hasSeparatorOnLeft,
|
|
708
|
+
onClick: () => {
|
|
709
|
+
isLocalChange.current = true;
|
|
710
|
+
const separator = createSeparator();
|
|
711
|
+
setState(s => {
|
|
712
|
+
const newItems = [...s.items];
|
|
713
|
+
newItems.splice(index, 0, separator);
|
|
714
|
+
return { ...s, items: newItems };
|
|
715
|
+
});
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: 'Aggiungi separatore a destra',
|
|
720
|
+
icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
|
|
721
|
+
disabled: hasSeparatorOnRight,
|
|
722
|
+
onClick: () => {
|
|
723
|
+
isLocalChange.current = true;
|
|
724
|
+
const separator = createSeparator();
|
|
725
|
+
setState(s => {
|
|
726
|
+
const newItems = [...s.items];
|
|
727
|
+
newItems.splice(index + 1, 0, separator);
|
|
728
|
+
return { ...s, items: newItems };
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
beginGroup: true,
|
|
734
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
735
|
+
icon: _jsx(IconRotate, { fontSize: 16 }),
|
|
736
|
+
onClick: toggleOrientation,
|
|
737
|
+
},
|
|
738
|
+
];
|
|
739
|
+
}, [state.items, state.orientation, removeItem, createSeparator, toggleOrientation]);
|
|
740
|
+
const getSeparatorRightClickMenuItems = useCallback((index) => {
|
|
741
|
+
return [
|
|
742
|
+
{
|
|
743
|
+
name: 'Rimuovi',
|
|
744
|
+
icon: _jsx(IconDelete, { fontSize: 16 }),
|
|
745
|
+
onClick: () => removeItem(state.items[index].id),
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
name: 'Aggiungi separatore a sinistra',
|
|
749
|
+
icon: _jsx(IconSeparator, { fontSize: 16 }),
|
|
750
|
+
disabled: true,
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: 'Aggiungi separatore a destra',
|
|
754
|
+
icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
|
|
755
|
+
disabled: true,
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
beginGroup: true,
|
|
759
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
760
|
+
icon: _jsx(IconRotate, { fontSize: 16 }),
|
|
761
|
+
onClick: toggleOrientation,
|
|
762
|
+
},
|
|
763
|
+
];
|
|
764
|
+
}, [state.items, state.orientation, removeItem, toggleOrientation]);
|
|
296
765
|
// Drag and drop for reordering
|
|
297
766
|
const handleDragStart = (e, index) => {
|
|
298
|
-
if (!
|
|
767
|
+
if (!enableConfigMode)
|
|
299
768
|
return;
|
|
300
769
|
e.dataTransfer.effectAllowed = 'move';
|
|
301
770
|
e.dataTransfer.setData('text/plain', index.toString());
|
|
302
771
|
setState(s => ({ ...s, draggedItemIndex: index }));
|
|
303
772
|
};
|
|
304
773
|
const handleDragEnter = (e, index) => {
|
|
305
|
-
if (!
|
|
774
|
+
if (!enableConfigMode)
|
|
306
775
|
return;
|
|
307
776
|
e.preventDefault();
|
|
308
777
|
if (state.draggedItemIndex !== index) {
|
|
@@ -310,13 +779,13 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
310
779
|
}
|
|
311
780
|
};
|
|
312
781
|
const handleDragOver = (e) => {
|
|
313
|
-
if (!
|
|
782
|
+
if (!enableConfigMode)
|
|
314
783
|
return;
|
|
315
784
|
e.preventDefault();
|
|
316
785
|
e.dataTransfer.dropEffect = 'move';
|
|
317
786
|
};
|
|
318
787
|
const handleDragLeave = (_e, index) => {
|
|
319
|
-
if (!
|
|
788
|
+
if (!enableConfigMode)
|
|
320
789
|
return;
|
|
321
790
|
// Only clear if we're actually leaving this specific item
|
|
322
791
|
if (dragOverIndex === index) {
|
|
@@ -324,7 +793,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
324
793
|
}
|
|
325
794
|
};
|
|
326
795
|
const handleDrop = (e, dropIndex) => {
|
|
327
|
-
if (!
|
|
796
|
+
if (!enableConfigMode || state.draggedItemIndex === null)
|
|
328
797
|
return;
|
|
329
798
|
e.preventDefault();
|
|
330
799
|
e.stopPropagation();
|
|
@@ -333,6 +802,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
333
802
|
setDragOverIndex(null);
|
|
334
803
|
return;
|
|
335
804
|
}
|
|
805
|
+
isLocalChange.current = true;
|
|
336
806
|
const newItems = [...state.items];
|
|
337
807
|
const [draggedItem] = newItems.splice(dragIndex, 1);
|
|
338
808
|
newItems.splice(dropIndex, 0, draggedItem);
|
|
@@ -347,24 +817,37 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
|
|
|
347
817
|
setState(s => ({ ...s, draggedItemIndex: null }));
|
|
348
818
|
setDragOverIndex(null);
|
|
349
819
|
};
|
|
350
|
-
return (_jsxs(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
820
|
+
return (_jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": false, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: undefined, children: [_jsx(ContextMenu, { items: [
|
|
821
|
+
{
|
|
822
|
+
name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
|
|
823
|
+
icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
|
|
824
|
+
onClick: toggleOrientation,
|
|
825
|
+
},
|
|
826
|
+
], trigger: "right", children: _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) => {
|
|
827
|
+
// Handle separator items
|
|
828
|
+
if (item.isSeparator) {
|
|
829
|
+
return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: enableConfigMode, 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: enableConfigMode ? (_jsx(ContextMenu, { items: getSeparatorRightClickMenuItems(index), trigger: "right", children: _jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": false }) })) : (_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": false })) }, item.id));
|
|
830
|
+
}
|
|
831
|
+
// Get current state (disabled and onClick) from contextMenuItems
|
|
832
|
+
const currentState = getCurrentItemState(item.id);
|
|
833
|
+
// Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
|
|
834
|
+
const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
|
|
835
|
+
? currentState.disabled === true
|
|
836
|
+
: item.disabled === true;
|
|
837
|
+
const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
|
|
838
|
+
return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: enableConfigMode, 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: enableConfigMode ? (_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: () => {
|
|
839
|
+
if (isDisabled)
|
|
840
|
+
return;
|
|
841
|
+
if (currentOnClick) {
|
|
842
|
+
currentOnClick();
|
|
843
|
+
}
|
|
844
|
+
}, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })) })) : (item.staticItem ? (_jsx("div", { children: item.staticItem })) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
|
|
845
|
+
if (isDisabled)
|
|
846
|
+
return;
|
|
847
|
+
if (currentOnClick) {
|
|
848
|
+
currentOnClick();
|
|
849
|
+
}
|
|
850
|
+
}, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) }))) }, item.id));
|
|
851
|
+
}), enableConfigMode && hasContextMenu && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) }))] }));
|
|
369
852
|
};
|
|
370
853
|
export default TMFloatingMenuBar;
|