Anpassung Drag & Drop-Logik in FormDesigner
This commit is contained in:
parent
3c2d1af118
commit
ac240bf6a8
@ -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>
|
||||||
|
|||||||
@ -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();
|
|
||||||
if (this.isInnerMostLayout) {
|
|
||||||
this.dragOverActive = true;
|
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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user