@vc-shell/framework 1.1.0-alpha.4 → 1.1.0-alpha.6
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/core/composables/useMenuService/index.ts +20 -110
- package/core/composables/useWidgets/index.ts +2 -1
- package/core/plugins/modularity/index.ts +3 -3
- package/core/services/menu-service.ts +195 -0
- package/core/services/widget-service.ts +20 -0
- package/core/types/index.ts +1 -1
- package/dist/core/composables/useMenuService/index.d.ts +4 -10
- package/dist/core/composables/useMenuService/index.d.ts.map +1 -1
- package/dist/core/composables/useWidgets/index.d.ts +2 -1
- package/dist/core/composables/useWidgets/index.d.ts.map +1 -1
- package/dist/core/plugins/modularity/index.d.ts.map +1 -1
- package/dist/core/services/menu-service.d.ts +17 -0
- package/dist/core/services/menu-service.d.ts.map +1 -0
- package/dist/core/services/widget-service.d.ts +4 -0
- package/dist/core/services/widget-service.d.ts.map +1 -1
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/framework.js +220 -210
- package/dist/{index-CrxFDC2b.js → index-3ySdd-mG.js} +1 -1
- package/dist/{index-B1YR_MYV.js → index-B-nvqNbp.js} +1 -1
- package/dist/{index-xLYzNPa7.js → index-BQF2-UMe.js} +1 -1
- package/dist/{index-BBYyHeYA.js → index-BXlxP2d2.js} +1 -1
- package/dist/{index-Cf9Tz1ql.js → index-C7P-aBjd.js} +1 -1
- package/dist/{index-8LELHzw9.js → index-CO_2IshF.js} +1 -1
- package/dist/{index-BA98L1jI.js → index-CfyFpaKq.js} +1 -1
- package/dist/{index-DVljTjbf.js → index-Ci23AX3j.js} +1 -1
- package/dist/{index-D1JchciU.js → index-CyuFXG83.js} +1 -1
- package/dist/{index-CWKrD2Cd.js → index-D1rpRTKf.js} +1 -1
- package/dist/{index-BAeTsi-X.js → index-DLxTAT7x.js} +1 -1
- package/dist/{index-BuO5ByG9.js → index-DOVhosAY.js} +1 -1
- package/dist/{index-DLtsQ_PJ.js → index-DZAq0B3U.js} +23780 -23339
- package/dist/{index-BrUitdDo.js → index-DtkJ7xTB.js} +1 -1
- package/dist/{index-9lJxZE5w.js → index-DvGVm1rK.js} +1 -1
- package/dist/{index-RwX3kiZh.js → index-EDF1MDtU.js} +1 -1
- package/dist/{index-CJ5I7vTn.js → index-LjqdX6jw.js} +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/injection-keys.d.ts +2 -0
- package/dist/injection-keys.d.ts.map +1 -1
- package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts +2 -0
- package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts.map +1 -1
- package/dist/shared/components/draggable-dashboard/composables/useCellSizeCalculator.d.ts +25 -0
- package/dist/shared/components/draggable-dashboard/composables/useCellSizeCalculator.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useCollisionDetection.d.ts +27 -0
- package/dist/shared/components/draggable-dashboard/composables/useCollisionDetection.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts +22 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts.map +1 -1
- package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts +12 -4
- package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts.map +1 -1
- package/dist/shared/components/draggable-dashboard/composables/useDragClone.d.ts +15 -0
- package/dist/shared/components/draggable-dashboard/composables/useDragClone.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useEventCoordinates.d.ts +33 -0
- package/dist/shared/components/draggable-dashboard/composables/useEventCoordinates.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useGridPosition.d.ts +57 -0
- package/dist/shared/components/draggable-dashboard/composables/useGridPosition.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useGridSystem.d.ts +22 -0
- package/dist/shared/components/draggable-dashboard/composables/useGridSystem.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useLayoutPersistence.d.ts +19 -0
- package/dist/shared/components/draggable-dashboard/composables/useLayoutPersistence.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useResizeObserver.d.ts +18 -0
- package/dist/shared/components/draggable-dashboard/composables/useResizeObserver.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useWidgetLayout.d.ts +14 -0
- package/dist/shared/components/draggable-dashboard/composables/useWidgetLayout.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useWidgetStyles.d.ts +21 -0
- package/dist/shared/components/draggable-dashboard/composables/useWidgetStyles.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/types.d.ts +5 -1
- package/dist/shared/components/draggable-dashboard/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/OffersIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/OffersIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/OrdersIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/OrdersIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/PeopleIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/PeopleIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/ProductsIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/ProductsIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/ProfileIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/ProfileIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/index.d.ts +6 -0
- package/dist/ui/components/atoms/vc-icon/icons/index.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue.d.ts +1 -0
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts +2 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/vc-table.vue.d.ts.map +1 -1
- package/package.json +4 -4
- package/shared/components/draggable-dashboard/DraggableDashboard.vue +114 -148
- package/shared/components/draggable-dashboard/composables/useCellSizeCalculator.ts +121 -0
- package/shared/components/draggable-dashboard/composables/useCollisionDetection.ts +219 -0
- package/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.ts +126 -331
- package/shared/components/draggable-dashboard/composables/useDashboardGrid.ts +74 -220
- package/shared/components/draggable-dashboard/composables/useDragClone.ts +97 -0
- package/shared/components/draggable-dashboard/composables/useEventCoordinates.ts +91 -0
- package/shared/components/draggable-dashboard/composables/useGridPosition.ts +150 -0
- package/shared/components/draggable-dashboard/composables/useGridSystem.ts +169 -0
- package/shared/components/draggable-dashboard/composables/useLayoutPersistence.ts +89 -0
- package/shared/components/draggable-dashboard/composables/useResizeObserver.ts +105 -0
- package/shared/components/draggable-dashboard/composables/useWidgetLayout.ts +264 -0
- package/shared/components/draggable-dashboard/composables/useWidgetStyles.ts +120 -0
- package/shared/components/draggable-dashboard/types.ts +6 -1
- package/shared/components/notification-dropdown/notification-dropdown.vue +1 -0
- package/ui/components/atoms/vc-icon/icons/FulfillmentCentersIcon.vue +27 -0
- package/ui/components/atoms/vc-icon/icons/OffersIcon.vue +23 -0
- package/ui/components/atoms/vc-icon/icons/OrdersIcon.vue +19 -0
- package/ui/components/atoms/vc-icon/icons/PeopleIcon.vue +21 -0
- package/ui/components/atoms/vc-icon/icons/ProductsIcon.vue +23 -0
- package/ui/components/atoms/vc-icon/icons/ProfileIcon.vue +18 -0
- package/ui/components/atoms/vc-icon/icons/index.ts +6 -0
- package/ui/components/atoms/vc-icon/vc-icon.vue +101 -82
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/components/app-bar-button/app-bar-button.vue +10 -3
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +3 -4
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue +2 -2
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +1 -0
- package/ui/components/organisms/vc-app/vc-app.vue +2 -3
- package/ui/components/organisms/vc-table/vc-table.vue +4 -1
|
@@ -1,250 +1,104 @@
|
|
|
1
|
-
import { computed
|
|
2
|
-
import type { IDashboardWidget, DashboardWidgetPosition
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
|
|
3
3
|
import { useDashboard } from "../../../../core/composables/useDashboard";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ dx: 1, dy: 0 }, // Right
|
|
19
|
-
{ dx: 0, dy: -1 }, // Up
|
|
20
|
-
{ dx: -1, dy: 0 }, // Left
|
|
21
|
-
{ dx: 1, dy: 1 }, // Right-down
|
|
22
|
-
{ dx: -1, dy: 1 }, // Left-down
|
|
23
|
-
{ dx: 1, dy: -1 }, // Right-up
|
|
24
|
-
{ dx: -1, dy: -1 }, // Left-up
|
|
25
|
-
];
|
|
26
|
-
|
|
4
|
+
import { useLayoutPersistence } from "./useLayoutPersistence";
|
|
5
|
+
import { useGridSystem } from "./useGridSystem";
|
|
6
|
+
import { useWidgetLayout } from "./useWidgetLayout";
|
|
7
|
+
|
|
8
|
+
// LocalStorage key for dashboard layout
|
|
9
|
+
const DASHBOARD_LAYOUT_KEY = "vc-dashboard-layout";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main composable for managing the dashboard
|
|
13
|
+
*
|
|
14
|
+
* Combines functionality for working with the grid, widget placement, and layout persistence
|
|
15
|
+
*
|
|
16
|
+
* @returns An object with functions for working with the dashboard
|
|
17
|
+
*/
|
|
27
18
|
export function useDashboardGrid() {
|
|
19
|
+
// Dashboard service
|
|
28
20
|
const dashboard = useDashboard();
|
|
21
|
+
|
|
22
|
+
// Reactive properties of widgets and layout
|
|
29
23
|
const widgets = computed(() => dashboard.getWidgets());
|
|
30
24
|
const layout = computed(() => dashboard.getLayout());
|
|
31
25
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
if (widgets.value.length === 0) return MIN_GRID_ROWS;
|
|
26
|
+
// Initialize subsystems
|
|
27
|
+
const grid = useGridSystem();
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
...Array.from(layout.value.values()).map((pos: DashboardWidgetPosition, index) => {
|
|
38
|
-
const widget = widgets.value.find((w) => w.id === Array.from(layout.value.keys())[index]);
|
|
39
|
-
return widget ? pos.y + widget.size.height : pos.y + 1;
|
|
40
|
-
}),
|
|
41
|
-
0,
|
|
42
|
-
);
|
|
29
|
+
const widgetLayout = useWidgetLayout(dashboard.updateWidgetPosition);
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return dynamicRows.value;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Get current number of rows
|
|
50
|
-
const getGridRows = () => currentRows.value;
|
|
51
|
-
|
|
52
|
-
// Improved collision detection function
|
|
53
|
-
const hasCollision = (
|
|
54
|
-
widget: IDashboardWidget,
|
|
55
|
-
position: DashboardWidgetPosition,
|
|
56
|
-
excludeWidgetId?: string,
|
|
57
|
-
): boolean => {
|
|
58
|
-
// Check only bottom grid boundary
|
|
59
|
-
if (position.x < 0 || position.x + widget.size.width > GRID_COLUMNS || position.y < 0) {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Create occupied cells map for optimization
|
|
64
|
-
const occupiedCells = new Set<string>();
|
|
65
|
-
|
|
66
|
-
// Fill occupied cells map
|
|
67
|
-
widgets.value.forEach((other) => {
|
|
68
|
-
if (other.id === widget.id || (excludeWidgetId && other.id === excludeWidgetId)) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const otherPos = layout.value.get(other.id);
|
|
73
|
-
if (!otherPos) return;
|
|
74
|
-
|
|
75
|
-
for (let x = otherPos.x; x < otherPos.x + other.size.width; x++) {
|
|
76
|
-
for (let y = otherPos.y; y < otherPos.y + other.size.height; y++) {
|
|
77
|
-
occupiedCells.add(`${x},${y}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
31
|
+
// Hook for working with localStorage
|
|
32
|
+
const persistence = useLayoutPersistence(DASHBOARD_LAYOUT_KEY, dashboard.updateWidgetPosition);
|
|
81
33
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return false;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the current number of rows in the grid
|
|
36
|
+
*/
|
|
37
|
+
const getGridRows = (): number => {
|
|
38
|
+
return grid.updateGridRows(widgets.value, layout.value);
|
|
92
39
|
};
|
|
93
40
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return currentPos;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Start search from top-left corner or current position
|
|
104
|
-
const startX = currentPos?.x || 0;
|
|
105
|
-
const startY = currentPos?.y || 0;
|
|
106
|
-
|
|
107
|
-
// Spiral search for free position
|
|
108
|
-
let layer = 0;
|
|
109
|
-
const maxLayers = Math.max(GRID_COLUMNS, currentRows.value);
|
|
110
|
-
|
|
111
|
-
while (layer < maxLayers) {
|
|
112
|
-
// Check all directions in current layer
|
|
113
|
-
for (const { dx, dy } of SEARCH_DIRECTIONS) {
|
|
114
|
-
const x = startX + dx * layer;
|
|
115
|
-
const y = startY + dy * layer;
|
|
116
|
-
|
|
117
|
-
const position = { x, y };
|
|
118
|
-
|
|
119
|
-
// Check position
|
|
120
|
-
if (!hasCollision(widget, position, widget.id)) {
|
|
121
|
-
return position;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
layer++;
|
|
126
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* Saves the current layout to localStorage
|
|
43
|
+
*/
|
|
44
|
+
const saveLayoutToLocalStorage = (): void => {
|
|
45
|
+
persistence.saveLayout(layout.value);
|
|
46
|
+
};
|
|
127
47
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Loads the layout from localStorage
|
|
50
|
+
*
|
|
51
|
+
* @returns true if the layout was successfully loaded
|
|
52
|
+
*/
|
|
53
|
+
const loadLayoutFromLocalStorage = (): boolean => {
|
|
54
|
+
return persistence.loadLayout(widgets.value);
|
|
133
55
|
};
|
|
134
56
|
|
|
135
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Arranges widgets in rows
|
|
59
|
+
*
|
|
60
|
+
* @param widgetsToArrange An array of widgets to arrange
|
|
61
|
+
*/
|
|
136
62
|
const arrangeWidgetsInRows = (widgetsToArrange: IDashboardWidget[]): void => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const sortedWidgets = [...widgetsToArrange].sort((a, b) => {
|
|
140
|
-
// Sort by size (largest first)
|
|
141
|
-
const aSize = a.size.width * a.size.height;
|
|
142
|
-
const bSize = b.size.width * b.size.height;
|
|
143
|
-
return bSize - aSize;
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Current position for placement
|
|
147
|
-
let currentX = 0;
|
|
148
|
-
let currentY = 0;
|
|
149
|
-
let rowHeight = 0;
|
|
150
|
-
|
|
151
|
-
sortedWidgets.forEach((widget) => {
|
|
152
|
-
// Check if widget fits in current row
|
|
153
|
-
if (currentX + widget.size.width > GRID_COLUMNS) {
|
|
154
|
-
// If it doesn't fit, move to the next row
|
|
155
|
-
currentX = 0;
|
|
156
|
-
currentY += rowHeight;
|
|
157
|
-
rowHeight = 0;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Place widget on current position
|
|
161
|
-
const position = { x: currentX, y: currentY };
|
|
162
|
-
dashboard.updateWidgetPosition(widget.id, position);
|
|
163
|
-
|
|
164
|
-
// Update current position
|
|
165
|
-
currentX += widget.size.width;
|
|
166
|
-
|
|
167
|
-
// Update maximum height for current row
|
|
168
|
-
rowHeight = Math.max(rowHeight, widget.size.height);
|
|
169
|
-
});
|
|
63
|
+
widgetLayout.arrangeWidgetsInRows(widgetsToArrange, widgets.value, layout.value);
|
|
170
64
|
};
|
|
171
65
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Check if all widgets have a position
|
|
183
|
-
const hasUnpositionedWidgets = widgets.value.some((widget: IDashboardWidget) => !layout.value.has(widget.id));
|
|
184
|
-
|
|
185
|
-
// If there are widgets without a position, then rearrange
|
|
186
|
-
if (hasUnpositionedWidgets) {
|
|
187
|
-
arrangeWidgetsInRows(widgets.value);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Check for collisions
|
|
192
|
-
const hasCollisions = widgets.value.some((widget: IDashboardWidget) => {
|
|
193
|
-
const pos = layout.value.get(widget.id);
|
|
194
|
-
return pos && hasCollision(widget, pos, widget.id);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// If there are collisions, then rearrange
|
|
198
|
-
if (hasCollisions) {
|
|
199
|
-
arrangeWidgetsInRows(widgets.value);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check if widgets are compact
|
|
204
|
-
const maxY = Math.max(...Array.from(layout.value.values()).map((pos: DashboardWidgetPosition) => pos.y + 1), 0);
|
|
205
|
-
|
|
206
|
-
// If widgets occupy many rows but are not fully occupied, then consider this placement suboptimal
|
|
207
|
-
const totalCells = GRID_COLUMNS * maxY;
|
|
208
|
-
const occupiedCells = widgets.value.reduce((sum: number, widget: IDashboardWidget) => {
|
|
209
|
-
return sum + widget.size.width * widget.size.height;
|
|
210
|
-
}, 0);
|
|
66
|
+
/**
|
|
67
|
+
* Initializes widgets with their built-in positions
|
|
68
|
+
*
|
|
69
|
+
* @returns true if at least one widget had a built-in position
|
|
70
|
+
*/
|
|
71
|
+
const initializeWithBuiltInPositions = (): boolean => {
|
|
72
|
+
return widgetLayout.initializeWithBuiltInPositions(widgets.value, layout.value);
|
|
73
|
+
};
|
|
211
74
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Initializes the initial positions of widgets
|
|
77
|
+
*/
|
|
78
|
+
const initializeLayout = (): void => {
|
|
79
|
+
// Priority 1: Loading from localStorage
|
|
80
|
+
const layoutLoadedFromStorage = loadLayoutFromLocalStorage();
|
|
218
81
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
const posB = layout.value.get(b.id);
|
|
223
|
-
if (!posA && !posB) return 0;
|
|
224
|
-
if (!posA) return 1;
|
|
225
|
-
if (!posB) return -1;
|
|
226
|
-
return posA.y - posB.y || posA.x - posB.x;
|
|
227
|
-
});
|
|
82
|
+
// Priority 2: Using built-in widget positions
|
|
83
|
+
if (!layoutLoadedFromStorage) {
|
|
84
|
+
const usedBuiltInPositions = initializeWithBuiltInPositions();
|
|
228
85
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!currentPos) {
|
|
233
|
-
const freePosition = findFreePosition(widget);
|
|
234
|
-
dashboard.updateWidgetPosition(widget.id, freePosition);
|
|
235
|
-
} else if (hasCollision(widget, currentPos, widget.id)) {
|
|
236
|
-
const freePosition = findFreePosition(widget);
|
|
237
|
-
dashboard.updateWidgetPosition(widget.id, freePosition);
|
|
86
|
+
// Priority 3: Standard initialization
|
|
87
|
+
if (!usedBuiltInPositions) {
|
|
88
|
+
arrangeWidgetsInRows(widgets.value);
|
|
238
89
|
}
|
|
239
|
-
}
|
|
90
|
+
}
|
|
240
91
|
};
|
|
241
92
|
|
|
242
93
|
return {
|
|
243
94
|
widgets,
|
|
244
95
|
layout,
|
|
96
|
+
GRID_COLUMNS: grid.GRID_COLUMNS,
|
|
97
|
+
getGridRows,
|
|
98
|
+
saveLayoutToLocalStorage,
|
|
99
|
+
loadLayoutFromLocalStorage,
|
|
245
100
|
arrangeWidgetsInRows,
|
|
101
|
+
initializeWithBuiltInPositions,
|
|
246
102
|
initializeLayout,
|
|
247
|
-
GRID_COLUMNS,
|
|
248
|
-
getGridRows,
|
|
249
103
|
};
|
|
250
104
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composible for managing a widget clone during dragging
|
|
5
|
+
*
|
|
6
|
+
* Provides functions for creating, updating, and deleting a widget clone
|
|
7
|
+
* during dragging, as well as for animating the transition
|
|
8
|
+
*
|
|
9
|
+
* @returns An object with functions for working with the widget clone
|
|
10
|
+
*/
|
|
11
|
+
export function useDragClone() {
|
|
12
|
+
// Reference to the DOM element of the clone
|
|
13
|
+
const dragClone = ref<HTMLElement | null>(null);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a widget clone for dragging
|
|
17
|
+
*
|
|
18
|
+
* @param element The HTML element from which dragging starts
|
|
19
|
+
* @returns The DOM element of the clone or null if the clone was not created
|
|
20
|
+
*/
|
|
21
|
+
const createDragClone = (element: HTMLElement) => {
|
|
22
|
+
// Find the parent widget element
|
|
23
|
+
const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
|
|
24
|
+
if (!widgetElement) return null;
|
|
25
|
+
|
|
26
|
+
const clone = widgetElement.cloneNode(true) as HTMLElement;
|
|
27
|
+
const rect = widgetElement.getBoundingClientRect();
|
|
28
|
+
|
|
29
|
+
// Consider the page scroll
|
|
30
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
31
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
32
|
+
|
|
33
|
+
// Copy all computed styles from the original
|
|
34
|
+
const computedStyle = window.getComputedStyle(widgetElement);
|
|
35
|
+
|
|
36
|
+
clone.style.position = "fixed";
|
|
37
|
+
clone.style.width = `${rect.width}px`;
|
|
38
|
+
clone.style.height = `${rect.height}px`;
|
|
39
|
+
clone.style.left = `${rect.left + scrollX}px`;
|
|
40
|
+
clone.style.top = `${rect.top + scrollY}px`;
|
|
41
|
+
clone.style.zIndex = "9999";
|
|
42
|
+
clone.style.pointerEvents = "none";
|
|
43
|
+
clone.style.opacity = "0.95";
|
|
44
|
+
clone.style.transform = "scale(1.02)";
|
|
45
|
+
clone.style.transition = "transform 0.1s ease, opacity 0.2s ease";
|
|
46
|
+
clone.style.boxShadow = "0 10px 25px rgba(0,0,0,0.15)";
|
|
47
|
+
clone.style.willChange = "transform";
|
|
48
|
+
|
|
49
|
+
// Copy important styles from the original
|
|
50
|
+
clone.style.backgroundColor = computedStyle.backgroundColor;
|
|
51
|
+
clone.style.borderRadius = computedStyle.borderRadius;
|
|
52
|
+
|
|
53
|
+
// Add a class for styling the clone
|
|
54
|
+
clone.classList.add("dashboard-widget-clone");
|
|
55
|
+
|
|
56
|
+
// Animation of appearance
|
|
57
|
+
requestAnimationFrame(() => {
|
|
58
|
+
clone.style.transform = "scale(1.02)";
|
|
59
|
+
clone.style.opacity = "0.92";
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
document.body.appendChild(clone);
|
|
63
|
+
dragClone.value = clone;
|
|
64
|
+
return clone;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Updates the position of the clone during dragging
|
|
69
|
+
*
|
|
70
|
+
* @param deltaX The offset along the X axis
|
|
71
|
+
* @param deltaY The offset along the Y axis
|
|
72
|
+
*/
|
|
73
|
+
const updateDragClonePosition = (deltaX: number, deltaY: number) => {
|
|
74
|
+
if (!dragClone.value) return;
|
|
75
|
+
|
|
76
|
+
dragClone.value.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Removes the widget clone
|
|
81
|
+
*/
|
|
82
|
+
const removeDragClone = () => {
|
|
83
|
+
if (!dragClone.value) return;
|
|
84
|
+
|
|
85
|
+
if (dragClone.value.parentNode) {
|
|
86
|
+
document.body.removeChild(dragClone.value);
|
|
87
|
+
}
|
|
88
|
+
dragClone.value = null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
dragClone,
|
|
93
|
+
createDragClone,
|
|
94
|
+
updateDragClonePosition,
|
|
95
|
+
removeDragClone,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for event coordinates
|
|
5
|
+
*/
|
|
6
|
+
export interface EventCoordinates {
|
|
7
|
+
clientX: number;
|
|
8
|
+
clientY: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Composible for processing event coordinates
|
|
13
|
+
*
|
|
14
|
+
* Provides functions for getting coordinates from different types of events,
|
|
15
|
+
* tracking the initial position, and calculating the offset
|
|
16
|
+
*
|
|
17
|
+
* @returns An object with functions for working with event coordinates
|
|
18
|
+
*/
|
|
19
|
+
export function useEventCoordinates() {
|
|
20
|
+
// Initial position of the mouse/touch
|
|
21
|
+
const initialPosition = ref<EventCoordinates>({ clientX: 0, clientY: 0 });
|
|
22
|
+
|
|
23
|
+
// Flag indicating whether the device is touch-enabled
|
|
24
|
+
const isTouchDevice = ref(false);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets coordinates from a mouse or touch event
|
|
28
|
+
*
|
|
29
|
+
* @param event The mouse or touch event
|
|
30
|
+
* @returns An object with coordinates clientX and clientY
|
|
31
|
+
*/
|
|
32
|
+
const getEventCoordinates = (event: MouseEvent | TouchEvent): EventCoordinates => {
|
|
33
|
+
if ("touches" in event) {
|
|
34
|
+
// Touch event
|
|
35
|
+
isTouchDevice.value = true;
|
|
36
|
+
return {
|
|
37
|
+
clientX: event.touches[0].clientX,
|
|
38
|
+
clientY: event.touches[0].clientY,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Mouse event
|
|
42
|
+
isTouchDevice.value = false;
|
|
43
|
+
return {
|
|
44
|
+
clientX: (event as MouseEvent).clientX,
|
|
45
|
+
clientY: (event as MouseEvent).clientY,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Saves the initial position of the event
|
|
51
|
+
*
|
|
52
|
+
* @param event The mouse or touch event
|
|
53
|
+
*/
|
|
54
|
+
const saveInitialPosition = (event: MouseEvent | TouchEvent) => {
|
|
55
|
+
initialPosition.value = getEventCoordinates(event);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Calculates the offset from the initial position
|
|
60
|
+
*
|
|
61
|
+
* @param currentCoordinates The current coordinates
|
|
62
|
+
* @returns An object with the offsets along the X and Y axes
|
|
63
|
+
*/
|
|
64
|
+
const calculateDelta = (currentCoordinates: EventCoordinates) => {
|
|
65
|
+
return {
|
|
66
|
+
deltaX: currentCoordinates.clientX - initialPosition.value.clientX,
|
|
67
|
+
deltaY: currentCoordinates.clientY - initialPosition.value.clientY,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks if the offset from the initial position exceeds the specified threshold
|
|
73
|
+
*
|
|
74
|
+
* @param currentCoordinates The current coordinates
|
|
75
|
+
* @param threshold The threshold of the offset in pixels
|
|
76
|
+
* @returns true if the offset exceeds the threshold
|
|
77
|
+
*/
|
|
78
|
+
const hasMovedBeyondThreshold = (currentCoordinates: EventCoordinates, threshold = 3) => {
|
|
79
|
+
const { deltaX, deltaY } = calculateDelta(currentCoordinates);
|
|
80
|
+
return Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
initialPosition,
|
|
85
|
+
isTouchDevice,
|
|
86
|
+
getEventCoordinates,
|
|
87
|
+
saveInitialPosition,
|
|
88
|
+
calculateDelta,
|
|
89
|
+
hasMovedBeyondThreshold,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
|
|
3
|
+
import type { EventCoordinates } from "./useEventCoordinates";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for the cell size
|
|
7
|
+
*/
|
|
8
|
+
export interface CellSize {
|
|
9
|
+
cellWidth: number;
|
|
10
|
+
cellHeight: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Interface for the drag offset
|
|
15
|
+
*/
|
|
16
|
+
export interface DragOffset {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Composible for calculating the position of a widget in the grid
|
|
23
|
+
*
|
|
24
|
+
* Provides functions for converting mouse coordinates to grid coordinates,
|
|
25
|
+
* considering the cell sizes, drag offset, and grid boundaries
|
|
26
|
+
*
|
|
27
|
+
* @param gridColumns The number of columns in the grid
|
|
28
|
+
* @param getGridRows The function for getting the number of rows in the grid
|
|
29
|
+
* @returns An object with functions for working with the grid position
|
|
30
|
+
*/
|
|
31
|
+
export function useGridPosition(gridColumns: number, getGridRows?: () => number) {
|
|
32
|
+
// Current preview position
|
|
33
|
+
const previewPosition = ref<DashboardWidgetPosition | null>(null);
|
|
34
|
+
|
|
35
|
+
// Drag offset (relative to the top left corner of the widget)
|
|
36
|
+
const dragOffset = ref<DragOffset>({ x: 0, y: 0 });
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculates the drag offset
|
|
40
|
+
*
|
|
41
|
+
* @param event The mouse or touch event
|
|
42
|
+
* @param element The HTML element of the widget
|
|
43
|
+
* @param cellSize The cell sizes
|
|
44
|
+
*/
|
|
45
|
+
const calculateDragOffset = (event: MouseEvent | TouchEvent, element: HTMLElement, cellSize: CellSize) => {
|
|
46
|
+
const rect = element.getBoundingClientRect();
|
|
47
|
+
|
|
48
|
+
// Get the coordinates of the event
|
|
49
|
+
let clientX: number, clientY: number;
|
|
50
|
+
|
|
51
|
+
if ("touches" in event) {
|
|
52
|
+
clientX = event.touches[0].clientX;
|
|
53
|
+
clientY = event.touches[0].clientY;
|
|
54
|
+
} else {
|
|
55
|
+
clientX = event.clientX;
|
|
56
|
+
clientY = event.clientY;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Calculate the offset relative to the cell size
|
|
60
|
+
dragOffset.value = {
|
|
61
|
+
x: (clientX - rect.left) / cellSize.cellWidth,
|
|
62
|
+
y: (clientY - rect.top) / cellSize.cellHeight,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Converts mouse coordinates to grid coordinates
|
|
68
|
+
*
|
|
69
|
+
* @param coords The mouse coordinates
|
|
70
|
+
* @param containerRect The sizes and position of the grid container
|
|
71
|
+
* @param cellSize The cell sizes
|
|
72
|
+
* @param scrollOffset The scroll offset of the container
|
|
73
|
+
* @returns The coordinates in the grid
|
|
74
|
+
*/
|
|
75
|
+
const mouseToGridCoordinates = (
|
|
76
|
+
coords: EventCoordinates,
|
|
77
|
+
containerRect: DOMRect,
|
|
78
|
+
cellSize: CellSize,
|
|
79
|
+
scrollOffset: { left: number; top: number },
|
|
80
|
+
): { gridX: number; gridY: number } => {
|
|
81
|
+
// Calculate the position of the mouse relative to the container with the scroll offset
|
|
82
|
+
const mouseX = coords.clientX - containerRect.left + scrollOffset.left;
|
|
83
|
+
const mouseY = coords.clientY - containerRect.top + scrollOffset.top;
|
|
84
|
+
|
|
85
|
+
// Convert the mouse coordinates to grid coordinates with the drag offset
|
|
86
|
+
const gridX = Math.round(mouseX / cellSize.cellWidth - dragOffset.value.x);
|
|
87
|
+
const gridY = Math.round(mouseY / cellSize.cellHeight - dragOffset.value.y);
|
|
88
|
+
|
|
89
|
+
return { gridX, gridY };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Constrains the position of the widget to the grid boundaries
|
|
94
|
+
*
|
|
95
|
+
* @param gridX The X-coordinate in the grid
|
|
96
|
+
* @param gridY The Y-coordinate in the grid
|
|
97
|
+
* @param widgetWidth The width of the widget in cells
|
|
98
|
+
* @param widgetHeight The height of the widget in cells
|
|
99
|
+
* @returns The constrained coordinates in the grid
|
|
100
|
+
*/
|
|
101
|
+
const constrainToGrid = (
|
|
102
|
+
gridX: number,
|
|
103
|
+
gridY: number,
|
|
104
|
+
widgetWidth: number,
|
|
105
|
+
widgetHeight: number,
|
|
106
|
+
): { gridX: number; gridY: number } => {
|
|
107
|
+
// Constrain the minimum values (not less than 0)
|
|
108
|
+
gridX = Math.max(0, gridX);
|
|
109
|
+
gridY = Math.max(0, gridY);
|
|
110
|
+
|
|
111
|
+
// The right and bottom boundaries depend on the size of the widget and the size of the grid
|
|
112
|
+
const gridWidth = gridColumns;
|
|
113
|
+
const gridHeight = getGridRows?.() || 100; // Use a large value if getGridRows is not defined
|
|
114
|
+
|
|
115
|
+
// Constrain the position so that the widget remains within the grid
|
|
116
|
+
gridX = Math.min(gridX, gridWidth - widgetWidth);
|
|
117
|
+
gridY = Math.min(gridY, gridHeight - widgetHeight);
|
|
118
|
+
|
|
119
|
+
return { gridX, gridY };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Updates the preview position
|
|
124
|
+
*
|
|
125
|
+
* @param position The new position
|
|
126
|
+
* @returns The updated preview position
|
|
127
|
+
*/
|
|
128
|
+
const updatePreviewPosition = (position: DashboardWidgetPosition): DashboardWidgetPosition => {
|
|
129
|
+
previewPosition.value = position;
|
|
130
|
+
return position;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resets the preview position and drag offset
|
|
135
|
+
*/
|
|
136
|
+
const resetPosition = () => {
|
|
137
|
+
previewPosition.value = null;
|
|
138
|
+
dragOffset.value = { x: 0, y: 0 };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
previewPosition,
|
|
143
|
+
dragOffset,
|
|
144
|
+
calculateDragOffset,
|
|
145
|
+
mouseToGridCoordinates,
|
|
146
|
+
constrainToGrid,
|
|
147
|
+
updatePreviewPosition,
|
|
148
|
+
resetPosition,
|
|
149
|
+
};
|
|
150
|
+
}
|