WIP - add drag functionallity for page layouts
This commit is contained in:
@@ -16,7 +16,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border rounded-lg bg-slate-50 dark:bg-slate-900 p-4 min-h-[600px]">
|
<div class="border rounded-lg bg-slate-50 dark:bg-slate-900 p-4 min-h-[600px]">
|
||||||
<div ref="gridContainer" class="grid-stack">
|
<div
|
||||||
|
ref="gridContainer"
|
||||||
|
class="grid-stack"
|
||||||
|
@dragover.prevent="handleDragOver"
|
||||||
|
@drop="handleDrop"
|
||||||
|
>
|
||||||
<!-- Grid items will be dynamically added here -->
|
<!-- Grid items will be dynamically added here -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,12 +30,15 @@
|
|||||||
<!-- Available Fields Sidebar -->
|
<!-- Available Fields Sidebar -->
|
||||||
<div class="w-80 border-l bg-white dark:bg-slate-950 p-4 overflow-auto">
|
<div class="w-80 border-l bg-white dark:bg-slate-950 p-4 overflow-auto">
|
||||||
<h3 class="text-lg font-semibold mb-4">Available Fields</h3>
|
<h3 class="text-lg font-semibold mb-4">Available Fields</h3>
|
||||||
<p class="text-xs text-muted-foreground mb-4">Click to add field to grid</p>
|
<p class="text-xs text-muted-foreground mb-4">Click or drag to add field to grid</p>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2" id="sidebar-fields">
|
||||||
<div
|
<div
|
||||||
v-for="field in availableFields"
|
v-for="field in availableFields"
|
||||||
:key="field.id"
|
:key="field.id"
|
||||||
class="p-3 border rounded cursor-pointer bg-white dark:bg-slate-900 hover:border-primary hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
|
class="p-3 border rounded cursor-move bg-white dark:bg-slate-900 hover:border-primary hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
|
||||||
|
:data-field-id="field.id"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="handleDragStart($event, field)"
|
||||||
@click="addFieldToGrid(field)"
|
@click="addFieldToGrid(field)"
|
||||||
>
|
>
|
||||||
<div class="font-medium text-sm">{{ field.label }}</div>
|
<div class="font-medium text-sm">{{ field.label }}</div>
|
||||||
@@ -44,7 +52,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
import { GridStack } from 'gridstack'
|
import { GridStack } from 'gridstack'
|
||||||
import 'gridstack/dist/gridstack.min.css'
|
import 'gridstack/dist/gridstack.min.css'
|
||||||
import type { FieldLayoutItem } from '~/types/page-layout'
|
import type { FieldLayoutItem } from '~/types/page-layout'
|
||||||
@@ -79,9 +87,11 @@ const initGrid = () => {
|
|||||||
grid = GridStack.init({
|
grid = GridStack.init({
|
||||||
column: 6,
|
column: 6,
|
||||||
cellHeight: 80,
|
cellHeight: 80,
|
||||||
minRow: 1,
|
minRow: 10,
|
||||||
float: true,
|
float: true,
|
||||||
animate: true,
|
animate: true,
|
||||||
|
acceptWidgets: true,
|
||||||
|
disableOneColumnMode: true,
|
||||||
resizable: {
|
resizable: {
|
||||||
handles: 'e, w'
|
handles: 'e, w'
|
||||||
}
|
}
|
||||||
@@ -95,7 +105,8 @@ const initGrid = () => {
|
|||||||
// Listen for item removal
|
// Listen for item removal
|
||||||
grid.on('removed', (event, items) => {
|
grid.on('removed', (event, items) => {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const fieldId = item.el?.getAttribute('data-field-id')
|
const contentEl = item.el?.querySelector('.grid-stack-item-content')
|
||||||
|
const fieldId = contentEl?.getAttribute('data-field-id')
|
||||||
if (fieldId) {
|
if (fieldId) {
|
||||||
placedFieldIds.value.delete(fieldId)
|
placedFieldIds.value.delete(fieldId)
|
||||||
gridItems.value.delete(fieldId)
|
gridItems.value.delete(fieldId)
|
||||||
@@ -121,10 +132,46 @@ const loadLayout = (layout: FieldLayoutItem[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDragStart = (event: DragEvent, field: FieldConfig) => {
|
const handleDragStart = (event: DragEvent, field: FieldConfig) => {
|
||||||
// Store field data for drop event
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer?.setData('field', JSON.stringify(field))
|
event.dataTransfer.effectAllowed = 'copy';
|
||||||
|
event.dataTransfer.setData('application/json', JSON.stringify(field));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDragOver = (event: DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (event: DragEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const fieldData = event.dataTransfer?.getData('application/json');
|
||||||
|
if (!fieldData || !grid) return;
|
||||||
|
|
||||||
|
const field = JSON.parse(fieldData);
|
||||||
|
|
||||||
|
// Get the grid bounding rect
|
||||||
|
const gridRect = gridContainer.value?.getBoundingClientRect();
|
||||||
|
if (!gridRect) return;
|
||||||
|
|
||||||
|
// Calculate grid position from drop coordinates
|
||||||
|
const x = event.clientX - gridRect.left;
|
||||||
|
const y = event.clientY - gridRect.top;
|
||||||
|
|
||||||
|
// Convert pixels to grid coordinates (approx)
|
||||||
|
const cellWidth = gridRect.width / 6; // 6 columns
|
||||||
|
const cellHeight = 80; // from our config
|
||||||
|
|
||||||
|
const gridX = Math.floor(x / cellWidth);
|
||||||
|
const gridY = Math.floor(y / cellHeight);
|
||||||
|
|
||||||
|
// Add the field at the calculated position
|
||||||
|
addFieldToGrid(field, gridX, gridY);
|
||||||
|
};
|
||||||
|
|
||||||
const addFieldToGrid = (field: FieldConfig, x?: number, y?: number, w: number = 3, h: number = 1) => {
|
const addFieldToGrid = (field: FieldConfig, x?: number, y?: number, w: number = 3, h: number = 1) => {
|
||||||
if (!grid || placedFieldIds.value.has(field.id)) return
|
if (!grid || placedFieldIds.value.has(field.id)) return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user