Anpassung Drag & Drop-Logik in FormDesigner

This commit is contained in:
= 2025-02-27 00:16:26 +01:00
parent 3c2d1af118
commit ac240bf6a8
3 changed files with 128 additions and 64 deletions

View File

@ -1,6 +1,10 @@
<template> <template>
<div> <div class="component-container" :class="direction">
<div class="drop-zone" :class="computedDropClass" @dragover.prevent @dragenter="onDragEnter" @drop="onDrop($event, index)"></div>
<component :is="resolvedComponent" v-bind="element" @update-items="updateSchema" /> <component :is="resolvedComponent" v-bind="element" @update-items="updateSchema" />
<div class="drop-zone" :class="computedDropClass" @dragover.prevent @dragenter="onDragEnter" @drop="onDrop($event, index + 1)"></div>
</div> </div>
</template> </template>
@ -29,6 +33,14 @@ export default defineComponent({
type: Object, type: Object,
required: true, required: true,
}, },
index: {
type: Number,
required: true,
},
direction: {
type: String,
required: true,
},
}, },
computed: { computed: {
resolvedComponent() { resolvedComponent() {
@ -42,11 +54,67 @@ export default defineComponent({
}; };
return componentMap[this.element.type] || null; return componentMap[this.element.type] || null;
}, },
computedDropClass() {
return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
},
}, },
methods: { methods: {
updateSchema(newItems) { updateSchema(newItems) {
this.$emit("update-schema", newItems); // 🔥 Leitet die Änderung weiter this.$emit("update-schema", newItems); // 🔥 Leitet die Änderung weiter
}, },
onDragEnter(event) {
event.preventDefault();
},
onDrop(event, position) {
event.preventDefault();
const droppedType = event.dataTransfer.getData("text/plain");
const newElement = this.createElementByType(droppedType);
if (newElement) {
this.$emit("insert-item", { position, newElement });
}
},
createElementByType(type) {
switch (type) {
case "InputText":
return { type: "InputText", label: "Neues Textfeld", placeholder: "Hier eingeben..." };
case "InputNumber":
return { type: "InputNumber", label: "Neue Zahl", placeholder: "0" };
case "InputDate":
return { type: "InputDate", label: "Neues Datum" };
case "Button":
return { type: "Button", label: "Klicken" };
case "Label":
return { type: "Label", value: "Neues Label" };
case "FlexLayout":
return { type: "FlexLayout", direction: "vertical", items: [] };
default:
console.warn("⚠ Unbekannter Typ:", type);
return null;
}
},
}, },
}); });
</script> </script>
<style scoped>
.component-container {
display: flex;
align-items: center;
}
/* Drop-Zonen für horizontale Layouts */
.drop-zone-horizontal {
width: 10px;
height: 100%;
background: rgba(0, 0, 255, 0.3);
cursor: pointer;
}
/* Drop-Zonen für vertikale Layouts */
.drop-zone-vertical {
height: 10px;
width: 100%;
background: rgba(0, 0, 255, 0.3);
cursor: pointer;
}
</style>

View File

