Enhance drag-and-drop functionality in Dashboard: Update styles for dragging state in DashboardWidget, including opacity and box-shadow adjustments. Improve LoggedInView by adding drop indicators for better user experience during widget rearrangement. Refactor drag-and-drop logic to maintain visual cues and ensure smoother interactions.

This commit is contained in:
Torsten Schulz (local)
2026-01-30 10:30:07 +01:00
parent 8fd15614af
commit 3999b17e88
2 changed files with 70 additions and 8 deletions

View File

@@ -264,10 +264,13 @@ export default {
border-radius: 8px; border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06); box-shadow: 0 1px 3px rgba(0,0,0,0.06);
overflow: hidden; overflow: hidden;
transition: opacity 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
} }
.dashboard-widget.is-dragging { .dashboard-widget.is-dragging {
opacity: 0.7; opacity: 0.5;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: 0 8px 24px rgba(0,0,0,0.25);
transform: rotate(2deg);
cursor: grabbing;
} }
.dashboard-widget__titlebar { .dashboard-widget__titlebar {

View File

@@ -63,9 +63,19 @@
@drop="onGridDrop" @drop="onGridDrop"
> >
<template v-for="(w, index) in widgets" :key="w.id"> <template v-for="(w, index) in widgets" :key="w.id">
<!-- Platzhalter vor dem Widget, wenn hier eingefügt werden soll -->
<div
v-if="dragOverIndex === index && draggedIndex !== null && draggedIndex !== index"
class="dashboard-grid-cell dashboard-drop-indicator"
>
<div class="drop-indicator-line"></div>
</div>
<div <div
class="dashboard-grid-cell" class="dashboard-grid-cell"
:class="{ 'drop-target': dragOverIndex === index && draggedIndex !== index }" :class="{
'drop-target': dragOverIndex === index && draggedIndex !== index,
'drag-source': draggedIndex === index
}"
@dragover.prevent="() => setDropTarget(index)" @dragover.prevent="() => setDropTarget(index)"
@dragleave="clearDropTarget" @dragleave="clearDropTarget"
@drop="onDrop(index)" @drop="onDrop(index)"
@@ -77,7 +87,7 @@
:endpoint="effectiveEndpoint(w)" :endpoint="effectiveEndpoint(w)"
:request-counter="widgetRequestCounter(index)" :request-counter="widgetRequestCounter(index)"
@drag-start="() => (draggedIndex = index)" @drag-start="() => (draggedIndex = index)"
@drag-end="() => (draggedIndex = null)" @drag-end="() => onDragEnd()"
/> />
<div v-else class="dashboard-widget-edit"> <div v-else class="dashboard-widget-edit">
<div class="widget-edit-fields"> <div class="widget-edit-fields">
@@ -98,6 +108,13 @@
</button> </button>
</div> </div>
</div> </div>
<!-- Platzhalter am Ende, wenn dort eingefügt werden soll -->
<div
v-if="dragOverIndex === widgets.length && draggedIndex !== null"
class="dashboard-grid-cell dashboard-drop-indicator"
>
<div class="drop-indicator-line"></div>
</div>
</template> </template>
</div> </div>
@@ -240,13 +257,18 @@ export default {
await this.saveConfig(); await this.saveConfig();
}, },
setDropTarget(index) { setDropTarget(index) {
this.dragOverIndex = index; if (this.draggedIndex !== null && this.draggedIndex !== index) {
this.dragOverIndex = index;
}
}, },
clearDropTarget() { clearDropTarget() {
this.dragOverIndex = null; // Nicht sofort löschen, damit der Indikator sichtbar bleibt
// Wird beim Drop oder Drag-End gelöscht
}, },
onGridDragover() { onGridDragover() {
this.dragOverIndex = this.widgets.length; if (this.draggedIndex !== null) {
this.dragOverIndex = this.widgets.length;
}
}, },
async onGridDrop() { async onGridDrop() {
if (this.draggedIndex == null) return; if (this.draggedIndex == null) return;
@@ -277,6 +299,10 @@ export default {
this.draggedIndex = null; this.draggedIndex = null;
this.dragOverIndex = null; this.dragOverIndex = null;
await this.saveConfig(); await this.saveConfig();
},
onDragEnd() {
this.draggedIndex = null;
this.dragOverIndex = null;
} }
} }
}; };
@@ -374,7 +400,7 @@ export default {
.dashboard-grid { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
grid-auto-rows: 200px; grid-auto-rows: 420px;
gap: 20px; gap: 20px;
} }
@@ -382,6 +408,7 @@ export default {
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: all 0.2s ease;
} }
.dashboard-grid-cell > * { .dashboard-grid-cell > * {
@@ -395,6 +422,38 @@ export default {
outline: 2px dashed #0d6efd; outline: 2px dashed #0d6efd;
outline-offset: 4px; outline-offset: 4px;
border-radius: 8px; border-radius: 8px;
background-color: rgba(13, 110, 253, 0.05);
}
.dashboard-grid-cell.drag-source {
opacity: 0.4;
}
.dashboard-drop-indicator {
display: flex;
align-items: center;
justify-content: center;
min-height: 0;
padding: 0;
}
.drop-indicator-line {
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, #0d6efd, transparent);
border-radius: 2px;
animation: pulse-drop-indicator 1.5s ease-in-out infinite;
}
@keyframes pulse-drop-indicator {
0%, 100% {
opacity: 0.5;
transform: scaleX(0.8);
}
50% {
opacity: 1;
transform: scaleX(1);
}
} }
.dashboard-widget-edit { .dashboard-widget-edit {