Angular library providing dashboard components including grid layout, drag-and-drop widgets, resize handles, toolbar, config panel, layout presets, and persistence services.
155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import { Injectable, signal, computed } from '@angular/core';
|
|
import type { DbResizeDirection, DbDragPreview, DbResizePreview } from '../types/dashboard.types';
|
|
import { pixelToGridPosition, clamp } from '../utils/grid.utils';
|
|
|
|
@Injectable()
|
|
export class DashboardDragService {
|
|
// Move state
|
|
readonly isDragging = signal(false);
|
|
readonly draggedWidgetId = signal<string | null>(null);
|
|
readonly dragStartCol = signal(0);
|
|
readonly dragStartRow = signal(0);
|
|
readonly dragPreviewCol = signal(0);
|
|
readonly dragPreviewRow = signal(0);
|
|
readonly dragPreviewColSpan = signal(0);
|
|
readonly dragPreviewRowSpan = signal(0);
|
|
|
|
// Resize state
|
|
readonly isResizing = signal(false);
|
|
readonly resizeWidgetId = signal<string | null>(null);
|
|
readonly resizeDirection = signal<DbResizeDirection | null>(null);
|
|
readonly resizeStartColSpan = signal(0);
|
|
readonly resizeStartRowSpan = signal(0);
|
|
readonly resizePreviewColSpan = signal(0);
|
|
readonly resizePreviewRowSpan = signal(0);
|
|
|
|
// Computed
|
|
readonly isActive = computed(() => this.isDragging() || this.isResizing());
|
|
|
|
readonly dragPreview = computed<DbDragPreview | null>(() => {
|
|
if (!this.isDragging()) return null;
|
|
return {
|
|
col: this.dragPreviewCol(),
|
|
row: this.dragPreviewRow(),
|
|
colSpan: this.dragPreviewColSpan(),
|
|
rowSpan: this.dragPreviewRowSpan(),
|
|
};
|
|
});
|
|
|
|
readonly resizePreview = computed<DbResizePreview | null>(() => {
|
|
if (!this.isResizing()) return null;
|
|
return {
|
|
colSpan: this.resizePreviewColSpan(),
|
|
rowSpan: this.resizePreviewRowSpan(),
|
|
};
|
|
});
|
|
|
|
startDrag(widgetId: string, col: number, row: number, colSpan: number, rowSpan: number): void {
|
|
this.isDragging.set(true);
|
|
this.draggedWidgetId.set(widgetId);
|
|
this.dragStartCol.set(col);
|
|
this.dragStartRow.set(row);
|
|
this.dragPreviewCol.set(col);
|
|
this.dragPreviewRow.set(row);
|
|
this.dragPreviewColSpan.set(colSpan);
|
|
this.dragPreviewRowSpan.set(rowSpan);
|
|
}
|
|
|
|
updateDragPreview(col: number, row: number): void {
|
|
this.dragPreviewCol.set(col);
|
|
this.dragPreviewRow.set(row);
|
|
}
|
|
|
|
endDrag(): { widgetId: string; col: number; row: number } | null {
|
|
const widgetId = this.draggedWidgetId();
|
|
const col = this.dragPreviewCol();
|
|
const row = this.dragPreviewRow();
|
|
|
|
if (!widgetId) {
|
|
this.cancelAll();
|
|
return null;
|
|
}
|
|
|
|
this.isDragging.set(false);
|
|
this.draggedWidgetId.set(null);
|
|
this.dragStartCol.set(0);
|
|
this.dragStartRow.set(0);
|
|
this.dragPreviewCol.set(0);
|
|
this.dragPreviewRow.set(0);
|
|
this.dragPreviewColSpan.set(0);
|
|
this.dragPreviewRowSpan.set(0);
|
|
|
|
return { widgetId, col, row };
|
|
}
|
|
|
|
startResize(
|
|
widgetId: string,
|
|
direction: DbResizeDirection,
|
|
colSpan: number,
|
|
rowSpan: number,
|
|
): void {
|
|
this.isResizing.set(true);
|
|
this.resizeWidgetId.set(widgetId);
|
|
this.resizeDirection.set(direction);
|
|
this.resizeStartColSpan.set(colSpan);
|
|
this.resizeStartRowSpan.set(rowSpan);
|
|
this.resizePreviewColSpan.set(colSpan);
|
|
this.resizePreviewRowSpan.set(rowSpan);
|
|
}
|
|
|
|
updateResizePreview(colSpan: number, rowSpan: number): void {
|
|
this.resizePreviewColSpan.set(colSpan);
|
|
this.resizePreviewRowSpan.set(rowSpan);
|
|
}
|
|
|
|
endResize(): { widgetId: string; colSpan: number; rowSpan: number } | null {
|
|
const widgetId = this.resizeWidgetId();
|
|
const colSpan = this.resizePreviewColSpan();
|
|
const rowSpan = this.resizePreviewRowSpan();
|
|
|
|
if (!widgetId) {
|
|
this.cancelAll();
|
|
return null;
|
|
}
|
|
|
|
this.isResizing.set(false);
|
|
this.resizeWidgetId.set(null);
|
|
this.resizeDirection.set(null);
|
|
this.resizeStartColSpan.set(0);
|
|
this.resizeStartRowSpan.set(0);
|
|
this.resizePreviewColSpan.set(0);
|
|
this.resizePreviewRowSpan.set(0);
|
|
|
|
return { widgetId, colSpan, rowSpan };
|
|
}
|
|
|
|
cancelAll(): void {
|
|
this.isDragging.set(false);
|
|
this.draggedWidgetId.set(null);
|
|
this.dragStartCol.set(0);
|
|
this.dragStartRow.set(0);
|
|
this.dragPreviewCol.set(0);
|
|
this.dragPreviewRow.set(0);
|
|
this.dragPreviewColSpan.set(0);
|
|
this.dragPreviewRowSpan.set(0);
|
|
this.isResizing.set(false);
|
|
this.resizeWidgetId.set(null);
|
|
this.resizeDirection.set(null);
|
|
this.resizeStartColSpan.set(0);
|
|
this.resizeStartRowSpan.set(0);
|
|
this.resizePreviewColSpan.set(0);
|
|
this.resizePreviewRowSpan.set(0);
|
|
}
|
|
|
|
calculateGridPosition(
|
|
event: DragEvent | MouseEvent,
|
|
gridEl: HTMLElement,
|
|
columns: number,
|
|
rowHeight: number,
|
|
gap: number,
|
|
): { col: number; row: number } {
|
|
const gridRect = gridEl.getBoundingClientRect();
|
|
return pixelToGridPosition(event.clientX, event.clientY, gridRect, columns, rowHeight, gap);
|
|
}
|
|
}
|