@vc-shell/framework 1.1.0-alpha.3 → 1.1.0-alpha.4
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/index.ts +18 -17
- package/core/composables/useDashboard/index.ts +19 -0
- package/core/composables/{useGlobalSearch.ts → useGlobalSearch/index.ts} +3 -5
- package/core/composables/useWidgets/index.ts +19 -18
- package/core/plugins/modularity/loader.ts +2 -1
- package/core/services/dashboard-service.ts +121 -0
- package/core/services/widget-service.ts +1 -4
- package/dist/core/composables/index.d.ts +1 -0
- package/dist/core/composables/index.d.ts.map +1 -1
- package/dist/core/composables/useDashboard/index.d.ts +5 -0
- package/dist/core/composables/useDashboard/index.d.ts.map +1 -0
- package/dist/core/composables/{useGlobalSearch.d.ts → useGlobalSearch/index.d.ts} +1 -1
- package/dist/core/composables/useGlobalSearch/index.d.ts.map +1 -0
- package/dist/core/plugins/modularity/loader.d.ts.map +1 -1
- package/dist/core/services/dashboard-service.d.ts +33 -0
- package/dist/core/services/dashboard-service.d.ts.map +1 -0
- package/dist/core/services/widget-service.d.ts.map +1 -1
- package/dist/framework.js +235 -225
- package/dist/{index-Bu12RZTu.js → index-8LELHzw9.js} +1 -1
- package/dist/{index-Bwl2ND2Q.js → index-9lJxZE5w.js} +1 -1
- package/dist/{index-CJi-BbTb.js → index-B1YR_MYV.js} +1 -1
- package/dist/{index-BhdwVgUw.js → index-BA98L1jI.js} +1 -1
- package/dist/{index-NdrUF1u3.js → index-BAeTsi-X.js} +1 -1
- package/dist/{index-CbRqPQTw.js → index-BBYyHeYA.js} +1 -1
- package/dist/{index-CsaYfhir.js → index-BrUitdDo.js} +1 -1
- package/dist/{index-CZ_pj3nW.js → index-BuO5ByG9.js} +1 -1
- package/dist/{index-DFPb-jDP.js → index-CJ5I7vTn.js} +1 -1
- package/dist/{index-BdoAu2fz.js → index-CWKrD2Cd.js} +1 -1
- package/dist/{index-DVaMW7gL.js → index-Cf9Tz1ql.js} +1 -1
- package/dist/{index-B89uIUkS.js → index-CrxFDC2b.js} +1 -1
- package/dist/{index-BcQiBkO6.js → index-D1JchciU.js} +1 -1
- package/dist/{index-CEvuTGIu.js → index-DLtsQ_PJ.js} +31254 -31134
- package/dist/{index-COjjAS6v.js → index-DVljTjbf.js} +1 -1
- package/dist/{index-DjQ6Ffv8.js → index-RwX3kiZh.js} +28 -28
- package/dist/{index-S9Ht7s3i.js → index-xLYzNPa7.js} +1 -1
- package/dist/index.css +1 -1
- package/dist/injection-keys.d.ts +28 -0
- package/dist/injection-keys.d.ts.map +1 -1
- package/dist/shared/components/dashboard-widget-card/dashboard-widget-card.vue.d.ts +25 -0
- package/dist/shared/components/dashboard-widget-card/dashboard-widget-card.vue.d.ts.map +1 -0
- package/dist/shared/components/dashboard-widget-card/index.d.ts +2 -0
- package/dist/shared/components/dashboard-widget-card/index.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts +6 -0
- package/dist/shared/components/draggable-dashboard/DraggableDashboard.vue.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/_internal/DashboardWidget.vue.d.ts +20 -0
- package/dist/shared/components/draggable-dashboard/_internal/DashboardWidget.vue.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts +354 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts +12 -0
- package/dist/shared/components/draggable-dashboard/composables/useDashboardGrid.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/index.d.ts +2 -0
- package/dist/shared/components/draggable-dashboard/index.d.ts.map +1 -0
- package/dist/shared/components/draggable-dashboard/types.d.ts +80 -0
- package/dist/shared/components/draggable-dashboard/types.d.ts.map +1 -0
- package/dist/shared/components/index.d.ts +2 -0
- package/dist/shared/components/index.d.ts.map +1 -1
- package/dist/shared/components/user-dropdown-button/_internal/user-info.vue.d.ts.map +1 -1
- package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
- package/dist/shared/composables/useMenuExpanded.d.ts +2 -0
- package/dist/shared/composables/useMenuExpanded.d.ts.map +1 -1
- package/dist/shared/modules/dynamic/components/fields/storybook/Button.stories.d.ts +0 -41
- package/dist/shared/modules/dynamic/components/fields/storybook/Button.stories.d.ts.map +1 -1
- package/dist/shared/modules/dynamic/components/fields/storybook/Card.stories.d.ts.map +1 -1
- package/dist/shared/modules/dynamic/components/fields/storybook/common/templates.d.ts +1 -1
- package/dist/shared/modules/dynamic/components/fields/storybook/common/templates.d.ts.map +1 -1
- package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts +2 -25
- package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts.map +1 -1
- package/dist/tailwind.config.d.ts +1 -81
- package/dist/tailwind.config.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/components/atoms/vc-button/vc-button.stories.d.ts +169 -734
- package/dist/ui/components/atoms/vc-button/vc-button.stories.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts +18 -2
- package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-card/index.d.ts +2 -0
- package/dist/ui/components/atoms/vc-card/index.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-card/vc-card.stories.d.ts +12 -0
- package/dist/ui/components/atoms/vc-card/vc-card.stories.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-card/vc-card.vue.d.ts +2 -0
- package/dist/ui/components/atoms/vc-card/vc-card.vue.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue.d.ts +18 -0
- package/dist/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue.d.ts.map +1 -0
- package/dist/ui/components/atoms/vc-icon/icons/index.d.ts +2 -0
- package/dist/ui/components/atoms/vc-icon/icons/index.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.d.ts +2 -0
- package/dist/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts +0 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts +2 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/vc-app.stories.d.ts +13 -67
- package/dist/ui/components/organisms/vc-app/vc-app.stories.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts +5 -65
- package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-base-header/vc-table-base-header.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/{vc-table-header/vc-table-header.vue.d.ts → vc-table-columns-header/vc-table-columns-header.vue.d.ts} +1 -1
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-columns-header/vc-table-columns-header.vue.d.ts.map +1 -0
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-header/vc-table-header.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue.d.ts +33 -3
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/composables/useTableRowReorder.d.ts.map +1 -1
- package/package.json +10 -5
- package/shared/components/dashboard-widget-card/dashboard-widget-card.vue +67 -0
- package/shared/components/dashboard-widget-card/index.ts +1 -0
- package/shared/components/draggable-dashboard/DraggableDashboard.vue +369 -0
- package/shared/components/draggable-dashboard/_internal/DashboardWidget.vue +133 -0
- package/shared/components/draggable-dashboard/composables/useDashboardDragAndDrop.ts +547 -0
- package/shared/components/draggable-dashboard/composables/useDashboardGrid.ts +250 -0
- package/shared/components/draggable-dashboard/index.ts +1 -0
- package/shared/components/draggable-dashboard/types.ts +91 -0
- package/shared/components/index.ts +2 -0
- package/shared/components/user-dropdown-button/_internal/user-info.vue +25 -12
- package/shared/components/user-dropdown-button/user-dropdown-button.vue +3 -3
- package/shared/composables/useMenuExpanded.ts +24 -0
- package/shared/modules/assets/components/assets-details/assets-details.vue +1 -1
- package/shared/modules/dynamic/components/fields/storybook/Button.stories.ts +186 -247
- package/shared/modules/dynamic/components/fields/storybook/Card.stories.ts +175 -176
- package/shared/modules/dynamic/components/fields/storybook/common/templates.ts +8 -8
- package/shared/modules/dynamic/pages/dynamic-blade-list.vue +153 -187
- package/tailwind.config.ts +127 -126
- package/ui/components/atoms/vc-button/vc-button.stories.ts +1 -16
- package/ui/components/atoms/vc-button/vc-button.vue +74 -63
- package/ui/components/atoms/vc-card/vc-card.stories.ts +102 -102
- package/ui/components/atoms/vc-card/vc-card.vue +164 -159
- package/ui/components/atoms/vc-icon/icons/GridDotsIcon.vue +22 -0
- package/ui/components/atoms/vc-icon/icons/ShoppingCardIcon.vue +16 -0
- package/ui/components/atoms/vc-icon/icons/index.ts +2 -0
- package/ui/components/molecules/vc-field/vc-field.vue +1 -1
- package/ui/components/organisms/vc-app/_internal/composables/useAppMenuState.ts +12 -1
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarContent.vue +1 -2
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarHeader.vue +1 -1
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/_internal/AppBarOverlay.vue +0 -1
- package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +274 -112
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +81 -37
- package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +7 -5
- package/ui/components/organisms/vc-app/vc-app.vue +26 -15
- package/ui/components/organisms/vc-table/_internal/vc-table-base-header/vc-table-base-header.vue +5 -7
- package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/{vc-table-header/vc-table-header.vue → vc-table-columns-header/vc-table-columns-header.vue} +23 -21
- package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-row/vc-table-row.vue +1 -0
- package/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/vc-table-desktop-view.vue +1 -1
- package/ui/components/organisms/vc-table/_internal/vc-table-header/vc-table-header.vue +12 -1
- package/ui/components/organisms/vc-table/_internal/vc-table-mobile-view/vc-table-mobile-view.vue +45 -2
- package/ui/components/organisms/vc-table/composables/useTableColumnReorder.ts +5 -5
- package/ui/components/organisms/vc-table/composables/useTableColumnResize.ts +1 -1
- package/ui/components/organisms/vc-table/composables/useTableRowReorder.ts +1 -0
- package/core/services/toolbarbus-service.ts +0 -34
- package/dist/core/composables/useGlobalSearch.d.ts.map +0 -1
- package/dist/core/services/toolbarbus-service.d.ts +0 -10
- package/dist/core/services/toolbarbus-service.d.ts.map +0 -1
- package/dist/ui/components/organisms/vc-app/composables/useToolbarSlots.d.ts +0 -5
- package/dist/ui/components/organisms/vc-app/composables/useToolbarSlots.d.ts.map +0 -1
- package/dist/ui/components/organisms/vc-table/_internal/vc-table-desktop-view/_internal/vc-table-header/vc-table-header.vue.d.ts.map +0 -1
- package/ui/components/organisms/vc-app/composables/useToolbarSlots.ts +0 -37
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { ref, computed, onUnmounted } from "vue";
|
|
2
|
+
import type { IDashboardWidget, DashboardWidgetPosition } from "../types";
|
|
3
|
+
import { GRID_COLUMNS } from "./useDashboardGrid";
|
|
4
|
+
import { useDashboard } from "../../../../core/composables/useDashboard";
|
|
5
|
+
|
|
6
|
+
// Cell height constant, corresponds to --dashboard-cell-height CSS variable
|
|
7
|
+
// Value of 80px is defined in DraggableDashboard.vue
|
|
8
|
+
const CELL_HEIGHT = 80;
|
|
9
|
+
|
|
10
|
+
export function useDashboardDragAndDrop(
|
|
11
|
+
updateWidgetPosition: (widgetId: string, position: DashboardWidgetPosition) => void,
|
|
12
|
+
getGridRows?: () => number,
|
|
13
|
+
) {
|
|
14
|
+
const dashboard = useDashboard();
|
|
15
|
+
const widgets = computed(() => dashboard.getWidgets());
|
|
16
|
+
const gridContainer = ref<HTMLElement | null>(null);
|
|
17
|
+
|
|
18
|
+
// Drag state
|
|
19
|
+
const draggedWidget = ref<IDashboardWidget | null>(null);
|
|
20
|
+
const previewPosition = ref<DashboardWidgetPosition | null>(null);
|
|
21
|
+
const isDragging = ref(false);
|
|
22
|
+
const dragOffset = ref({ x: 0, y: 0 });
|
|
23
|
+
const displacedWidgets = ref<Map<string, DashboardWidgetPosition>>(new Map());
|
|
24
|
+
|
|
25
|
+
// Drag clone
|
|
26
|
+
const dragClone = ref<HTMLElement | null>(null);
|
|
27
|
+
const initialMousePosition = ref({ x: 0, y: 0 });
|
|
28
|
+
const isTouchDevice = ref(false);
|
|
29
|
+
|
|
30
|
+
// Get cell size
|
|
31
|
+
const calculateCellSize = () => {
|
|
32
|
+
if (!gridContainer.value) return { cellWidth: 0, cellHeight: CELL_HEIGHT };
|
|
33
|
+
const rect = gridContainer.value.getBoundingClientRect();
|
|
34
|
+
return {
|
|
35
|
+
cellWidth: rect.width / GRID_COLUMNS,
|
|
36
|
+
cellHeight: CELL_HEIGHT,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Create widget clone for dragging
|
|
41
|
+
const createDragClone = (element: HTMLElement) => {
|
|
42
|
+
// Find parent widget element
|
|
43
|
+
const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
|
|
44
|
+
if (!widgetElement) return null;
|
|
45
|
+
|
|
46
|
+
const clone = widgetElement.cloneNode(true) as HTMLElement;
|
|
47
|
+
const rect = widgetElement.getBoundingClientRect();
|
|
48
|
+
|
|
49
|
+
// Учитываем прокрутку страницы
|
|
50
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
51
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
52
|
+
|
|
53
|
+
// Копируем все вычисленные стили с оригинала
|
|
54
|
+
const computedStyle = window.getComputedStyle(widgetElement);
|
|
55
|
+
|
|
56
|
+
clone.style.position = "fixed";
|
|
57
|
+
clone.style.width = `${rect.width}px`;
|
|
58
|
+
clone.style.height = `${rect.height}px`;
|
|
59
|
+
clone.style.left = `${rect.left + scrollX}px`;
|
|
60
|
+
clone.style.top = `${rect.top + scrollY}px`;
|
|
61
|
+
clone.style.zIndex = "9999";
|
|
62
|
+
clone.style.pointerEvents = "none";
|
|
63
|
+
clone.style.opacity = "0.95";
|
|
64
|
+
clone.style.transform = "scale(1.02)";
|
|
65
|
+
clone.style.transition = "transform 0.1s ease, opacity 0.2s ease";
|
|
66
|
+
clone.style.boxShadow = "0 10px 25px rgba(0,0,0,0.15)";
|
|
67
|
+
clone.style.willChange = "transform";
|
|
68
|
+
|
|
69
|
+
// Копируем важные стили с оригинала
|
|
70
|
+
clone.style.backgroundColor = computedStyle.backgroundColor;
|
|
71
|
+
clone.style.borderRadius = computedStyle.borderRadius;
|
|
72
|
+
|
|
73
|
+
// Добавляем класс для стилизации клона
|
|
74
|
+
clone.classList.add("dashboard-widget-clone");
|
|
75
|
+
|
|
76
|
+
// Анимация появления
|
|
77
|
+
requestAnimationFrame(() => {
|
|
78
|
+
clone.style.transform = "scale(1.02)";
|
|
79
|
+
clone.style.opacity = "0.92";
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
document.body.appendChild(clone);
|
|
83
|
+
return clone;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Optimized intersection check function
|
|
87
|
+
const intersect = (
|
|
88
|
+
widgetA: IDashboardWidget,
|
|
89
|
+
posA: DashboardWidgetPosition,
|
|
90
|
+
widgetB: IDashboardWidget,
|
|
91
|
+
posB: DashboardWidgetPosition,
|
|
92
|
+
) => {
|
|
93
|
+
// Early Y-axis check (if difference is greater than sum of heights, no intersection)
|
|
94
|
+
const verticalDistance = Math.abs(posA.y - posB.y);
|
|
95
|
+
if (verticalDistance > widgetA.size.height + widgetB.size.height) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Early X-axis check (if difference is greater than sum of widths, no intersection)
|
|
100
|
+
const horizontalDistance = Math.abs(posA.x - posB.x);
|
|
101
|
+
if (horizontalDistance > widgetA.size.width + widgetB.size.width) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Standard rectangle intersection check
|
|
106
|
+
const aLeft = posA.x;
|
|
107
|
+
const aRight = posA.x + widgetA.size.width;
|
|
108
|
+
const aTop = posA.y;
|
|
109
|
+
const aBottom = posA.y + widgetA.size.height;
|
|
110
|
+
|
|
111
|
+
const bLeft = posB.x;
|
|
112
|
+
const bRight = posB.x + widgetB.size.width;
|
|
113
|
+
const bTop = posB.y;
|
|
114
|
+
const bBottom = posB.y + widgetB.size.height;
|
|
115
|
+
|
|
116
|
+
return !(aRight <= bLeft || aLeft >= bRight || aBottom <= bTop || aTop >= bBottom);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Optimized function for updating displaced widgets
|
|
120
|
+
const updateDisplacedWidgets = () => {
|
|
121
|
+
if (!draggedWidget.value || !previewPosition.value || !isDragging.value) {
|
|
122
|
+
displacedWidgets.value.clear();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const layout = new Map(dashboard.getLayout());
|
|
127
|
+
const newDisplacements = new Map<string, DashboardWidgetPosition>();
|
|
128
|
+
const processedWidgets = new Set<string>();
|
|
129
|
+
|
|
130
|
+
// Cache for initial collision checks
|
|
131
|
+
const directCollisionWidgets = new Set<string>();
|
|
132
|
+
|
|
133
|
+
// Check collisions with other widgets
|
|
134
|
+
for (const widget of widgets.value) {
|
|
135
|
+
if (widget.id === draggedWidget.value.id) continue;
|
|
136
|
+
|
|
137
|
+
const pos = layout.get(widget.id);
|
|
138
|
+
if (!pos) continue;
|
|
139
|
+
|
|
140
|
+
if (intersect(draggedWidget.value, previewPosition.value, widget, pos)) {
|
|
141
|
+
directCollisionWidgets.add(widget.id);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If no direct collisions, skip additional displacement calculations
|
|
146
|
+
if (directCollisionWidgets.size === 0) {
|
|
147
|
+
displacedWidgets.value = newDisplacements;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Sort widgets top to bottom for more natural rearrangement
|
|
152
|
+
const sortedWidgets = [...widgets.value]
|
|
153
|
+
.filter((w) => w.id !== draggedWidget.value?.id)
|
|
154
|
+
.sort((a, b) => {
|
|
155
|
+
const posA = layout.get(a.id);
|
|
156
|
+
const posB = layout.get(b.id);
|
|
157
|
+
if (!posA || !posB) return 0;
|
|
158
|
+
// Sort by Y first, then by X for more stable results
|
|
159
|
+
return posA.y === posB.y ? posA.x - posB.x : posA.y - posB.y;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Function for checking collisions with caching
|
|
163
|
+
const collisionCache = new Map<string, boolean>();
|
|
164
|
+
|
|
165
|
+
const checkCollision = (widget: IDashboardWidget, position: DashboardWidgetPosition) => {
|
|
166
|
+
const cacheKey = `${widget.id}_${position.x}_${position.y}`;
|
|
167
|
+
|
|
168
|
+
if (collisionCache.has(cacheKey)) {
|
|
169
|
+
return collisionCache.get(cacheKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check intersection with dragged widget
|
|
173
|
+
let hasCollision = intersect(draggedWidget.value!, previewPosition.value!, widget, position);
|
|
174
|
+
|
|
175
|
+
// Check intersections with already displaced widgets if no collision with dragged widget
|
|
176
|
+
if (!hasCollision) {
|
|
177
|
+
for (const [id, pos] of newDisplacements) {
|
|
178
|
+
const w = widgets.value.find((w) => w.id === id);
|
|
179
|
+
if (w && intersect(widget, position, w, pos)) {
|
|
180
|
+
hasCollision = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
collisionCache.set(cacheKey, hasCollision);
|
|
187
|
+
return hasCollision;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Process widgets with direct collisions first
|
|
191
|
+
for (const widget of sortedWidgets) {
|
|
192
|
+
if (processedWidgets.has(widget.id)) continue;
|
|
193
|
+
|
|
194
|
+
const currentPos = layout.get(widget.id);
|
|
195
|
+
if (!currentPos) continue;
|
|
196
|
+
|
|
197
|
+
// Focus on widgets with direct collisions first
|
|
198
|
+
if (!directCollisionWidgets.has(widget.id) && newDisplacements.size === 0) continue;
|
|
199
|
+
|
|
200
|
+
// Check collisions at current position
|
|
201
|
+
const hasCollision = checkCollision(widget, currentPos);
|
|
202
|
+
|
|
203
|
+
if (hasCollision) {
|
|
204
|
+
// Find new position below dragged widget
|
|
205
|
+
const newY = Math.max(previewPosition.value.y + draggedWidget.value.size.height, currentPos.y + 1);
|
|
206
|
+
let finalY = newY;
|
|
207
|
+
|
|
208
|
+
// Find nearest position without collisions, optimized
|
|
209
|
+
const maxIterations = 50; // Protection against infinite loop
|
|
210
|
+
let iterations = 0;
|
|
211
|
+
|
|
212
|
+
while (checkCollision(widget, { x: currentPos.x, y: finalY }) && iterations < maxIterations) {
|
|
213
|
+
finalY++;
|
|
214
|
+
iterations++;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
newDisplacements.set(widget.id, { x: currentPos.x, y: finalY });
|
|
218
|
+
processedWidgets.add(widget.id);
|
|
219
|
+
|
|
220
|
+
// If we displaced a widget with direct collision, check if we created new collisions
|
|
221
|
+
for (const w of sortedWidgets) {
|
|
222
|
+
if (w.id !== widget.id && !processedWidgets.has(w.id)) {
|
|
223
|
+
const pos = layout.get(w.id);
|
|
224
|
+
if (pos && intersect(widget, { x: currentPos.x, y: finalY }, w, pos)) {
|
|
225
|
+
directCollisionWidgets.add(w.id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
displacedWidgets.value = newDisplacements;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Get event coordinates for either mouse or touch events
|
|
236
|
+
const getEventCoordinates = (event: MouseEvent | TouchEvent) => {
|
|
237
|
+
if ("touches" in event) {
|
|
238
|
+
// Touch event
|
|
239
|
+
return {
|
|
240
|
+
clientX: event.touches[0].clientX,
|
|
241
|
+
clientY: event.touches[0].clientY,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// Mouse event
|
|
245
|
+
return {
|
|
246
|
+
clientX: (event as MouseEvent).clientX,
|
|
247
|
+
clientY: (event as MouseEvent).clientY,
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Common logic for mouse and touch move events
|
|
252
|
+
const updateDragPosition = (event: MouseEvent | TouchEvent) => {
|
|
253
|
+
if (!gridContainer.value || !draggedWidget.value || !dragClone.value) return;
|
|
254
|
+
|
|
255
|
+
// Save event data as it might be unavailable in requestAnimationFrame
|
|
256
|
+
const coords = getEventCoordinates(event);
|
|
257
|
+
|
|
258
|
+
// Use requestAnimationFrame for UI update optimization
|
|
259
|
+
requestAnimationFrame(() => {
|
|
260
|
+
if (!gridContainer.value || !draggedWidget.value || !dragClone.value) return;
|
|
261
|
+
|
|
262
|
+
const rect = gridContainer.value.getBoundingClientRect();
|
|
263
|
+
const { cellWidth, cellHeight } = calculateCellSize();
|
|
264
|
+
|
|
265
|
+
// Update clone position relative to initial mouse position
|
|
266
|
+
const deltaX = coords.clientX - initialMousePosition.value.x;
|
|
267
|
+
const deltaY = coords.clientY - initialMousePosition.value.y;
|
|
268
|
+
|
|
269
|
+
dragClone.value.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
270
|
+
|
|
271
|
+
// Calculate container scroll
|
|
272
|
+
const gridScrollLeft = gridContainer.value.scrollLeft;
|
|
273
|
+
const gridScrollTop = gridContainer.value.scrollTop;
|
|
274
|
+
|
|
275
|
+
// Calculate container offset in window
|
|
276
|
+
const gridRectLeft = rect.left;
|
|
277
|
+
const gridRectTop = rect.top;
|
|
278
|
+
|
|
279
|
+
// Calculate mouse position relative to grid with scroll
|
|
280
|
+
const mouseX = coords.clientX - gridRectLeft + gridScrollLeft;
|
|
281
|
+
const mouseY = coords.clientY - gridRectTop + gridScrollTop;
|
|
282
|
+
|
|
283
|
+
// Convert mouse coordinates to grid coordinates with drag offset
|
|
284
|
+
let gridX = Math.round(mouseX / cellWidth - dragOffset.value.x);
|
|
285
|
+
let gridY = Math.round(mouseY / cellHeight - dragOffset.value.y);
|
|
286
|
+
|
|
287
|
+
// Limit boundaries (minimum 0)
|
|
288
|
+
gridX = Math.max(0, gridX);
|
|
289
|
+
gridY = Math.max(0, gridY);
|
|
290
|
+
|
|
291
|
+
// Right and bottom boundaries depend on widget size and grid dimensions
|
|
292
|
+
const gridWidth = GRID_COLUMNS;
|
|
293
|
+
const gridHeight = getGridRows?.() || 100; // Use large value if getGridRows is not defined
|
|
294
|
+
|
|
295
|
+
// Limit position to keep widget within grid boundaries
|
|
296
|
+
gridX = Math.min(gridX, gridWidth - draggedWidget.value.size.width);
|
|
297
|
+
gridY = Math.min(gridY, gridHeight - draggedWidget.value.size.height);
|
|
298
|
+
|
|
299
|
+
// Update preview position only if it changed
|
|
300
|
+
if (!previewPosition.value || previewPosition.value.x !== gridX || previewPosition.value.y !== gridY) {
|
|
301
|
+
previewPosition.value = { x: gridX, y: gridY };
|
|
302
|
+
|
|
303
|
+
// Recalculate displaced widgets on position change
|
|
304
|
+
updateDisplacedWidgets();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Enhanced mouse event handler
|
|
310
|
+
const handleMouseMove = (event: MouseEvent) => {
|
|
311
|
+
if (!isDragging.value || !draggedWidget.value || !gridContainer.value || !dragClone.value) return;
|
|
312
|
+
updateDragPosition(event);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Enhanced touch event handler
|
|
316
|
+
const handleTouchMove = (event: TouchEvent) => {
|
|
317
|
+
if (!isDragging.value || !draggedWidget.value || !gridContainer.value || !dragClone.value) return;
|
|
318
|
+
|
|
319
|
+
// Prevent page scrolling during drag
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
|
|
322
|
+
updateDragPosition(event);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Enhanced drag end handlers
|
|
326
|
+
const handleMouseUp = () => {
|
|
327
|
+
finishDrag();
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const handleTouchEnd = () => {
|
|
331
|
+
finishDrag();
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// Common drag finish function
|
|
335
|
+
const finishDrag = () => {
|
|
336
|
+
if (!isDragging.value || !draggedWidget.value || !previewPosition.value) return;
|
|
337
|
+
|
|
338
|
+
// Get necessary data
|
|
339
|
+
const draggedWidgetId = draggedWidget.value.id;
|
|
340
|
+
const finalPosition = previewPosition.value;
|
|
341
|
+
|
|
342
|
+
// When there's a clone, finish dragging smoothly
|
|
343
|
+
if (dragClone.value) {
|
|
344
|
+
// Get DOM element of original widget
|
|
345
|
+
const originalWidget = document.querySelector(`.dashboard-widget[data-id="${draggedWidgetId}"]`) as HTMLElement;
|
|
346
|
+
|
|
347
|
+
if (originalWidget) {
|
|
348
|
+
const { cellWidth, cellHeight } = calculateCellSize();
|
|
349
|
+
const widgetGap = 20; // Widget gap in pixels
|
|
350
|
+
|
|
351
|
+
// Get current clone position (where user released mouse)
|
|
352
|
+
const cloneRect = dragClone.value.getBoundingClientRect();
|
|
353
|
+
|
|
354
|
+
// Get page scroll
|
|
355
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
356
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
357
|
+
|
|
358
|
+
// Get grid container position with scroll
|
|
359
|
+
const gridRect = gridContainer.value!.getBoundingClientRect();
|
|
360
|
+
const gridScrollLeft = gridContainer.value!.scrollLeft;
|
|
361
|
+
const gridScrollTop = gridContainer.value!.scrollTop;
|
|
362
|
+
|
|
363
|
+
// Calculate relative clone position inside container with all scrolls
|
|
364
|
+
const relX = cloneRect.left + scrollX - (gridRect.left + scrollX) + gridScrollLeft;
|
|
365
|
+
const relY = cloneRect.top + scrollY - (gridRect.top + scrollY) + gridScrollTop;
|
|
366
|
+
|
|
367
|
+
// Calculate final coordinates where widget should move
|
|
368
|
+
const finalX = finalPosition.x * cellWidth + widgetGap / 2;
|
|
369
|
+
const finalY = finalPosition.y * cellHeight + widgetGap / 2;
|
|
370
|
+
|
|
371
|
+
// First place original widget exactly at clone's position
|
|
372
|
+
originalWidget.style.transition = "none";
|
|
373
|
+
originalWidget.style.transform = `translate(${relX}px, ${relY}px)`;
|
|
374
|
+
originalWidget.style.opacity = "1";
|
|
375
|
+
originalWidget.style.zIndex = "1000";
|
|
376
|
+
|
|
377
|
+
// Remove clone immediately since original is in its place
|
|
378
|
+
if (dragClone.value.parentNode) {
|
|
379
|
+
document.body.removeChild(dragClone.value);
|
|
380
|
+
}
|
|
381
|
+
dragClone.value = null;
|
|
382
|
+
|
|
383
|
+
// Force reflow to apply styles
|
|
384
|
+
originalWidget.offsetHeight; // eslint-disable-line
|
|
385
|
+
|
|
386
|
+
// Update position in data store
|
|
387
|
+
updateWidgetPosition(draggedWidgetId, finalPosition);
|
|
388
|
+
|
|
389
|
+
// Update positions of all displaced widgets
|
|
390
|
+
for (const [widgetId, position] of displacedWidgets.value.entries()) {
|
|
391
|
+
updateWidgetPosition(widgetId, position);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Now smoothly animate widget to its final position
|
|
395
|
+
setTimeout(() => {
|
|
396
|
+
originalWidget.style.transition = `transform var(--dashboard-transition-duration) var(--dashboard-transition-timing)`;
|
|
397
|
+
originalWidget.style.transform = `translate(${finalX}px, ${finalY}px)`;
|
|
398
|
+
}, 20); // Small delay to ensure style changes apply correctly
|
|
399
|
+
} else {
|
|
400
|
+
// If original not found, just remove clone and update data
|
|
401
|
+
if (dragClone.value.parentNode) {
|
|
402
|
+
document.body.removeChild(dragClone.value);
|
|
403
|
+
}
|
|
404
|
+
dragClone.value = null;
|
|
405
|
+
|
|
406
|
+
// Update position in store
|
|
407
|
+
updateWidgetPosition(draggedWidgetId, finalPosition);
|
|
408
|
+
|
|
409
|
+
// Update positions of all displaced widgets
|
|
410
|
+
for (const [widgetId, position] of displacedWidgets.value.entries()) {
|
|
411
|
+
updateWidgetPosition(widgetId, position);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// If no clone, just update data
|
|
416
|
+
updateWidgetPosition(draggedWidgetId, finalPosition);
|
|
417
|
+
|
|
418
|
+
// Update positions of all displaced widgets
|
|
419
|
+
for (const [widgetId, position] of displacedWidgets.value.entries()) {
|
|
420
|
+
updateWidgetPosition(widgetId, position);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Remove event handlers
|
|
425
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
426
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
427
|
+
document.removeEventListener("touchmove", handleTouchMove, { passive: false } as EventListenerOptions);
|
|
428
|
+
document.removeEventListener("touchend", handleTouchEnd);
|
|
429
|
+
document.removeEventListener("touchcancel", handleTouchEnd);
|
|
430
|
+
|
|
431
|
+
// Reset state with delay to allow animation to complete
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
isDragging.value = false;
|
|
434
|
+
draggedWidget.value = null;
|
|
435
|
+
previewPosition.value = null;
|
|
436
|
+
displacedWidgets.value.clear();
|
|
437
|
+
dragOffset.value = { x: 0, y: 0 };
|
|
438
|
+
}, 50);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// Enhanced mousedown handler with passive listeners
|
|
442
|
+
const handleMouseDown = (event: MouseEvent | TouchEvent, widget: IDashboardWidget, element: HTMLElement) => {
|
|
443
|
+
if (!gridContainer.value) return;
|
|
444
|
+
|
|
445
|
+
// Check event type
|
|
446
|
+
isTouchDevice.value = "touches" in event;
|
|
447
|
+
|
|
448
|
+
// Use preventDefault only for mouse events, use passive: false for touch
|
|
449
|
+
if (!isTouchDevice.value) {
|
|
450
|
+
event.preventDefault();
|
|
451
|
+
}
|
|
452
|
+
event.stopPropagation();
|
|
453
|
+
|
|
454
|
+
// Store initial position for drag detection
|
|
455
|
+
const initialEvent = getEventCoordinates(event);
|
|
456
|
+
let hasDragStarted = false;
|
|
457
|
+
|
|
458
|
+
// Find parent widget element
|
|
459
|
+
const widgetElement = element.closest(".dashboard-widget") as HTMLElement;
|
|
460
|
+
if (!widgetElement) return;
|
|
461
|
+
|
|
462
|
+
const rect = widgetElement.getBoundingClientRect();
|
|
463
|
+
const { cellWidth, cellHeight } = calculateCellSize();
|
|
464
|
+
|
|
465
|
+
// Temporary move handler to detect actual drag start
|
|
466
|
+
const tempMoveHandler = (moveEvent: MouseEvent | TouchEvent) => {
|
|
467
|
+
if (hasDragStarted) return;
|
|
468
|
+
|
|
469
|
+
const currentCoords = getEventCoordinates(moveEvent);
|
|
470
|
+
const deltaX = Math.abs(currentCoords.clientX - initialEvent.clientX);
|
|
471
|
+
const deltaY = Math.abs(currentCoords.clientY - initialEvent.clientY);
|
|
472
|
+
|
|
473
|
+
// Start drag only if mouse moved more than 3 pixels
|
|
474
|
+
if (deltaX > 3 || deltaY > 3) {
|
|
475
|
+
hasDragStarted = true;
|
|
476
|
+
isDragging.value = true;
|
|
477
|
+
draggedWidget.value = widget;
|
|
478
|
+
|
|
479
|
+
// Save exact coordinate offset without rounding
|
|
480
|
+
dragOffset.value = {
|
|
481
|
+
x: (initialEvent.clientX - rect.left) / cellWidth,
|
|
482
|
+
y: (initialEvent.clientY - rect.top) / cellHeight,
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
initialMousePosition.value = {
|
|
486
|
+
x: initialEvent.clientX,
|
|
487
|
+
y: initialEvent.clientY,
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
dragClone.value = createDragClone(element);
|
|
491
|
+
|
|
492
|
+
// Remove temporary handlers
|
|
493
|
+
document.removeEventListener("mousemove", tempMoveHandler);
|
|
494
|
+
document.removeEventListener("touchmove", tempMoveHandler);
|
|
495
|
+
document.removeEventListener("mouseup", tempUpHandler);
|
|
496
|
+
document.removeEventListener("touchend", tempUpHandler);
|
|
497
|
+
|
|
498
|
+
// Add actual drag handlers
|
|
499
|
+
if (isTouchDevice.value) {
|
|
500
|
+
document.addEventListener("touchmove", handleTouchMove, { passive: false } as EventListenerOptions);
|
|
501
|
+
document.addEventListener("touchend", handleTouchEnd);
|
|
502
|
+
document.addEventListener("touchcancel", handleTouchEnd);
|
|
503
|
+
} else {
|
|
504
|
+
document.addEventListener("mousemove", handleMouseMove, { passive: true });
|
|
505
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// Temporary up handler to cancel drag detection
|
|
511
|
+
const tempUpHandler = () => {
|
|
512
|
+
if (!hasDragStarted) {
|
|
513
|
+
document.removeEventListener("mousemove", tempMoveHandler);
|
|
514
|
+
document.removeEventListener("touchmove", tempMoveHandler);
|
|
515
|
+
document.removeEventListener("mouseup", tempUpHandler);
|
|
516
|
+
document.removeEventListener("touchend", tempUpHandler);
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// Add temporary handlers to detect drag start
|
|
521
|
+
if (isTouchDevice.value) {
|
|
522
|
+
document.addEventListener("touchmove", tempMoveHandler, { passive: true });
|
|
523
|
+
document.addEventListener("touchend", tempUpHandler);
|
|
524
|
+
} else {
|
|
525
|
+
document.addEventListener("mousemove", tempMoveHandler, { passive: true });
|
|
526
|
+
document.addEventListener("mouseup", tempUpHandler);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const setGridContainer = (container: HTMLElement) => {
|
|
531
|
+
gridContainer.value = container;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Clean up event handlers when the component is unmounted
|
|
535
|
+
onUnmounted(() => {
|
|
536
|
+
finishDrag();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
draggedWidget,
|
|
541
|
+
previewPosition,
|
|
542
|
+
displacedWidgets,
|
|
543
|
+
isDragging,
|
|
544
|
+
handleMouseDown, // Now handles both mouse and touch events
|
|
545
|
+
setGridContainer,
|
|
546
|
+
};
|
|
547
|
+
}
|