@ -1,8 +1,6 @@
<template> <template>
<div> <div class="flex-layout" :class="computedDirection">
<div class="flex-layout" :class="[computedDirection, { 'drag-over': dragOverActive }]" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop="onDrop"> <component-renderer v-for="(item, key) in items" :key="key" :element="item" :index="key" :direction="direction" @update-schema="updateChildItems(key, $event)" @insert-item="insertItem" />
<component-renderer v-for="(item, key) in items" :key="key" :element="item" @update-schema="updateChildItems(key, $event)" />
</div>
</div> </div>
</template> </template>
@ -16,7 +14,6 @@ export default defineComponent({
data() { data() {
return { return {
dragOverActive: false, dragOverActive: false,
hasParentFlexLayout: false,
}; };
}, },
props: { props: {
@ -33,67 +30,52 @@ export default defineComponent({
computedDirection() { computedDirection() {
return this.direction === "horizontal" ? "horizontal" : "vertical"; return this.direction === "horizontal" ? "horizontal" : "vertical";
}, },
isInnerMostLayout() { computedDropClass() {
return !this.items.some((item) => item.type === "FlexLayout") && this.hasParentFlexLayout; return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
},
isOuterLayout() {
return !this.hasParentFlexLayout;
}, },
}, },
methods: { methods: {
/**
* 🔄 Aktualisiert verschachtelte FlexLayouts
*/
updateChildItems(index, newItems) { updateChildItems(index, newItems) {
const updatedItems = [...this.items]; const updatedItems = [...this.items];
updatedItems[index] = { ...updatedItems[index], items: newItems }; // Tiefere Rekursion möglich updatedItems[index] = { ...updatedItems[index], items: newItems };
this.$emit("update-items", updatedItems); // 🔥 Weitergabe an Parent this.$emit("update-items", updatedItems);
}, },
/**
* 🚀 Wenn ein Drag über das Layout geht
*/
onDragOver(event) { onDragOver(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); this.dragOverActive = true;
if (this.isInnerMostLayout) {
this.dragOverActive = true;
} else if (this.isOuterLayout) {
this.dragOverActive = true;
} else {
this.dragOverActive = false;
}
}, },
onDragEnter(event) {
/**
* 📌 Wenn ein Element in das FlexLayout gedroppt wird
*/
onDrop(event) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); },
this.dragOverActive = false; onDrop(event, position) {
event.preventDefault();
console.log(`📌 onDrop aufgerufen bei Position: ${position}`); // 🔥 Debugging-Log
const droppedType = event.dataTransfer.getData("text/plain"); const droppedType = event.dataTransfer.getData("text/plain");
console.log(`📦 Gedropptes Element: ${droppedType}`); // 🔥 Zeigt an, was gedroppt wurde
const newElement = this.createElementByType(droppedType); const newElement = this.createElementByType(droppedType);
if (newElement) { if (newElement) {
const updatedItems = [...this.items, newElement]; // Neue Liste mit Element console.log(`✅ Neues Element erstellt:`, newElement);
this.$emit("update-items", updatedItems); // 🔥 An Parent weiterleiten this.insertItem({ position, newElement });
} else {
console.warn("⚠ Kein gültiges Element erstellt!");
} }
}, },
insertItem({ position, newElement }) {
console.log(`🔧 Inserting at position: ${position}`, newElement); // 🔥 Debugging-Log
/** const updatedItems = [...this.items];
* Wenn das Drag das Layout verlässt
*/ // 🛠 Stelle sicher, dass `position` innerhalb der Liste bleibt
onDragLeave(event) { if (position < 0) position = 0;
event.preventDefault(); if (position > updatedItems.length) position = updatedItems.length;
event.stopPropagation();
this.dragOverActive = false; updatedItems.splice(position, 0, newElement); // 🔥 Element an richtiger Stelle einfügen
console.log("🔄 Updated Items:", updatedItems); // 🔥 Zeigt die neue Liste in der Konsole
this.$emit("update-items", updatedItems); // 🔥 Änderungen an Parent weitergeben
}, },
/**
* 🔧 Erstellt das passende Element basierend auf dem Typ
*/
createElementByType(type) { createElementByType(type) {
switch (type) { switch (type) {
case "InputText": case "InputText":
@ -114,16 +96,6 @@ export default defineComponent({
} }
}, },
}, },
mounted() {
let parent = this.$parent;
while (parent) {
if (parent.$options.name === "FlexLayoutRenderer") {
this.hasParentFlexLayout = true;
break;
}
parent = parent.$parent;
}
},
}); });
</script> </script>
@ -134,6 +106,7 @@ export default defineComponent({
min-height: 50px; min-height: 50px;
} }
/* Layouts richtig ausrichten */
.horizontal { .horizontal {
flex-direction: row; flex-direction: row;
} }
@ -142,8 +115,21 @@ export default defineComponent({
flex-direction: column; flex-direction: column;
} }
.flex-layout.drag-over { /* Drop-Zonen für horizontale Layouts (zwischen Spalten) */
border-left: 5px solid var(--bs-primary); .drop-zone-horizontal {
background: rgba(0, 0, 255, 0.03); width: 8px; /* Dünne vertikale Linie für horizontale Anordnung */
height: auto; /* ✅ Höhe passt sich dem Inhalt an */
align-self: center; /* ✅ Zentriert die Drop-Zone vertikal */
background: rgba(0, 0, 255, 0.3);
cursor: pointer;
}
/* Drop-Zonen für vertikale Layouts (zwischen Reihen) */
.drop-zone-vertical {
height: 6px; /* Dünne horizontale Linie für vertikale Anordnung */
width: 100%;
background: rgba(0, 0, 255, 0.3);
margin: 2px 0; /* ✅ Kleinere Abstände */
cursor: pointer;
} }
</style> </style>

View File

@ -21,7 +21,7 @@
</a> </a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-4"> <a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-3">
<svg class="bi bi-diagram-3 pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16"> <svg class="bi bi-diagram-3 pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
<path <path
fill-rule="evenodd" fill-rule="evenodd"
@ -32,7 +32,7 @@
</a> </a>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-3"> <a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-4">
<svg class="bi bi-filetype-json pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16"> <svg class="bi bi-filetype-json pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
<path <path
fill-rule="evenodd" fill-rule="evenodd"
@ -93,7 +93,7 @@
<p>Content for tab 3.</p> <p>Content for tab 3.</p>
</div> </div>
<div id="tab-4" class="tab-pane" role="tabpanel"> <div id="tab-4" class="tab-pane" role="tabpanel">
<p>Content for tab 4.</p> <textarea class="pro-textarea" v-model="jsonOutput" readonly></textarea>
</div> </div>
</div> </div>
</div> </div>
@ -212,6 +212,11 @@ export default {
document.removeEventListener("mouseup", this.stopResize); document.removeEventListener("mouseup", this.stopResize);
}, },
}, },
computed: {
jsonOutput() {
return JSON.stringify(this.formJSON, null, 2); // JSON sauber formatieren
},
},
}; };
</script> </script>
@ -259,4 +264,9 @@ export default {
cursor: ns-resize; cursor: ns-resize;
user-select: none; user-select: none;
} }
.pro-textarea {
height: 100dvh;
width: 100dvh;
}
</style> </style>