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(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(null); readonly resizeDirection = signal(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(() => { if (!this.isDragging()) return null; return { col: this.dragPreviewCol(), row: this.dragPreviewRow(), colSpan: this.dragPreviewColSpan(), rowSpan: this.dragPreviewRowSpan(), }; }); readonly resizePreview = computed(() => { 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); } }