@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,250 @@
|
|
|
1
|
+
import { computed, ref, watch } from "vue";
|
|
2
|
+
import type { IDashboardWidget, DashboardWidgetPosition, DashboardGridConfig } from "../types";
|
|
3
|
+
import { useDashboard } from "../../../../core/composables/useDashboard";
|
|
4
|
+
|
|
5
|
+
// Constants for grid
|
|
6
|
+
export const GRID_COLUMNS = 12;
|
|
7
|
+
// Initial rows, but now it's the minimum value
|
|
8
|
+
export const MIN_GRID_ROWS = 12;
|
|
9
|
+
// Increase height buffer for "infinite" scroll
|
|
10
|
+
const ROWS_BUFFER = 10;
|
|
11
|
+
// Dynamically calculated number of rows
|
|
12
|
+
const dynamicRows = ref(MIN_GRID_ROWS);
|
|
13
|
+
|
|
14
|
+
// Constants for position search optimization
|
|
15
|
+
const SEARCH_DIRECTIONS = [
|
|
16
|
+
{ dx: 0, dy: 0 }, // Current position
|
|
17
|
+
{ dx: 0, dy: 1 }, // Down
|
|
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
|
+
|
|
27
|
+
export function useDashboardGrid() {
|
|
28
|
+
const dashboard = useDashboard();
|
|
29
|
+
const widgets = computed(() => dashboard.getWidgets());
|
|
30
|
+
const layout = computed(() => dashboard.getLayout());
|
|
31
|
+
|
|
32
|
+
// Calculate current maximum number of rows based on existing widgets
|
|
33
|
+
const currentRows = computed(() => {
|
|
34
|
+
if (widgets.value.length === 0) return MIN_GRID_ROWS;
|
|
35
|
+
|
|
36
|
+
const maxY = Math.max(
|
|
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
|
+
);
|
|
43
|
+
|
|
44
|
+
// Update dynamic number of rows with large buffer
|
|
45
|
+
dynamicRows.value = Math.max(maxY + ROWS_BUFFER, MIN_GRID_ROWS);
|
|
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
|
+
});
|
|
81
|
+
|
|
82
|
+
// Check collisions for each widget cell
|
|
83
|
+
for (let x = position.x; x < position.x + widget.size.width; x++) {
|
|
84
|
+
for (let y = position.y; y < position.y + widget.size.height; y++) {
|
|
85
|
+
if (occupiedCells.has(`${x},${y}`)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Improved free position search function
|
|
95
|
+
const findFreePosition = (widget: IDashboardWidget): DashboardWidgetPosition => {
|
|
96
|
+
const currentPos = layout.value.get(widget.id);
|
|
97
|
+
|
|
98
|
+
// If current position is free, use it
|
|
99
|
+
if (currentPos && !hasCollision(widget, currentPos, widget.id)) {
|
|
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
|
+
}
|
|
127
|
+
|
|
128
|
+
// If no position found, add to the end
|
|
129
|
+
return {
|
|
130
|
+
x: 0,
|
|
131
|
+
y: currentRows.value,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Arrange widgets in rows, minimizing empty spaces
|
|
136
|
+
const arrangeWidgetsInRows = (widgetsToArrange: IDashboardWidget[]): void => {
|
|
137
|
+
if (widgetsToArrange.length === 0) return;
|
|
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
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Initialize initial widget positions
|
|
173
|
+
const initializeLayout = () => {
|
|
174
|
+
// Check if layout rebuild is needed
|
|
175
|
+
|
|
176
|
+
// Check if not all widgets have positioned yet
|
|
177
|
+
if (widgets.value.length !== layout.value.size) {
|
|
178
|
+
arrangeWidgetsInRows(widgets.value);
|
|
179
|
+
return;
|
|
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);
|
|
211
|
+
|
|
212
|
+
// If less than 70% of the grid is occupied and widgets occupy many rows, then rearrange
|
|
213
|
+
const fillRatio = occupiedCells / totalCells;
|
|
214
|
+
if (maxY > 1 && fillRatio < 0.7) {
|
|
215
|
+
arrangeWidgetsInRows(widgets.value);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// For widgets with positions, check and eliminate only collisions
|
|
220
|
+
const sortedWidgets = [...widgets.value].sort((a, b) => {
|
|
221
|
+
const posA = layout.value.get(a.id);
|
|
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
|
+
});
|
|
228
|
+
|
|
229
|
+
// Rearrange widgets, avoiding collisions
|
|
230
|
+
sortedWidgets.forEach((widget) => {
|
|
231
|
+
const currentPos = layout.value.get(widget.id);
|
|
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);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
widgets,
|
|
244
|
+
layout,
|
|
245
|
+
arrangeWidgetsInRows,
|
|
246
|
+
initializeLayout,
|
|
247
|
+
GRID_COLUMNS,
|
|
248
|
+
getGridRows,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as DraggableDashboard } from "./DraggableDashboard.vue";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Position type for a widget on the dashboard grid
|
|
5
|
+
*/
|
|
6
|
+
export interface DashboardWidgetPosition {
|
|
7
|
+
/**
|
|
8
|
+
* X coordinate (left to right)
|
|
9
|
+
* @minimum 0
|
|
10
|
+
*/
|
|
11
|
+
x: number;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Y coordinate (top to bottom)
|
|
15
|
+
* @minimum 0
|
|
16
|
+
*/
|
|
17
|
+
y: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Widget size type
|
|
22
|
+
*/
|
|
23
|
+
export interface DashboardWidgetSize {
|
|
24
|
+
/**
|
|
25
|
+
* Width in grid cells
|
|
26
|
+
* @minimum 1
|
|
27
|
+
* @maximum 12
|
|
28
|
+
*/
|
|
29
|
+
width: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Height in grid cells
|
|
33
|
+
* @minimum 1
|
|
34
|
+
*/
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Dashboard widget type
|
|
40
|
+
*/
|
|
41
|
+
export interface IDashboardWidget {
|
|
42
|
+
/**
|
|
43
|
+
* Unique widget identifier
|
|
44
|
+
*/
|
|
45
|
+
id: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Widget title
|
|
49
|
+
*/
|
|
50
|
+
name?: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Rendering component
|
|
54
|
+
*/
|
|
55
|
+
component: Component;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Component properties
|
|
59
|
+
*/
|
|
60
|
+
props?: Record<string, unknown>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Widget size
|
|
64
|
+
*/
|
|
65
|
+
size: DashboardWidgetSize;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Drag event type
|
|
70
|
+
*/
|
|
71
|
+
export interface DashboardDragEvent {
|
|
72
|
+
widget: IDashboardWidget;
|
|
73
|
+
position: DashboardWidgetPosition;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Grid configuration
|
|
78
|
+
*/
|
|
79
|
+
export interface DashboardGridConfig {
|
|
80
|
+
/**
|
|
81
|
+
* Number of columns in the grid
|
|
82
|
+
* @default 12
|
|
83
|
+
*/
|
|
84
|
+
columns: number;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Cell height in pixels
|
|
88
|
+
* @default 80
|
|
89
|
+
*/
|
|
90
|
+
cellHeight: number;
|
|
91
|
+
}
|
|
@@ -10,19 +10,22 @@
|
|
|
10
10
|
icon="fas fa-user-circle"
|
|
11
11
|
class="vc-user-info__icon"
|
|
12
12
|
/>
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
<Transition name="opacity">
|
|
14
|
+
<div
|
|
15
|
+
v-show="isExpanded || $isMobile.value"
|
|
16
|
+
class="vc-user-info__info"
|
|
17
|
+
>
|
|
18
|
+
<div class="vc-user-info__name">
|
|
19
|
+
{{ name || (user && "fullName" in user && user.fullName) || user?.userName }}
|
|
20
|
+
</div>
|
|
21
|
+
<div class="vc-user-info__role">
|
|
22
|
+
{{
|
|
23
|
+
(role && $t(`SHELL.USER.ROLE.${role}`)) ||
|
|
24
|
+
(user?.isAdministrator ? $t("SHELL.USER.ROLE.ADMINISTRATOR") : "")
|
|
25
|
+
}}
|
|
26
|
+
</div>
|
|
24
27
|
</div>
|
|
25
|
-
</
|
|
28
|
+
</Transition>
|
|
26
29
|
</div>
|
|
27
30
|
</template>
|
|
28
31
|
|
|
@@ -79,4 +82,14 @@ const imageHandler = computed(() => {
|
|
|
79
82
|
@apply tw-text-sm tw-text-[color:var(--user-dropdown-account-info-role-color)];
|
|
80
83
|
}
|
|
81
84
|
}
|
|
85
|
+
|
|
86
|
+
.opacity-enter-active,
|
|
87
|
+
.opacity-leave-active {
|
|
88
|
+
transition: opacity 0.3s ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.opacity-enter-from,
|
|
92
|
+
.opacity-leave-to {
|
|
93
|
+
opacity: 0;
|
|
94
|
+
}
|
|
82
95
|
</style>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
:avatar-url="avatarUrl"
|
|
22
22
|
:name="name"
|
|
23
23
|
:role="role"
|
|
24
|
-
:is-expanded="isExpanded"
|
|
24
|
+
:is-expanded="isExpanded || isHoverExpanded"
|
|
25
25
|
/>
|
|
26
26
|
<!-- <UserActions
|
|
27
27
|
:profile-menu="profileMenu"
|
|
@@ -59,7 +59,7 @@ defineProps<Props>();
|
|
|
59
59
|
|
|
60
60
|
const { t } = useI18n({ useScope: "global" });
|
|
61
61
|
const isSidebarOpened = ref(false);
|
|
62
|
-
const { isExpanded } = useMenuExpanded();
|
|
62
|
+
const { isExpanded, isHoverExpanded } = useMenuExpanded();
|
|
63
63
|
|
|
64
64
|
function handleMenuItemClick(item: IMenuItem) {
|
|
65
65
|
item.clickHandler?.();
|
|
@@ -115,7 +115,7 @@ function handleClick() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
&__actions {
|
|
118
|
-
@apply tw-
|
|
118
|
+
@apply tw-h-full;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
&__trigger {
|
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import { useLocalStorage } from "@vueuse/core";
|
|
2
|
+
import { ref } from "vue";
|
|
2
3
|
|
|
3
4
|
const STORAGE_KEY = "VC_APP_MENU_EXPANDED";
|
|
5
|
+
const HOVER_DELAY = 200;
|
|
6
|
+
|
|
7
|
+
const isHoverExpanded = ref(false);
|
|
4
8
|
|
|
5
9
|
export const useMenuExpanded = () => {
|
|
6
10
|
const isExpanded = useLocalStorage(STORAGE_KEY, true);
|
|
7
11
|
|
|
12
|
+
let expandTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
13
|
+
|
|
8
14
|
const toggleExpanded = () => {
|
|
9
15
|
isExpanded.value = !isExpanded.value;
|
|
10
16
|
};
|
|
11
17
|
|
|
18
|
+
const toggleHoverExpanded = (shouldExpand?: boolean) => {
|
|
19
|
+
if (expandTimeout) {
|
|
20
|
+
clearTimeout(expandTimeout);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (shouldExpand) {
|
|
24
|
+
expandTimeout = setTimeout(() => {
|
|
25
|
+
if (isHoverExpanded.value !== shouldExpand) {
|
|
26
|
+
isHoverExpanded.value = shouldExpand;
|
|
27
|
+
}
|
|
28
|
+
}, HOVER_DELAY);
|
|
29
|
+
} else if (shouldExpand === false) {
|
|
30
|
+
isHoverExpanded.value = shouldExpand;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
12
34
|
return {
|
|
13
35
|
isExpanded,
|
|
14
36
|
toggleExpanded,
|
|
37
|
+
isHoverExpanded,
|
|
38
|
+
toggleHoverExpanded,
|
|
15
39
|
};
|
|
16
40
|
};
|