Alles und nix
This commit is contained in:
parent
c374ef8074
commit
0c84fb7b6d
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<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" />
|
|
||||||
|
|
||||||
<div class="drop-zone" :class="computedDropClass" @dragover.prevent @dragenter="onDragEnter" @drop="onDrop($event, index + 1)"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent, defineAsyncComponent } from "vue";
|
|
||||||
|
|
||||||
const InputTextRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputTextRenderer.vue"));
|
|
||||||
const InputNumberRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputNumberRenderer.vue"));
|
|
||||||
const InputDateRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/InputDateRenderer.vue"));
|
|
||||||
const LabelRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/LabelRenderer.vue"));
|
|
||||||
const ButtonRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/ButtonRenderer.vue"));
|
|
||||||
const FlexLayoutRenderer = defineAsyncComponent(() => import("@/components/formDesigner/formRenderer/FlexLayoutRenderer.vue"));
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "ComponentRenderer",
|
|
||||||
components: {
|
|
||||||
InputTextRenderer,
|
|
||||||
InputNumberRenderer,
|
|
||||||
InputDateRenderer,
|
|
||||||
LabelRenderer,
|
|
||||||
ButtonRenderer,
|
|
||||||
FlexLayoutRenderer, // Jetzt wird es erst geladen, wenn nötig
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
element: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
resolvedComponent() {
|
|
||||||
const componentMap = {
|
|
||||||
FlexLayout: "FlexLayoutRenderer",
|
|
||||||
Label: "LabelRenderer",
|
|
||||||
InputText: "InputTextRenderer",
|
|
||||||
InputNumber: "InputNumberRenderer",
|
|
||||||
InputDate: "InputDateRenderer",
|
|
||||||
Button: "ButtonRenderer",
|
|
||||||
};
|
|
||||||
return componentMap[this.element.type] || null;
|
|
||||||
},
|
|
||||||
computedDropClass() {
|
|
||||||
return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateSchema(newItems) {
|
|
||||||
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>
|
|
||||||
|
|
||||||
<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,144 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="accordion-1" class="accordion" role="tablist">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="false" aria-controls="accordion-1 .item-1">Steuerelemente</button></h2>
|
|
||||||
<div class="accordion-collapse collapse item-1" role="tabpanel" data-bs-parent="#accordion-1">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputText')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-input-cursor-text pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M5 2a.5.5 0 0 1 .5-.5c.862 0 1.573.287 2.06.566.174.099.321.198.44.286.119-.088.266-.187.44-.286A4.165 4.165 0 0 1 10.5 1.5a.5.5 0 0 1 0 1c-.638 0-1.177.213-1.564.434a3.49 3.49 0 0 0-.436.294V7.5H9a.5.5 0 0 1 0 1h-.5v4.272c.1.08.248.187.436.294.387.221.926.434 1.564.434a.5.5 0 0 1 0 1 4.165 4.165 0 0 1-2.06-.566A4.561 4.561 0 0 1 8 13.65a4.561 4.561 0 0 1-.44.285 4.165 4.165 0 0 1-2.06.566.5.5 0 0 1 0-1c.638 0 1.177-.213 1.564-.434.188-.107.335-.214.436-.294V8.5H7a.5.5 0 0 1 0-1h.5V3.228a3.49 3.49 0 0 0-.436-.294A3.166 3.166 0 0 0 5.5 2.5.5.5 0 0 1 5 2"
|
|
||||||
></path>
|
|
||||||
<path d="M10 5h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4v1h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4zM6 5V4H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4v-1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Textfeld</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-1 mt-1" />
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputNumber')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-0-square pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path
|
|
||||||
d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"
|
|
||||||
></path>
|
|
||||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Nummernfeld</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-1 mt-1" />
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'InputDate')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-calendar3 pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"></path>
|
|
||||||
<path
|
|
||||||
d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Datumsfeld</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-1 mt-1" />
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'Button')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-menu-button pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M0 1.5A1.5 1.5 0 0 1 1.5 0h8A1.5 1.5 0 0 1 11 1.5v2A1.5 1.5 0 0 1 9.5 5h-8A1.5 1.5 0 0 1 0 3.5zM1.5 1a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5z"></path>
|
|
||||||
<path
|
|
||||||
d="m7.823 2.823-.396-.396A.25.25 0 0 1 7.604 2h.792a.25.25 0 0 1 .177.427l-.396.396a.25.25 0 0 1-.354 0zM0 8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm1 3v2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2zm14-1V8a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2zM2 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Button</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">Layoutelemente</button></h2>
|
|
||||||
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'FlexLayout')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-grid-1x2 pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M6 1H1v14h5zm9 0h-5v5h5zm0 9v5h-5v-5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1z"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Flexibles Layout</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-1 mt-1" />
|
|
||||||
<div class="row ms-0 me-0" draggable="true" @dragstart="startDrag($event, 'Label')">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-alphabet-uppercase pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path
|
|
||||||
d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766h-2.55ZM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954H7.18Zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Label</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-3" aria-expanded="false" aria-controls="accordion-1 .item-3">Templates</button></h2>
|
|
||||||
<div class="accordion-collapse collapse item-3" role="tabpanel" data-bs-parent="#accordion-1">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<div class="row ms-0 me-0">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-chevron-down pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Accordion</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-1 mt-1" />
|
|
||||||
<div class="row ms-0 me-0">
|
|
||||||
<div class="col d-xxl-flex justify-content-xxl-start align-items-xxl-center">
|
|
||||||
<svg class="bi bi-floppy pe-0 me-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M11 2H9v3h2z"></path>
|
|
||||||
<path
|
|
||||||
d="M1.5 0h11.586a1.5 1.5 0 0 1 1.06.44l1.415 1.414A1.5 1.5 0 0 1 16 2.914V14.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13A1.5 1.5 0 0 1 1.5 0M1 1.5v13a.5.5 0 0 0 .5.5H2v-4.5A1.5 1.5 0 0 1 3.5 9h9a1.5 1.5 0 0 1 1.5 1.5V15h.5a.5.5 0 0 0 .5-.5V2.914a.5.5 0 0 0-.146-.353l-1.415-1.415A.5.5 0 0 0 13.086 1H13v4.5A1.5 1.5 0 0 1 11.5 7h-7A1.5 1.5 0 0 1 3 5.5V1H1.5a.5.5 0 0 0-.5.5m3 4a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5V1H4zM3 15h10v-4.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<p class="mb-0" style="font-size: 14px">Gespeicherter Eintrag 1</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "FormDesignerToolsComponent",
|
|
||||||
// Verwendete Komponenten
|
|
||||||
components: {},
|
|
||||||
// Props für die Komponente
|
|
||||||
props: {},
|
|
||||||
|
|
||||||
// Daten der Komponente
|
|
||||||
data() {},
|
|
||||||
|
|
||||||
// Computed Properties
|
|
||||||
computed: {},
|
|
||||||
|
|
||||||
// Methoden
|
|
||||||
methods: {
|
|
||||||
startDrag(event, type) {
|
|
||||||
console.log("Dragging:", type);
|
|
||||||
event.dataTransfer.setData("text/plain", type); // Speichere das Objekt als JSON
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Lifecycle-Hooks
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.accordion-button:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex-layout" :class="computedDirection">
|
|
||||||
<component-renderer v-for="(item, key) in items" :key="key" :element="item" :index="key" :direction="direction" @update-schema="updateChildItems(key, $event)" @insert-item="insertItem" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
import ComponentRenderer from "@/components/formDesigner/ComponentRenderer.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: "FlexLayoutRenderer",
|
|
||||||
components: { ComponentRenderer },
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
dragOverActive: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
direction: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
computedDirection() {
|
|
||||||
return this.direction === "horizontal" ? "horizontal" : "vertical";
|
|
||||||
},
|
|
||||||
computedDropClass() {
|
|
||||||
return this.direction === "horizontal" ? "drop-zone-horizontal" : "drop-zone-vertical";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateChildItems(index, newItems) {
|
|
||||||
const updatedItems = [...this.items];
|
|
||||||
updatedItems[index] = { ...updatedItems[index], items: newItems };
|
|
||||||
this.$emit("update-items", updatedItems);
|
|
||||||
},
|
|
||||||
onDragOver(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.dragOverActive = true;
|
|
||||||
},
|
|
||||||
onDragEnter(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
},
|
|
||||||
onDrop(event, position) {
|
|
||||||
event.preventDefault();
|
|
||||||
console.log(`📌 onDrop aufgerufen bei Position: ${position}`); // 🔥 Debugging-Log
|
|
||||||
|
|
||||||
const droppedType = event.dataTransfer.getData("text/plain");
|
|
||||||
console.log(`📦 Gedropptes Element: ${droppedType}`); // 🔥 Zeigt an, was gedroppt wurde
|
|
||||||
|
|
||||||
const newElement = this.createElementByType(droppedType);
|
|
||||||
if (newElement) {
|
|
||||||
console.log(`✅ Neues Element erstellt:`, newElement);
|
|
||||||
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];
|
|
||||||
|
|
||||||
// 🛠 Stelle sicher, dass `position` innerhalb der Liste bleibt
|
|
||||||
if (position < 0) position = 0;
|
|
||||||
if (position > updatedItems.length) position = updatedItems.length;
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
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>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.flex-layout {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
min-height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layouts richtig ausrichten */
|
|
||||||
.horizontal {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Drop-Zonen für horizontale Layouts (zwischen Spalten) */
|
|
||||||
.drop-zone-horizontal {
|
|
||||||
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>
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="row ms-0 ps-0 mb-4" style="text-align: left">
|
|
||||||
<div class="col-3 ps-0 pe-0 me-5" style="border: 1px solid rgba(39, 68, 93, 0.39); width: 100%">
|
|
||||||
<div @click.stop="toggleTable" class="d-inline-flex justify-content-between align-items-center align-items-sm-center align-items-md-center align-items-lg-center align-items-xl-center align-items-xxl-center ps-3" style="width: 100%; height: 50px; text-align: left; background: #71bbb2">
|
|
||||||
<div class="d-inline-flex align-items-xl-center">
|
|
||||||
<h1 style="font-size: 22px; text-align: left; width: auto">{{ tableTitleProp }} ({{ viewProp?.length || 0 }})</h1>
|
|
||||||
<svg class="pointer bi bi-plus-circle-fill ps-0 ms-3" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 18px; color: var(--bs-primary) !important">
|
|
||||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div style="text-align: right; width: 50px">
|
|
||||||
<svg v-if="showTable" @click.stop="toggleTable" :class="tableIcon" class="pointer bi me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 25px">
|
|
||||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"></path>
|
|
||||||
</svg>
|
|
||||||
<svg v-if="!showTable" @click.stop="toggleTable" class="pointer bi bi-chevron-right me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 25px">
|
|
||||||
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="showTable" class="table-responsive">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style="width: 80%">Name</th>
|
|
||||||
<th>Änderungsdatum</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr class="pro-row" @click="setActivePage('FormDesignerLayout', 'Ansicht (' + row.name + ')', {}, 'process')" v-for="row in viewProp" :key="row.name + '-' + row.changed">
|
|
||||||
<td>{{ row.name }}</td>
|
|
||||||
<td>{{ row.changed }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { useUiStore } from "../../store/uiStore.js";
|
|
||||||
export default {
|
|
||||||
name: "ProcessTableComponent",
|
|
||||||
// Verwendete Komponenten
|
|
||||||
components: {},
|
|
||||||
// Props für die Komponente
|
|
||||||
props: {
|
|
||||||
viewProp: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
tableTitleProp: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Daten der Komponente
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showTable: false,
|
|
||||||
uiStore: useUiStore(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Computed Propertiesprocesses.data
|
|
||||||
computed: {
|
|
||||||
openComponents() {
|
|
||||||
return this.uiStore.openComponents;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Methoden
|
|
||||||
methods: {
|
|
||||||
toggleTable() {
|
|
||||||
this.showTable = !this.showTable;
|
|
||||||
},
|
|
||||||
setActivePage(componentName, displayName, props, menuItem) {
|
|
||||||
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Lifecycle-Hooks
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.pro-row:hover td {
|
|
||||||
background-color: rgba(113, 187, 178, 0.3);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,14 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="width: 4.5rem; height: 100%; margin-right: 4.5rem; top: 0; background: var(--bs-primary); height: 100dvh">
|
<div
|
||||||
|
style="
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bs-primary);
|
||||||
|
height: 100dvh;
|
||||||
|
"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
<ul class="nav nav-pills flex-column text-center nav-flush mb-auto">
|
<ul class="nav nav-pills flex-column text-center nav-flush mb-auto">
|
||||||
<li v-for="(item, index) in menuItems" :key="index" class="nav-item" @mouseover="showPopover($event)" @mouseleave="hidePopoverWithDelay" :class="{ active: isActive(item.menuItem) }" @click="setActivePage(item.componentName, item.text)">
|
<li
|
||||||
|
v-for="(item, index) in menuItems"
|
||||||
|
:key="index"
|
||||||
|
class="nav-item"
|
||||||
|
@mouseover="showPopover($event)"
|
||||||
|
@mouseleave="hidePopoverWithDelay"
|
||||||
|
:class="{ active: isActive(item.menuItem) }"
|
||||||
|
@click="setActivePage(item.componentName, item.text)"
|
||||||
|
>
|
||||||
<a class="nav-link py-3 border-bottom rounded-0" href="#">
|
<a class="nav-link py-3 border-bottom rounded-0" href="#">
|
||||||
<Icon :icon="item.icon" :style="{ fontSize: '20px' }" />
|
<Icon :icon="item.icon" :style="{ fontSize: '20px' }" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- Menu-Popover-->
|
<!-- Menu-Popover-->
|
||||||
<div v-if="popoverVisible" class="popover-box" :style="popoverStyle" @mouseenter="popoverVisible = true" @mouseleave="hidePopoverWithDelay">
|
<div
|
||||||
|
v-if="popoverVisible"
|
||||||
|
class="popover-box"
|
||||||
|
:style="popoverStyle"
|
||||||
|
@mouseenter="popoverVisible = true"
|
||||||
|
@mouseleave="hidePopoverWithDelay"
|
||||||
|
>
|
||||||
<p>{{ popoverText }}</p>
|
<p>{{ popoverText }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -31,12 +54,42 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menuItems: [
|
menuItems: [
|
||||||
{ icon: "carbon:home", text: "Dashboard", componentName: "DevelopmentDashboardComponent", menuItem: "home" },
|
{
|
||||||
{ icon: "carbon:ibm-process-mining", text: "Prozesse", componentName: "ProcessComponent", menuItem: "process" },
|
icon: "carbon:home",
|
||||||
{ icon: "carbon:user-multiple", text: "Benutzer", componentName: "UserComponent", menuItem: "users" },
|
text: "Dashboard",
|
||||||
{ icon: "carbon:building", text: "Firmen", componentName: "CompanyComponent", menuItem: "company" },
|
componentName: "DevelopmentDashboardComponent",
|
||||||
{ icon: "carbon:object-storage", text: "Objekte", componentName: "StorageComponent", menuItem: "storage" },
|
menuItem: "home",
|
||||||
{ icon: "carbon:enumeration-usage", text: "Enums", componentName: "StatisticsComponent", menuItem: "stats" },
|
},
|
||||||
|
{
|
||||||
|
icon: "carbon:ibm-process-mining",
|
||||||
|
text: "Prozesse",
|
||||||
|
componentName: "ProcessComponent",
|
||||||
|
menuItem: "process",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "carbon:user-multiple",
|
||||||
|
text: "Benutzer",
|
||||||
|
componentName: "UserComponent",
|
||||||
|
menuItem: "users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "carbon:building",
|
||||||
|
text: "Firmen",
|
||||||
|
componentName: "CompanyComponent",
|
||||||
|
menuItem: "company",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "carbon:object-storage",
|
||||||
|
text: "Objekte",
|
||||||
|
componentName: "StorageComponent",
|
||||||
|
menuItem: "storage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "carbon:enumeration-usage",
|
||||||
|
text: "Enums",
|
||||||
|
componentName: "StatisticsComponent",
|
||||||
|
menuItem: "stats",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
uiStore: useUiStore(),
|
uiStore: useUiStore(),
|
||||||
popoverVisible: false,
|
popoverVisible: false,
|
||||||
@ -48,7 +101,9 @@ export default {
|
|||||||
// Computed Properties
|
// Computed Properties
|
||||||
computed: {
|
computed: {
|
||||||
activeMenuItem() {
|
activeMenuItem() {
|
||||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
const activeComponent = this.uiStore.openComponents.find(
|
||||||
|
(comp) => comp.active
|
||||||
|
);
|
||||||
return activeComponent ? activeComponent.menuItem : null;
|
return activeComponent ? activeComponent.menuItem : null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -66,7 +121,9 @@ export default {
|
|||||||
top: `${rect.top + window.scrollY + 10}px`, // Höhe relativ zur Seite
|
top: `${rect.top + window.scrollY + 10}px`, // Höhe relativ zur Seite
|
||||||
left: `${rect.right + 10}px`, // 10px rechts vom Element
|
left: `${rect.right + 10}px`, // 10px rechts vom Element
|
||||||
};
|
};
|
||||||
const index = [...triggerElement.parentElement.children].indexOf(triggerElement);
|
const index = [...triggerElement.parentElement.children].indexOf(
|
||||||
|
triggerElement
|
||||||
|
);
|
||||||
this.popoverText = this.menuItems[index]?.text || "Info";
|
this.popoverText = this.menuItems[index]?.text || "Info";
|
||||||
}
|
}
|
||||||
this.popoverVisible = true;
|
this.popoverVisible = true;
|
||||||
@ -110,7 +167,7 @@ export default {
|
|||||||
|
|
||||||
.popover-box {
|
.popover-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: var(--bs-primary);
|
background: #142330;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
@ -2,10 +2,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<slot name="topbar"> </slot>
|
<slot name="topbar"> </slot>
|
||||||
<div class="d-inline-flex">
|
<div class="d-inline-flex">
|
||||||
<slot name="sidebar">test</slot>
|
<slot name="sidebar"></slot>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<template #default>
|
<template #default>
|
||||||
<component :is="activePageComponent" v-bind="currentProps"></component>
|
<component
|
||||||
|
:is="activePageComponent"
|
||||||
|
v-bind="currentProps"
|
||||||
|
></component>
|
||||||
</template>
|
</template>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<p>Lade Seite...</p>
|
<p>Lade Seite...</p>
|
||||||
@ -35,18 +38,32 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
activePageComponent() {
|
activePageComponent() {
|
||||||
const components = {
|
const components = {
|
||||||
Dashboard: defineAsyncComponent(() => import("@/pages/DevelopmentDashboard.vue")),
|
Dashboard: defineAsyncComponent(() =>
|
||||||
TestComponent: defineAsyncComponent(() => import("@/pages/TestComponent.vue")),
|
import("@/pages/DevelopmentDashboard.vue")
|
||||||
ProcessOverviewComponent: defineAsyncComponent(() => import("@/pages/ProcessOverviewComponent.vue")),
|
),
|
||||||
FormDesignerLayout: defineAsyncComponent(() => import("@/layouts/FormDesignerLayout.vue")),
|
TestComponent: defineAsyncComponent(() =>
|
||||||
|
import("@/pages/TestComponent.vue")
|
||||||
|
),
|
||||||
|
ProcessOverviewComponent: defineAsyncComponent(() =>
|
||||||
|
import("@/modules/process/pages/ProcessOverviewComponent.vue")
|
||||||
|
),
|
||||||
|
FormDesignerLayout: defineAsyncComponent(() =>
|
||||||
|
import("@/modules/process/layouts/FormDesignerLayout.vue")
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
const activeComponent = this.uiStore.openComponents.find(
|
||||||
|
(comp) => comp.active
|
||||||
|
);
|
||||||
|
|
||||||
return components[activeComponent?.componentName] || components["Dashboard"];
|
return (
|
||||||
|
components[activeComponent?.componentName] || components["Dashboard"]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
currentProps() {
|
currentProps() {
|
||||||
const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
|
const activeComponent = this.uiStore.openComponents.find(
|
||||||
|
(comp) => comp.active
|
||||||
|
);
|
||||||
return activeComponent?.props || {};
|
return activeComponent?.props || {};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,272 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
|
||||||
<div style="height: 100%">
|
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<a class="nav-link active" role="tab" data-bs-toggle="tab" href="#tab-1">
|
|
||||||
<svg class="bi bi-file-text pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" style="font-size: 16px">
|
|
||||||
<path d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"></path>
|
|
||||||
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"></path>
|
|
||||||
</svg>
|
|
||||||
Verwaltung
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-2">
|
|
||||||
<svg class="bi bi-card-heading pe-0 me-2" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">
|
|
||||||
<path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"></path>
|
|
||||||
<path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"></path>
|
|
||||||
</svg>
|
|
||||||
Editor
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<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">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
Objektstruktur
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<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">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M14 4.5V11h-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM4.151 15.29a1.176 1.176 0 0 1-.111-.449h.764a.578.578 0 0 0 .255.384c.07.049.154.087.25.114.095.028.201.041.319.041.164 0 .301-.023.413-.07a.559.559 0 0 0 .255-.193.507.507 0 0 0 .084-.29.387.387 0 0 0-.152-.326c-.101-.08-.256-.144-.463-.193l-.618-.143a1.72 1.72 0 0 1-.539-.214 1.001 1.001 0 0 1-.352-.367 1.068 1.068 0 0 1-.123-.524c0-.244.064-.457.19-.639.128-.181.304-.322.528-.422.225-.1.484-.149.777-.149.304 0 .564.05.779.152.217.102.384.239.5.41.12.17.186.359.2.566h-.75a.56.56 0 0 0-.12-.258.624.624 0 0 0-.246-.181.923.923 0 0 0-.37-.068c-.216 0-.387.05-.512.152a.472.472 0 0 0-.185.384c0 .121.048.22.144.3a.97.97 0 0 0 .404.175l.621.143c.217.05.406.12.566.211a1 1 0 0 1 .375.358c.09.148.135.335.135.56 0 .247-.063.466-.188.656a1.216 1.216 0 0 1-.539.439c-.234.105-.52.158-.858.158-.254 0-.476-.03-.665-.09a1.404 1.404 0 0 1-.478-.252 1.13 1.13 0 0 1-.29-.375Zm-3.104-.033a1.32 1.32 0 0 1-.082-.466h.764a.576.576 0 0 0 .074.27.499.499 0 0 0 .454.246c.19 0 .33-.055.422-.164.091-.11.137-.265.137-.466v-2.745h.791v2.725c0 .44-.119.774-.357 1.005-.237.23-.565.345-.985.345a1.59 1.59 0 0 1-.568-.094 1.145 1.145 0 0 1-.407-.266 1.14 1.14 0 0 1-.243-.39Zm9.091-1.585v.522c0 .256-.039.47-.117.641a.862.862 0 0 1-.322.387.877.877 0 0 1-.47.126.883.883 0 0 1-.47-.126.87.87 0 0 1-.32-.387 1.55 1.55 0 0 1-.117-.641v-.522c0-.258.039-.471.117-.641a.87.87 0 0 1 .32-.387.868.868 0 0 1 .47-.129c.177 0 .333.043.47.129a.862.862 0 0 1 .322.387c.078.17.117.383.117.641m.803.519v-.513c0-.377-.069-.701-.205-.973a1.46 1.46 0 0 0-.59-.63c-.253-.146-.559-.22-.916-.22-.356 0-.662.074-.92.22a1.441 1.441 0 0 0-.589.628c-.137.271-.205.596-.205.975v.513c0 .375.068.699.205.973.137.271.333.48.589.626.258.145.564.217.92.217.357 0 .663-.072.917-.217.256-.146.452-.355.589-.626.136-.274.205-.598.205-.973Zm1.29-.935v2.675h-.746v-3.999h.662l1.752 2.66h.032v-2.66h.75v4h-.656l-1.761-2.676h-.032Z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
JSON
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="tab-content" style="height: 100%">
|
|
||||||
<div id="tab-1" class="tab-pane active" role="tabpanel">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-xl-flex justify-content-xl-center align-items-xl-center" style="width: 300px; height: 200px; text-align: center; background: #71bbb2">
|
|
||||||
<h1 style="font-size: 22px">Menüauswahl</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="d-xl-flex justify-content-xl-center align-items-xl-center" style="width: 300px; height: 200px; text-align: center; background: #71bbb2">
|
|
||||||
<h1 style="font-size: 22px">Zuordnung</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="tab-2" class="tab-pane" role="tabpanel" style="height: 100%">
|
|
||||||
<div class="split-container">
|
|
||||||
<!-- Linker Bereich mit fixer Höhe oben -->
|
|
||||||
<div class="panel left" :style="{ width: leftWidth + 'px' }">
|
|
||||||
<div class="top-panel" :style="{ height: topHeight + 'px' }">
|
|
||||||
<FormDesignerTreeViewComponent :nodes="formJSON"></FormDesignerTreeViewComponent>
|
|
||||||
</div>
|
|
||||||
<div class="horizontal-splitter" @mousedown="startHorizontalResize"></div>
|
|
||||||
<div class="bottom-panel" :style="{ height: bottomHeight + 'px' }">
|
|
||||||
<FormDesignerToolsComponent></FormDesignerToolsComponent>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Vertikaler Splitter -->
|
|
||||||
<div class="splitter" @mousedown="startVerticalResize"></div>
|
|
||||||
|
|
||||||
<!-- Mittlerer Bereich -->
|
|
||||||
<div class="panel middle p-3" :style="{ width: middleWidth + 'px' }">
|
|
||||||
<FormRendererComponent :schema="formJSON" @update-schema="addElementToForm" />
|
|
||||||
<slot name="middle"></slot>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Zweiter vertikaler Splitter -->
|
|
||||||
<div class="splitter" @mousedown="startSecondVerticalResize"></div>
|
|
||||||
|
|
||||||
<!-- Rechter Bereich -->
|
|
||||||
<div class="panel right" :style="{ width: rightWidth + 'px' }">
|
|
||||||
<slot name="right"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="tab-3" class="tab-pane" role="tabpanel">
|
|
||||||
<p>Content for tab 3.</p>
|
|
||||||
</div>
|
|
||||||
<div id="tab-4" class="tab-pane" role="tabpanel">
|
|
||||||
<textarea class="pro-textarea" v-model="jsonOutput" readonly></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { reactive } from "vue";
|
|
||||||
import FormDesignerToolsComponent from "@/components/formDesigner/FormDesignerToolsComponent.vue";
|
|
||||||
import FormDesignerTreeViewComponent from "@/components/formDesigner/FormDesignerTreeViewComponent.vue";
|
|
||||||
import FormRendererComponent from "@/components/formDesigner/FormRendererComponent.vue";
|
|
||||||
import FormJSON from "@/dummyData/formularData.json";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "FormDesignerLayout",
|
|
||||||
// Verwendete Komponenten
|
|
||||||
components: {
|
|
||||||
FormDesignerToolsComponent,
|
|
||||||
FormDesignerTreeViewComponent,
|
|
||||||
FormRendererComponent,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
leftWidth: 400, // Feste Breite für den linken Bereich
|
|
||||||
rightWidth: 200, // Feste Breite für den rechten Bereich
|
|
||||||
middleWidth: window.innerWidth - 400, // Dynamisch: Restliche Breite
|
|
||||||
topHeight: 150, // Fixierte Höhe für den oberen Bereich
|
|
||||||
bottomHeight: window.innerHeight - 150, // Dynamisch: Resthöhe
|
|
||||||
isResizingVertical: false,
|
|
||||||
isResizingSecondVertical: false,
|
|
||||||
isResizingHorizontal: false,
|
|
||||||
startX: 0,
|
|
||||||
startY: 0,
|
|
||||||
formJSON: reactive([...FormJSON]),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
window.addEventListener("resize", this.updateSizes);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
window.removeEventListener("resize", this.updateSizes);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateSizes() {
|
|
||||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
|
||||||
this.bottomHeight = window.innerHeight - this.topHeight;
|
|
||||||
},
|
|
||||||
addElementToForm(newSchema) {
|
|
||||||
console.log("💾 `update-schema` angekommen in FormDesignerLayout! 🔥", newSchema);
|
|
||||||
this.formJSON = newSchema;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Vertikaler Splitter (zwischen links und Mitte)
|
|
||||||
startVerticalResize(event) {
|
|
||||||
this.isResizingVertical = true;
|
|
||||||
this.startX = event.clientX;
|
|
||||||
document.addEventListener("mousemove", this.resizeVertical);
|
|
||||||
document.addEventListener("mouseup", this.stopResize);
|
|
||||||
},
|
|
||||||
resizeVertical(event) {
|
|
||||||
if (!this.isResizingVertical) return;
|
|
||||||
const delta = event.clientX - this.startX;
|
|
||||||
const newLeftWidth = this.leftWidth + delta;
|
|
||||||
if (newLeftWidth >= 100 && this.middleWidth - delta >= 100) {
|
|
||||||
this.leftWidth = newLeftWidth;
|
|
||||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
|
||||||
}
|
|
||||||
this.startX = event.clientX;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Zweiter vertikaler Splitter (zwischen Mitte und Rechts)
|
|
||||||
startSecondVerticalResize(event) {
|
|
||||||
this.isResizingSecondVertical = true;
|
|
||||||
this.startX = event.clientX;
|
|
||||||
document.addEventListener("mousemove", this.resizeSecondVertical);
|
|
||||||
document.addEventListener("mouseup", this.stopResize);
|
|
||||||
},
|
|
||||||
resizeSecondVertical(event) {
|
|
||||||
if (!this.isResizingSecondVertical) return;
|
|
||||||
const delta = event.clientX - this.startX;
|
|
||||||
const newRightWidth = this.rightWidth - delta;
|
|
||||||
if (newRightWidth >= 100 && this.middleWidth + delta >= 100) {
|
|
||||||
this.rightWidth = newRightWidth;
|
|
||||||
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
|
||||||
}
|
|
||||||
this.startX = event.clientX;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Horizontaler Splitter für den linken Bereich
|
|
||||||
startHorizontalResize(event) {
|
|
||||||
this.isResizingHorizontal = true;
|
|
||||||
this.startY = event.clientY;
|
|
||||||
document.addEventListener("mousemove", this.resizeHorizontal);
|
|
||||||
document.addEventListener("mouseup", this.stopResize);
|
|
||||||
},
|
|
||||||
resizeHorizontal(event) {
|
|
||||||
if (!this.isResizingHorizontal) return;
|
|
||||||
const delta = event.clientY - this.startY;
|
|
||||||
const newTopHeight = this.topHeight + delta;
|
|
||||||
const newBottomHeight = this.bottomHeight - delta;
|
|
||||||
|
|
||||||
if (newTopHeight >= 50 && newBottomHeight >= 50) {
|
|
||||||
this.topHeight = newTopHeight;
|
|
||||||
this.bottomHeight = newBottomHeight;
|
|
||||||
}
|
|
||||||
this.startY = event.clientY;
|
|
||||||
},
|
|
||||||
|
|
||||||
stopResize() {
|
|
||||||
this.isResizingVertical = false;
|
|
||||||
this.isResizingSecondVertical = false;
|
|
||||||
this.isResizingHorizontal = false;
|
|
||||||
document.removeEventListener("mousemove", this.resizeVertical);
|
|
||||||
document.removeEventListener("mousemove", this.resizeSecondVertical);
|
|
||||||
document.removeEventListener("mousemove", this.resizeHorizontal);
|
|
||||||
document.removeEventListener("mouseup", this.stopResize);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
jsonOutput() {
|
|
||||||
return JSON.stringify(this.formJSON, null, 2); // ✅ JSON sauber formatieren
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.split-container {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Linker Bereich mit fixer Höhe oben */
|
|
||||||
.left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-panel {
|
|
||||||
height: 150px; /* Feste Höhe */
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-panel {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allgemeine Panel-Styling */
|
|
||||||
.panel {
|
|
||||||
overflow: auto;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Splitter für vertikale Trennung */
|
|
||||||
.splitter {
|
|
||||||
width: 6px;
|
|
||||||
background: #27445d21;
|
|
||||||
cursor: ew-resize;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Splitter für horizontale Trennung */
|
|
||||||
.horizontal-splitter {
|
|
||||||
height: 3px;
|
|
||||||
background: #27445d21;
|
|
||||||
cursor: ns-resize;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pro-textarea {
|
|
||||||
height: 100dvh;
|
|
||||||
width: 100dvh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -5,8 +5,13 @@ import "bootstrap/dist/css/bootstrap.min.css";
|
|||||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||||
import "@/assets/custom-bootstrap.css";
|
import "@/assets/custom-bootstrap.css";
|
||||||
|
|
||||||
|
import { client } from "@/modules/shared/services/WebSocketService";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
})();
|
||||||
|
|||||||
@ -0,0 +1,238 @@
|
|||||||
|
<template>
|
||||||
|
<div class="component-container" :class="direction">
|
||||||
|
<component
|
||||||
|
:is="resolvedComponent"
|
||||||
|
v-bind="element.props"
|
||||||
|
@update-items="updateSchema"
|
||||||
|
@dragover.prevent="onDragOver($event)"
|
||||||
|
@drop="onDrop($event, index)"
|
||||||
|
@dragleave="onDragLeave()"
|
||||||
|
:class="getDropClass"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, defineAsyncComponent } from "vue";
|
||||||
|
|
||||||
|
const InputTextRenderer = defineAsyncComponent(() =>
|
||||||
|
import(
|
||||||
|
"@/modules/process/components/formDesigner/renderer/InputTextRenderer.vue"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const InputNumberRenderer = defineAsyncComponent(() =>
|
||||||
|
import(
|
||||||
|
"@/modules/process/components/formDesigner/renderer/InputNumberRenderer.vue"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const InputDateRenderer = defineAsyncComponent(() =>
|
||||||
|
import(
|
||||||
|
"@/modules/process/components/formDesigner/renderer/InputDateRenderer.vue"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const LabelRenderer = defineAsyncComponent(() =>
|
||||||
|
import("@/modules/process/components/formDesigner/renderer/LabelRenderer.vue")
|
||||||
|
);
|
||||||
|
const ButtonRenderer = defineAsyncComponent(() =>
|
||||||
|
import(
|
||||||
|
"@/modules/process/components/formDesigner/renderer/ButtonRenderer.vue"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const FlexLayoutRenderer = defineAsyncComponent(() =>
|
||||||
|
import(
|
||||||
|
"@/modules/process/components/formDesigner/renderer/FlexLayoutRenderer.vue"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||||
|
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||||
|
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||||
|
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||||
|
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||||
|
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ComponentRenderer",
|
||||||
|
components: {
|
||||||
|
InputTextRenderer,
|
||||||
|
InputNumberRenderer,
|
||||||
|
InputDateRenderer,
|
||||||
|
LabelRenderer,
|
||||||
|
ButtonRenderer,
|
||||||
|
FlexLayoutRenderer,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dropIndicator: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
element: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
resolvedComponent() {
|
||||||
|
const componentMap = {
|
||||||
|
FlexLayout: "FlexLayoutRenderer",
|
||||||
|
Label: "LabelRenderer",
|
||||||
|
InputText: "InputTextRenderer",
|
||||||
|
InputNumber: "InputNumberRenderer",
|
||||||
|
InputDate: "InputDateRenderer",
|
||||||
|
Button: "ButtonRenderer",
|
||||||
|
};
|
||||||
|
return componentMap[this.element.type] || null;
|
||||||
|
},
|
||||||
|
getDropClass() {
|
||||||
|
if (!this.dropIndicator || this.dropIndicator?.type !== this.element.type)
|
||||||
|
return "";
|
||||||
|
switch (this.dropIndicator.position) {
|
||||||
|
case "above":
|
||||||
|
return "drop-above";
|
||||||
|
case "below":
|
||||||
|
return "drop-below";
|
||||||
|
case "right":
|
||||||
|
return "drop-right";
|
||||||
|
case "left":
|
||||||
|
return "drop-left";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateSchema(newItems) {
|
||||||
|
this.$emit("update-schema", newItems);
|
||||||
|
},
|
||||||
|
onDrop(event, index) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
this.dropIndicator?.position === "right" ||
|
||||||
|
this.dropIndicator?.position === "below"
|
||||||
|
) {
|
||||||
|
index = index + 1;
|
||||||
|
}
|
||||||
|
const droppedType = event.dataTransfer.getData("text/plain");
|
||||||
|
const newElement = this.createElementByType(droppedType);
|
||||||
|
this.dropIndicator = null;
|
||||||
|
if (newElement) {
|
||||||
|
this.$emit("insert-item", { index, newElement });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragOver(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const bounds = event.currentTarget.getBoundingClientRect();
|
||||||
|
const offsetX = event.clientX - bounds.left;
|
||||||
|
const offsetY = event.clientY - bounds.top;
|
||||||
|
|
||||||
|
const isHorizontal = this.direction === "horizontal";
|
||||||
|
|
||||||
|
const position = isHorizontal
|
||||||
|
? offsetX < bounds.width / 2
|
||||||
|
? "left"
|
||||||
|
: "right"
|
||||||
|
: offsetY < bounds.height / 2
|
||||||
|
? "above"
|
||||||
|
: "below";
|
||||||
|
|
||||||
|
const curElement = this.element;
|
||||||
|
this.dropIndicator = {
|
||||||
|
type: curElement.type,
|
||||||
|
position,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onDragLeave() {
|
||||||
|
if (
|
||||||
|
this.dropIndicator &&
|
||||||
|
this.dropIndicator?.type === this.element.type
|
||||||
|
) {
|
||||||
|
this.dropIndicator = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createElementByType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "InputText":
|
||||||
|
return new InputTextBlock({
|
||||||
|
label: "Neues Textfeld",
|
||||||
|
placeholder: "Beispieltext",
|
||||||
|
});
|
||||||
|
case "InputNumber":
|
||||||
|
return new InputNumberBlock({
|
||||||
|
label: "Neue Nummer",
|
||||||
|
placeholder: "Hier Nummer eingeben",
|
||||||
|
});
|
||||||
|
case "InputDate":
|
||||||
|
return new InputDateBlock({
|
||||||
|
label: "Neues Datum",
|
||||||
|
placeholder: "Hier Datum eingeben",
|
||||||
|
});
|
||||||
|
case "Button":
|
||||||
|
return new ButtonBlock({ label: "Neuer Button" });
|
||||||
|
case "Label":
|
||||||
|
return new LabelBlock({ label: "Neues Label" });
|
||||||
|
case "FlexLayout":
|
||||||
|
return new FlexLayoutBlock({
|
||||||
|
items: [new LabelBlock({ label: "Erstes Element" })],
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
console.warn("⚠ Unbekannter Typ:", type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.component-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-container .drop-above::before,
|
||||||
|
.component-container .drop-below::after,
|
||||||
|
.component-container .drop-left::before,
|
||||||
|
.component-container .drop-right::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background: #42b983;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-container .drop-above::before {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
.component-container .drop-below::after {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-container .drop-left::before {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
.component-container .drop-right::after {
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,346 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="btn-group formBtn m-1" role="group" aria-label="Button group">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="elementClass"
|
||||||
|
@click="setActive('element')"
|
||||||
|
>
|
||||||
|
Eingabe
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="layoutClass"
|
||||||
|
@click="setActive('layout')"
|
||||||
|
>
|
||||||
|
Layout
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="templateClass"
|
||||||
|
@click="setActive('template')"
|
||||||
|
>
|
||||||
|
Template
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 formElements" v-if="elementActive">
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'InputText')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-input-cursor-text pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M5 2a.5.5 0 0 1 .5-.5c.862 0 1.573.287 2.06.566.174.099.321.198.44.286.119-.088.266-.187.44-.286A4.165 4.165 0 0 1 10.5 1.5a.5.5 0 0 1 0 1c-.638 0-1.177.213-1.564.434a3.49 3.49 0 0 0-.436.294V7.5H9a.5.5 0 0 1 0 1h-.5v4.272c.1.08.248.187.436.294.387.221.926.434 1.564.434a.5.5 0 0 1 0 1 4.165 4.165 0 0 1-2.06-.566A4.561 4.561 0 0 1 8 13.65a4.561 4.561 0 0 1-.44.285 4.165 4.165 0 0 1-2.06.566.5.5 0 0 1 0-1c.638 0 1.177-.213 1.564-.434.188-.107.335-.214.436-.294V8.5H7a.5.5 0 0 1 0-1h.5V3.228a3.49 3.49 0 0 0-.436-.294A3.166 3.166 0 0 0 5.5 2.5.5.5 0 0 1 5 2"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M10 5h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4v1h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4zM6 5V4H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h4v-1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Textfeld</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-1 mt-1" />
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'InputNumber')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-0-square pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Nummernfeld</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-1 mt-1" />
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'InputDate')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-calendar3 pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Datumsfeld</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-1 mt-1" />
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'Button')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-menu-button pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 1.5A1.5 1.5 0 0 1 1.5 0h8A1.5 1.5 0 0 1 11 1.5v2A1.5 1.5 0 0 1 9.5 5h-8A1.5 1.5 0 0 1 0 3.5zM1.5 1a.5.5 0 0 0-.5.5v2a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-2a.5.5 0 0 0-.5-.5z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="m7.823 2.823-.396-.396A.25.25 0 0 1 7.604 2h.792a.25.25 0 0 1 .177.427l-.396.396a.25.25 0 0 1-.354 0zM0 8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm1 3v2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2zm14-1V8a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2zM2 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Button</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 formElements" v-if="layoutActive">
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'FlexLayout')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-grid-1x2 pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 1H1v14h5zm9 0h-5v5h5zm0 9v5h-5v-5zM0 1a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1zm9 0a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1zm1 8a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Flexibles Layout</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-1 mt-1" />
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="startDrag($event, 'Label')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-alphabet-uppercase pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766h-2.55ZM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954H7.18Zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Label</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 formElements" v-if="templateActive">
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-chevron-down pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Accordion</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="mb-1 mt-1" />
|
||||||
|
<div
|
||||||
|
class="row ms-0 me-0 d-flex formItem align-items-center justify-content-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col d-flex justify-content-xxl-start align-items-xxl-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-floppy pe-0 me-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path d="M11 2H9v3h2z"></path>
|
||||||
|
<path
|
||||||
|
d="M1.5 0h11.586a1.5 1.5 0 0 1 1.06.44l1.415 1.414A1.5 1.5 0 0 1 16 2.914V14.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 14.5v-13A1.5 1.5 0 0 1 1.5 0M1 1.5v13a.5.5 0 0 0 .5.5H2v-4.5A1.5 1.5 0 0 1 3.5 9h9a1.5 1.5 0 0 1 1.5 1.5V15h.5a.5.5 0 0 0 .5-.5V2.914a.5.5 0 0 0-.146-.353l-1.415-1.415A.5.5 0 0 0 13.086 1H13v4.5A1.5 1.5 0 0 1 11.5 7h-7A1.5 1.5 0 0 1 3 5.5V1H1.5a.5.5 0 0 0-.5.5m3 4a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 .5-.5V1H4zM3 15h10v-4.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<p class="mb-0" style="font-size: 14px">Gespeicherter Eintrag 1</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "FormDesignerToolsComponent",
|
||||||
|
// Verwendete Komponenten
|
||||||
|
components: {},
|
||||||
|
// Props für die Komponente
|
||||||
|
props: {},
|
||||||
|
|
||||||
|
// Daten der Komponente
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
elementActive: true,
|
||||||
|
layoutActive: false,
|
||||||
|
templateActive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed Properties
|
||||||
|
computed: {
|
||||||
|
elementClass() {
|
||||||
|
if (this.elementActive) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
layoutClass() {
|
||||||
|
if (this.layoutActive) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
templateClass() {
|
||||||
|
if (this.templateActive) {
|
||||||
|
return "active";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Methoden
|
||||||
|
methods: {
|
||||||
|
startDrag(event, type) {
|
||||||
|
console.log("Dragging:", type);
|
||||||
|
event.dataTransfer.setData("text/plain", type); // Speichere das Objekt als JSON
|
||||||
|
},
|
||||||
|
setActive(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "element":
|
||||||
|
this.elementActive = true;
|
||||||
|
this.layoutActive = false;
|
||||||
|
this.templateActive = false;
|
||||||
|
break;
|
||||||
|
case "layout":
|
||||||
|
this.layoutActive = true;
|
||||||
|
this.elementActive = false;
|
||||||
|
this.templateActive = false;
|
||||||
|
break;
|
||||||
|
case "template":
|
||||||
|
this.templateActive = true;
|
||||||
|
this.elementActive = false;
|
||||||
|
this.layoutActive = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Lifecycle-Hooks
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.accordion-button:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formBtn {
|
||||||
|
width: 97%;
|
||||||
|
border: white solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formBtn button:not(:last-child) {
|
||||||
|
border-right: white solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formBtn button {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formBtn button.active {
|
||||||
|
color: var(--bs-primary);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formBtn button:hover {
|
||||||
|
background-color: #617789;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formElements .formItem {
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formElements .formItem:hover {
|
||||||
|
background-color: #617789;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,9 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<FlexLayoutRenderer direction="vertical" :items="schema" @update-items="$emit('update-schema', $event)" />
|
<FlexLayoutRenderer
|
||||||
|
direction="vertical"
|
||||||
|
:items="schema"
|
||||||
|
@update-items="$emit('update-schema', $event)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FlexLayoutRenderer from "@/components/formDesigner/formRenderer/FlexLayoutRenderer.vue";
|
import FlexLayoutRenderer from "@/modules/process/components/formDesigner/renderer/FlexLayoutRenderer.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FormRendererComponent",
|
name: "FormRendererComponent",
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-layout" :class="computedDirection">
|
||||||
|
<component-renderer
|
||||||
|
v-for="(item, key) in items"
|
||||||
|
:key="key"
|
||||||
|
:element="item"
|
||||||
|
:index="key"
|
||||||
|
:direction="direction"
|
||||||
|
@update-schema="updateChildItems(key, $event)"
|
||||||
|
@insert-item="insertItem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import ComponentRenderer from "@/modules/process/components/formDesigner/ComponentRenderer.vue";
|
||||||
|
|
||||||
|
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||||
|
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||||
|
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||||
|
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||||
|
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||||
|
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "FlexLayoutRenderer",
|
||||||
|
components: { ComponentRenderer },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dragOverActive: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedDirection() {
|
||||||
|
return this.direction === "horizontal" ? "horizontal" : "vertical";
|
||||||
|
},
|
||||||
|
computedDropClass() {
|
||||||
|
return this.direction === "horizontal"
|
||||||
|
? "drop-zone-horizontal"
|
||||||
|
: "drop-zone-vertical";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChildItems(index, newItems) {
|
||||||
|
const updatedItems = [...this.items];
|
||||||
|
updatedItems[index] = { ...updatedItems[index], items: newItems };
|
||||||
|
this.$emit("update-items", updatedItems);
|
||||||
|
},
|
||||||
|
insertItem({ index, newElement }) {
|
||||||
|
const updatedItems = [...this.items];
|
||||||
|
|
||||||
|
// 🛠 Stelle sicher, dass `index` innerhalb der Liste bleibt
|
||||||
|
if (index < 0) index = 0;
|
||||||
|
if (index > updatedItems.length) index = updatedItems.length;
|
||||||
|
|
||||||
|
updatedItems.splice(index, 0, newElement);
|
||||||
|
|
||||||
|
this.$emit("update-items", updatedItems);
|
||||||
|
},
|
||||||
|
createElementByType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "InputText":
|
||||||
|
return new InputTextBlock({
|
||||||
|
label: "Neues Textfeld",
|
||||||
|
placeholder: "Beispieltext",
|
||||||
|
});
|
||||||
|
case "InputNumber":
|
||||||
|
return new InputNumberBlock({
|
||||||
|
label: "Neue Nummer",
|
||||||
|
placeholder: "Hier Nummer eingeben",
|
||||||
|
});
|
||||||
|
case "InputDate":
|
||||||
|
return new InputDateBlock({
|
||||||
|
label: "Neues Datum",
|
||||||
|
placeholder: "Hier Datum eingeben",
|
||||||
|
});
|
||||||
|
case "Button":
|
||||||
|
return new ButtonBlock({ label: "Neuer Button" });
|
||||||
|
case "Label":
|
||||||
|
return new LabelBlock({ label: "Neues Label" });
|
||||||
|
case "FlexLayout":
|
||||||
|
return new FlexLayoutBlock({
|
||||||
|
items: [new LabelBlock({ label: "Erstes Element" })],
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
console.warn("⚠ Unbekannter Typ:", type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flex-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layouts richtig ausrichten */
|
||||||
|
.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop-Zonen für horizontale Layouts (zwischen Spalten) */
|
||||||
|
.drop-zone-horizontal {
|
||||||
|
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>
|
||||||
@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<label>{{ value }}</label>
|
<label>{{ label }}</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "LabelRenderer",
|
name: "LabelRenderer",
|
||||||
props: {
|
props: {
|
||||||
value: String,
|
label: String,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
149
src/modules/process/components/process/ProcessTableComponent.vue
Normal file
149
src/modules/process/components/process/ProcessTableComponent.vue
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row ms-0 ps-0 mb-4" style="text-align: left">
|
||||||
|
<div
|
||||||
|
class="col-3 ps-0 pe-0 me-5"
|
||||||
|
style="border: 1px solid rgba(39, 68, 93, 0.39); width: 100%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
@click.stop="toggleTable"
|
||||||
|
class="d-inline-flex justify-content-between align-items-center align-items-sm-center align-items-md-center align-items-lg-center align-items-xl-center align-items-xxl-center ps-3"
|
||||||
|
style="width: 100%; height: 50px; text-align: left; background: #71bbb2"
|
||||||
|
>
|
||||||
|
<div class="d-inline-flex align-items-xl-center">
|
||||||
|
<h1 style="font-size: 22px; text-align: left; width: auto">
|
||||||
|
{{ tableTitleProp }} ({{ viewProp?.length || 0 }})
|
||||||
|
</h1>
|
||||||
|
<svg
|
||||||
|
class="pointer bi bi-plus-circle-fill ps-0 ms-3"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style="font-size: 18px; color: var(--bs-primary) !important"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right; width: 50px">
|
||||||
|
<svg
|
||||||
|
v-if="showTable"
|
||||||
|
@click.stop="toggleTable"
|
||||||
|
:class="tableIcon"
|
||||||
|
class="pointer bi me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style="font-size: 25px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
v-if="!showTable"
|
||||||
|
@click.stop="toggleTable"
|
||||||
|
class="pointer bi bi-chevron-right me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style="font-size: 25px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showTable" class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80%">Name</th>
|
||||||
|
<th>Änderungsdatum</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
class="pro-row"
|
||||||
|
@click="
|
||||||
|
setActivePage(
|
||||||
|
'FormDesignerLayout',
|
||||||
|
'Ansicht (' + row.name + ')',
|
||||||
|
{},
|
||||||
|
'process'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-for="row in viewProp"
|
||||||
|
:key="row.name + '-' + row.changed"
|
||||||
|
>
|
||||||
|
<td>{{ row.name }}</td>
|
||||||
|
<td>{{ row.changed }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useUiStore } from "../../../../store/uiStore.js";
|
||||||
|
export default {
|
||||||
|
name: "ProcessTableComponent",
|
||||||
|
// Verwendete Komponenten
|
||||||
|
components: {},
|
||||||
|
// Props für die Komponente
|
||||||
|
props: {
|
||||||
|
viewProp: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
tableTitleProp: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Daten der Komponente
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showTable: false,
|
||||||
|
uiStore: useUiStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed Propertiesprocesses.data
|
||||||
|
computed: {
|
||||||
|
openComponents() {
|
||||||
|
return this.uiStore.openComponents;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Methoden
|
||||||
|
methods: {
|
||||||
|
toggleTable() {
|
||||||
|
this.showTable = !this.showTable;
|
||||||
|
},
|
||||||
|
setActivePage(componentName, displayName, props, menuItem) {
|
||||||
|
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Lifecycle-Hooks
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pro-row:hover td {
|
||||||
|
background-color: rgba(113, 187, 178, 0.3);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
397
src/modules/process/layouts/FormDesignerLayout.vue
Normal file
397
src/modules/process/layouts/FormDesignerLayout.vue
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||||
|
<div style="height: 100%; width: calc(100vw - 120px)">
|
||||||
|
<ul class="nav nav-tabs" style="justify-content: center" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link active"
|
||||||
|
role="tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-1"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-file-text pe-0 me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style="font-size: 16px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Verwaltung
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" role="tab" data-bs-toggle="tab" href="#tab-2">
|
||||||
|
<svg
|
||||||
|
class="bi bi-card-heading pe-0 me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Editor
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" style="height: 100%">
|
||||||
|
<div id="tab-1" class="tab-pane active" role="tabpanel">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div
|
||||||
|
class="d-xl-flex justify-content-xl-center align-items-xl-center"
|
||||||
|
style="
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
|
text-align: center;
|
||||||
|
background: #71bbb2;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<h1 style="font-size: 22px">Menüauswahl</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div
|
||||||
|
class="d-xl-flex justify-content-xl-center align-items-xl-center"
|
||||||
|
style="
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
|
text-align: center;
|
||||||
|
background: #71bbb2;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<h1 style="font-size: 22px">Zuordnung</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="tab-2" class="tab-pane" role="tabpanel" style="height: 100%">
|
||||||
|
<div class="split-container">
|
||||||
|
<!-- Linker Bereich mit fixer Höhe oben -->
|
||||||
|
<div
|
||||||
|
class="panel left"
|
||||||
|
style="background-color: var(--bs-primary); color: white"
|
||||||
|
:style="{ width: leftWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<ul class="nav nav-tabs d-flex formPanel" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link active"
|
||||||
|
role="tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-elements"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-file-text pe-0 me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style="font-size: 16px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Elemente
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
role="tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
href="#tab-tree"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="bi bi-card-heading pe-0 me-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Baum
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" style="height: 100%">
|
||||||
|
<div id="tab-tree" class="tab-pane" role="tabpanel">
|
||||||
|
<FormDesignerTreeViewComponent
|
||||||
|
:nodes="formJSON"
|
||||||
|
></FormDesignerTreeViewComponent>
|
||||||
|
</div>
|
||||||
|
<div id="tab-elements" class="tab-pane active" role="tabpanel">
|
||||||
|
<FormDesignerToolsComponent></FormDesignerToolsComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vertikaler Splitter -->
|
||||||
|
<div class="splitter" @mousedown="startVerticalResize"></div>
|
||||||
|
|
||||||
|
<!-- Mittlerer Bereich -->
|
||||||
|
<div
|
||||||
|
class="panel middle p-3"
|
||||||
|
:style="{ width: middleWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<FormRendererComponent
|
||||||
|
:schema="schema"
|
||||||
|
@update-schema="addElementToForm"
|
||||||
|
/>
|
||||||
|
<slot name="middle"></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zweiter vertikaler Splitter -->
|
||||||
|
<div class="splitter" @mousedown="startSecondVerticalResize"></div>
|
||||||
|
|
||||||
|
<!-- Rechter Bereich -->
|
||||||
|
<div
|
||||||
|
class="panel right"
|
||||||
|
style="background-color: var(--bs-primary); color: white"
|
||||||
|
:style="{ width: rightWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormDesignerToolsComponent from "@/modules/process/components/formDesigner/FormDesignerToolsComponent.vue";
|
||||||
|
import FormDesignerTreeViewComponent from "@/modules/process/components/formDesigner/FormDesignerTreeViewComponent.vue";
|
||||||
|
import FormRendererComponent from "@/modules/process/components/formDesigner/FormRendererComponent.vue";
|
||||||
|
|
||||||
|
import { InputTextBlock } from "@/modules/process/models/formDesigner/blocks/InputTextBlock";
|
||||||
|
import { ButtonBlock } from "@/modules/process/models/formDesigner/blocks/ButtonBlock";
|
||||||
|
import { LabelBlock } from "@/modules/process/models/formDesigner/blocks/LabelBlock";
|
||||||
|
import { FlexLayoutBlock } from "@/modules/process/models/formDesigner/blocks/FlexLayoutBlock";
|
||||||
|
import { InputDateBlock } from "@/modules/process/models/formDesigner/blocks/InputDateBlock";
|
||||||
|
import { InputNumberBlock } from "@/modules/process/models/formDesigner/blocks/InputNumberBlock";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "FormDesignerLayout",
|
||||||
|
// Verwendete Komponenten
|
||||||
|
components: {
|
||||||
|
FormDesignerToolsComponent,
|
||||||
|
FormDesignerTreeViewComponent,
|
||||||
|
FormRendererComponent,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
leftWidth: 400, // Feste Breite für den linken Bereich
|
||||||
|
rightWidth: 200, // Feste Breite für den rechten Bereich
|
||||||
|
middleWidth: window.innerWidth - 400, // Dynamisch: Restliche Breite
|
||||||
|
topHeight: 150, // Fixierte Höhe für den oberen Bereich
|
||||||
|
bottomHeight: window.innerHeight - 150, // Dynamisch: Resthöhe
|
||||||
|
isResizingVertical: false,
|
||||||
|
isResizingSecondVertical: false,
|
||||||
|
isResizingHorizontal: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
schema: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("resize", this.updateSizes);
|
||||||
|
this.schema = [
|
||||||
|
new InputTextBlock({ label: "Vorname", placeholder: "Dein Name" }),
|
||||||
|
new ButtonBlock({ label: "Klick mich an" }),
|
||||||
|
new LabelBlock({ label: "Ich bin ein Label" }),
|
||||||
|
new FlexLayoutBlock({
|
||||||
|
items: [
|
||||||
|
new InputDateBlock({
|
||||||
|
label: "Geburtstag",
|
||||||
|
placeholder: "Datumswert",
|
||||||
|
}),
|
||||||
|
new InputNumberBlock({
|
||||||
|
label: "Alter",
|
||||||
|
placeholder: "Gib dein Alter ein",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener("resize", this.updateSizes);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateSizes() {
|
||||||
|
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||||
|
this.bottomHeight = window.innerHeight - this.topHeight;
|
||||||
|
},
|
||||||
|
addElementToForm(newSchema) {
|
||||||
|
this.schema = newSchema;
|
||||||
|
},
|
||||||
|
// Vertikaler Splitter (zwischen links und Mitte)
|
||||||
|
startVerticalResize(event) {
|
||||||
|
this.isResizingVertical = true;
|
||||||
|
this.startX = event.clientX;
|
||||||
|
document.addEventListener("mousemove", this.resizeVertical);
|
||||||
|
document.addEventListener("mouseup", this.stopResize);
|
||||||
|
},
|
||||||
|
resizeVertical(event) {
|
||||||
|
if (!this.isResizingVertical) return;
|
||||||
|
const delta = event.clientX - this.startX;
|
||||||
|
const newLeftWidth = this.leftWidth + delta;
|
||||||
|
if (newLeftWidth >= 100 && this.middleWidth - delta >= 100) {
|
||||||
|
this.leftWidth = newLeftWidth;
|
||||||
|
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||||
|
}
|
||||||
|
this.startX = event.clientX;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Zweiter vertikaler Splitter (zwischen Mitte und Rechts)
|
||||||
|
startSecondVerticalResize(event) {
|
||||||
|
this.isResizingSecondVertical = true;
|
||||||
|
this.startX = event.clientX;
|
||||||
|
document.addEventListener("mousemove", this.resizeSecondVertical);
|
||||||
|
document.addEventListener("mouseup", this.stopResize);
|
||||||
|
},
|
||||||
|
resizeSecondVertical(event) {
|
||||||
|
if (!this.isResizingSecondVertical) return;
|
||||||
|
const delta = event.clientX - this.startX;
|
||||||
|
const newRightWidth = this.rightWidth - delta;
|
||||||
|
if (newRightWidth >= 100 && this.middleWidth + delta >= 100) {
|
||||||
|
this.rightWidth = newRightWidth;
|
||||||
|
this.middleWidth = window.innerWidth - this.leftWidth - this.rightWidth;
|
||||||
|
}
|
||||||
|
this.startX = event.clientX;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Horizontaler Splitter für den linken Bereich
|
||||||
|
startHorizontalResize(event) {
|
||||||
|
this.isResizingHorizontal = true;
|
||||||
|
this.startY = event.clientY;
|
||||||
|
document.addEventListener("mousemove", this.resizeHorizontal);
|
||||||
|
document.addEventListener("mouseup", this.stopResize);
|
||||||
|
},
|
||||||
|
resizeHorizontal(event) {
|
||||||
|
if (!this.isResizingHorizontal) return;
|
||||||
|
const delta = event.clientY - this.startY;
|
||||||
|
const newTopHeight = this.topHeight + delta;
|
||||||
|
const newBottomHeight = this.bottomHeight - delta;
|
||||||
|
|
||||||
|
if (newTopHeight >= 50 && newBottomHeight >= 50) {
|
||||||
|
this.topHeight = newTopHeight;
|
||||||
|
this.bottomHeight = newBottomHeight;
|
||||||
|
}
|
||||||
|
this.startY = event.clientY;
|
||||||
|
},
|
||||||
|
|
||||||
|
stopResize() {
|
||||||
|
this.isResizingVertical = false;
|
||||||
|
this.isResizingSecondVertical = false;
|
||||||
|
this.isResizingHorizontal = false;
|
||||||
|
document.removeEventListener("mousemove", this.resizeVertical);
|
||||||
|
document.removeEventListener("mousemove", this.resizeSecondVertical);
|
||||||
|
document.removeEventListener("mousemove", this.resizeHorizontal);
|
||||||
|
document.removeEventListener("mouseup", this.stopResize);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
jsonOutput() {
|
||||||
|
return JSON.stringify(this.formJSON, null, 2); // ✅ JSON sauber formatieren
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.split-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Linker Bereich mit fixer Höhe oben */
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-panel {
|
||||||
|
height: 150px; /* Feste Höhe */
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-panel {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allgemeine Panel-Styling */
|
||||||
|
.panel {
|
||||||
|
overflow: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splitter für vertikale Trennung */
|
||||||
|
.splitter {
|
||||||
|
width: 6px;
|
||||||
|
background: #27445d21;
|
||||||
|
cursor: ew-resize;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splitter für horizontale Trennung */
|
||||||
|
.horizontal-splitter {
|
||||||
|
height: 3px;
|
||||||
|
background: #27445d21;
|
||||||
|
cursor: ns-resize;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pro-textarea {
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formPanel a {
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formPanel .nav-item {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formPanel .nav-link:not(.active) {
|
||||||
|
background-color: white;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formPanel .nav-link.active {
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
18
src/modules/process/models/formDesigner/FormBlock.js
Normal file
18
src/modules/process/models/formDesigner/FormBlock.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export class FormBlock {
|
||||||
|
constructor(type, props = {}) {
|
||||||
|
this.type = type;
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
type: this.type,
|
||||||
|
...this.props,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Logik für spätere Validierung
|
||||||
|
validate(value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/modules/process/models/formDesigner/FormBlockFactory.js
Normal file
27
src/modules/process/models/formDesigner/FormBlockFactory.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ButtonBlock } from "../../../utils/formular/blocks/ButtonBlock";
|
||||||
|
import { FlexLayoutBlock } from "../../../utils/formular/blocks/FlexLayoutBlock";
|
||||||
|
import { InputDateBlock } from "../../../utils/formular/blocks/InputDateBlock";
|
||||||
|
import { InputNumberBlock } from "../../../utils/formular/blocks/InputNumberBlock";
|
||||||
|
import { InputTextBlock } from "../../../utils/formular/blocks/InputTextBlock";
|
||||||
|
import { LabelBlock } from "../../../utils/formular/blocks/LabelBlock";
|
||||||
|
|
||||||
|
export class FormBlockFactory {
|
||||||
|
static createBlock(data) {
|
||||||
|
switch (data.type) {
|
||||||
|
case "InputText":
|
||||||
|
return new InputTextBlock(data);
|
||||||
|
case "Label":
|
||||||
|
return new LabelBlock(data);
|
||||||
|
case "Button":
|
||||||
|
return new ButtonBlock(data);
|
||||||
|
case "FlexLayout":
|
||||||
|
return new FlexLayoutBlock(data);
|
||||||
|
case "InputDate":
|
||||||
|
return new InputDateBlock(data);
|
||||||
|
case "InputNumber":
|
||||||
|
return new InputNumberBlock(data);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unbekannter Baustein-Typ: ${data.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class ButtonBlock extends FormBlock {
|
||||||
|
constructor({ label = "Klick mich" } = {}) {
|
||||||
|
super("Button", {
|
||||||
|
label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class FlexLayoutBlock extends FormBlock {
|
||||||
|
constructor({ direction = "horizontal", items = [] } = {}) {
|
||||||
|
super("FlexLayout", {
|
||||||
|
direction,
|
||||||
|
items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class InputDateBlock extends FormBlock {
|
||||||
|
constructor({ label = "", placeholder = "" } = {}) {
|
||||||
|
super("InputDate", {
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class InputNumberBlock extends FormBlock {
|
||||||
|
constructor({ label = "", placeholder = "" } = {}) {
|
||||||
|
super("InputNumber", {
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class InputTextBlock extends FormBlock {
|
||||||
|
constructor({ label = "", placeholder = "" } = {}) {
|
||||||
|
super("InputText", {
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/modules/process/models/formDesigner/blocks/LabelBlock.js
Normal file
13
src/modules/process/models/formDesigner/blocks/LabelBlock.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { FormBlock } from "../FormBlock";
|
||||||
|
|
||||||
|
export class LabelBlock extends FormBlock {
|
||||||
|
constructor({ label = "Ich bin ein Label" } = {}) {
|
||||||
|
super("Label", {
|
||||||
|
label,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validate() {
|
||||||
|
console.log("Validier ist noch nicht fertig");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/modules/process/models/process/Process.js
Normal file
15
src/modules/process/models/process/Process.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { BaseModel } from "@/modules/shared/models/Basemodel";
|
||||||
|
|
||||||
|
export class Process extends BaseModel {
|
||||||
|
constructor({ id, name, description } = {}) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.topic = "system_process";
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublicFields() {
|
||||||
|
return ["id", "name", "description"];
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/modules/process/pages/ProcessOverviewComponent.vue
Normal file
76
src/modules/process/pages/ProcessOverviewComponent.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||||
|
<h1 class="mb-3">{{ processObject.name }}</h1>
|
||||||
|
<label class="form-label mb-0" style="font-size: 14px">Beschreibung</label>
|
||||||
|
<textarea
|
||||||
|
class="form-control-sm mb-3 pb-2 ms-0 me-0"
|
||||||
|
wrap="hard"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="processObject.description"
|
||||||
|
></textarea>
|
||||||
|
<ProcessTableComponent
|
||||||
|
:viewProp="objectData"
|
||||||
|
:tableTitleProp="'Ansichten'"
|
||||||
|
></ProcessTableComponent>
|
||||||
|
<ProcessTableComponent
|
||||||
|
:viewProp="processObject.workflows"
|
||||||
|
:tableTitleProp="'Workflows'"
|
||||||
|
></ProcessTableComponent>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ProcessTableComponent from "@/modules/process/components/process/ProcessTableComponent.vue";
|
||||||
|
import { getObject } from "@/utils/getObject.js";
|
||||||
|
//import { getSpecificObject } from "@/utils/getSpecificObject";
|
||||||
|
export default {
|
||||||
|
name: "ProcessOverviewComponent",
|
||||||
|
// Verwendete Komponenten
|
||||||
|
components: {
|
||||||
|
ProcessTableComponent,
|
||||||
|
},
|
||||||
|
// Props für die Komponente
|
||||||
|
props: {
|
||||||
|
object: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
objectId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Daten der Komponente
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
processObject: {},
|
||||||
|
objectData: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Computed Propertiesprocesses.data
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
// Methoden
|
||||||
|
methods: {},
|
||||||
|
|
||||||
|
// Lifecycle-Hooks
|
||||||
|
mounted() {
|
||||||
|
this.processObject = { ...this.object };
|
||||||
|
console.log(this.processObject);
|
||||||
|
this.wsHandler = getObject(
|
||||||
|
"system_processView",
|
||||||
|
["name", "description"],
|
||||||
|
(id, updated) => {
|
||||||
|
this.objectData = { ...this.objectData, [id]: updated };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.specificObjectHandler?.unsubscribe();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
74
src/modules/shared/models/Basemodel.js
Normal file
74
src/modules/shared/models/Basemodel.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import socketService from "@/modules/shared/services/WebSocketService";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
|
||||||
|
export class BaseModel {
|
||||||
|
constructor() {
|
||||||
|
this._activeSubscriptions = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
makeReactive() {
|
||||||
|
return reactive(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return this.getPublicFields().reduce((obj, key) => {
|
||||||
|
obj[key] = this[key];
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(fields = [], callback = null) {
|
||||||
|
if (!this.topic || !this.id) {
|
||||||
|
console.warn("BaseModel.subscribe(): 'topic' und 'id' fehlen.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const filter = { _id: this.id };
|
||||||
|
const key = `${field}`;
|
||||||
|
|
||||||
|
const handler = (value) => {
|
||||||
|
const newValue = Array.isArray(value)
|
||||||
|
? value[0]?.transientValue
|
||||||
|
: value?.transientValue ?? value;
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this, field)) {
|
||||||
|
// Feld existiert bereits -> Vue kann es tracken
|
||||||
|
this[field] = newValue;
|
||||||
|
} else {
|
||||||
|
// 🧠 Feld existiert noch nicht -> tricksen:
|
||||||
|
Object.assign(this, { [field]: newValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
callback(field, newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this._activeSubscriptions.set(key, { field, filter, handler });
|
||||||
|
socketService.subscribe(this.topic, field, filter, handler);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(fields = []) {
|
||||||
|
if (!this.topic || !this.id) return;
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const key = `${field}`;
|
||||||
|
const sub = this._activeSubscriptions.get(key);
|
||||||
|
if (sub) {
|
||||||
|
socketService.unsubscribe(
|
||||||
|
this.topic,
|
||||||
|
sub.field,
|
||||||
|
sub.filter,
|
||||||
|
sub.handler
|
||||||
|
);
|
||||||
|
this._activeSubscriptions.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeAll() {
|
||||||
|
this.unsubscribe([...this._activeSubscriptions.keys()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/modules/shared/services/SubscriptionManager.js
Normal file
53
src/modules/shared/services/SubscriptionManager.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
function generateKey(subject, field, filter = {}) {
|
||||||
|
return `${subject}/${field}/${JSON.stringify(filter)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubscriptionManager {
|
||||||
|
constructor(socketClient) {
|
||||||
|
this.socketClient = socketClient;
|
||||||
|
this.subscribers = {}; // { key: [callback1, callback2, ...] }
|
||||||
|
|
||||||
|
this.socketClient.onEvent(this._handleMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleMessage(event) {
|
||||||
|
if (event.type !== "message") return;
|
||||||
|
|
||||||
|
const { subject, field, filter = {}, value } = event.payload;
|
||||||
|
const key = generateKey(subject, field, filter);
|
||||||
|
|
||||||
|
if (this.subscribers[key]) {
|
||||||
|
this.subscribers[key].forEach((cb) => {
|
||||||
|
if (typeof cb === "function") {
|
||||||
|
cb(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(subject, field, filter = {}, callback) {
|
||||||
|
const key = generateKey(subject, field, filter);
|
||||||
|
|
||||||
|
if (!this.subscribers[key]) {
|
||||||
|
this.subscribers[key] = [];
|
||||||
|
console.log({ action: "subscribe", subject, field, filter });
|
||||||
|
this.socketClient.send({ action: "subscribe", subject, field, filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscribers[key].push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(subject, field, filter = {}, callback) {
|
||||||
|
const key = generateKey(subject, field, filter);
|
||||||
|
const callbacks = this.subscribers[key];
|
||||||
|
|
||||||
|
if (!callbacks) return;
|
||||||
|
|
||||||
|
this.subscribers[key] = callbacks.filter((cb) => cb !== callback);
|
||||||
|
|
||||||
|
if (this.subscribers[key].length === 0) {
|
||||||
|
this.socketClient.send({ action: "unsubscribe", subject, field, filter });
|
||||||
|
delete this.subscribers[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/modules/shared/services/WebSocketClient.js
Normal file
61
src/modules/shared/services/WebSocketClient.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const VUE_APP_WS_URL = process.env.VUE_APP_WS_URL || "ws://localhost";
|
||||||
|
|
||||||
|
export class WebSocketClient {
|
||||||
|
constructor(url = VUE_APP_WS_URL) {
|
||||||
|
this.url = url;
|
||||||
|
this.socket = null;
|
||||||
|
this.handlers = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.socket = new WebSocket(this.url);
|
||||||
|
|
||||||
|
this.socket.onopen = () => {
|
||||||
|
console.log("WebSocket verbunden.");
|
||||||
|
this.handlers.forEach((h) => h({ type: "open" }));
|
||||||
|
resolve(); // ✅ Promise wird hier erfüllt
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
this.handlers.forEach((h) =>
|
||||||
|
h({ type: "message", payload: message })
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("WebSocket-Parsing-Fehler:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
console.warn("WebSocket getrennt. Reconnect in 2s...");
|
||||||
|
//setTimeout(() => this.connect(), 2000); // <- ruft sich selbst erneut auf
|
||||||
|
};
|
||||||
|
|
||||||
|
this.socket.onerror = (err) => {
|
||||||
|
console.error("WebSocket-Fehler:", err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data) {
|
||||||
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
||||||
|
this.socket.send(JSON.stringify(data));
|
||||||
|
} else {
|
||||||
|
console.warn("WebSocket nicht bereit:", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEvent(callback) {
|
||||||
|
this.handlers.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEvent(callback) {
|
||||||
|
this.handlers.delete(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/modules/shared/services/WebSocketService.js
Normal file
7
src/modules/shared/services/WebSocketService.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { WebSocketClient } from "./WebSocketClient";
|
||||||
|
import { SubscriptionManager } from "./SubscriptionManager";
|
||||||
|
|
||||||
|
export const client = new WebSocketClient();
|
||||||
|
const subscriptionManager = new SubscriptionManager(client);
|
||||||
|
|
||||||
|
export default subscriptionManager;
|
||||||
@ -1,32 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
||||||
<h1 class="mb-3">
|
<h1 class="mb-3">Prozesse</h1>
|
||||||
processes
|
|
||||||
<svg
|
|
||||||
class="bi bi-plus-circle-fill ps-0 ms-3"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
style="font-size: 20px; color: var(--bs-primary) !important"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.5 4.5a.5.5 0 0 0-1 0v3h-3a.5.5 0 0 0 0 1h3v3a.5.5 0 0 0 1 0v-3h3a.5.5 0 0 0 0-1h-3z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</h1>
|
|
||||||
<div class="row" style="text-align: left">
|
<div class="row" style="text-align: left">
|
||||||
<div
|
<div
|
||||||
v-for="process in processes"
|
v-for="(object, objectId) in objectData"
|
||||||
:key="process.id"
|
:key="objectId"
|
||||||
class="col-3 ps-0 pe-0 me-5 pointer"
|
class="col-3 ps-0 pe-0 me-5 pointer"
|
||||||
style="border: 1px solid rgba(39, 68, 93, 0.39); min-height: 130px"
|
style="border: 1px solid rgba(39, 68, 93, 0.39); min-height: 130px"
|
||||||
@click="
|
@click="
|
||||||
setActivePage(
|
setActivePage(
|
||||||
'ProcessOverviewComponent',
|
'ProcessOverviewComponent',
|
||||||
processData[process.id]?.name || 'Unknown',
|
object.name || 'Unknown',
|
||||||
{ process },
|
{ object: object, objectId: objectId },
|
||||||
'process'
|
'process'
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@ -41,11 +26,11 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h1 style="font-size: 22px">
|
<h1 style="font-size: 22px">
|
||||||
{{ processData[process.id]?.name || "Loading..." }}
|
{{ object.name || "Loading..." }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<p class="ms-2 pe-2 pt-2 pb-2" style="font-size: 14px">
|
<p class="ms-2 pe-2 pt-2 pb-2" style="font-size: 14px">
|
||||||
{{ process.description }}
|
{{ object.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,17 +38,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useUiStore } from "../store/uiStore.js";
|
import { useUiStore } from "@/store/uiStore.js";
|
||||||
import WebSocketService from "@/services/WebSocketService";
|
//import { getObject } from "@/utils/getObject.js";
|
||||||
|
import socketService from "@/modules/shared/services/WebSocketService";
|
||||||
|
import { Process } from "@/modules/process/models/process/Process";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DevelopmentDashboard",
|
name: "DevelopmentDashboard",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
processes: [],
|
objectData: [],
|
||||||
processData: {},
|
|
||||||
callbacks: {}, // field-wise callbacks per processId
|
|
||||||
fields: ["name"], // fields to subscribe
|
|
||||||
uiStore: useUiStore(),
|
uiStore: useUiStore(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -72,66 +56,26 @@ export default {
|
|||||||
setActivePage(componentName, displayName, props, menuItem) {
|
setActivePage(componentName, displayName, props, menuItem) {
|
||||||
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
this.uiStore.setActivePage(componentName, displayName, props, menuItem);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleNewProcessId(processId) {
|
|
||||||
if (this.processes.some((p) => p.id === processId)) return;
|
|
||||||
|
|
||||||
this.processes.push({ id: processId });
|
|
||||||
|
|
||||||
if (!this.processData[processId]) {
|
|
||||||
this.processData[processId] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.callbacks[processId]) {
|
|
||||||
this.callbacks[processId] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fields.forEach((field) => {
|
|
||||||
const callback = (value) => {
|
|
||||||
this.handleUpdate(processId, field, value);
|
|
||||||
};
|
|
||||||
this.callbacks[processId][field] = callback;
|
|
||||||
|
|
||||||
WebSocketService.subscribe(
|
|
||||||
"process",
|
|
||||||
field,
|
|
||||||
{ id: processId },
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
});
|
|
||||||
console.log(this.processes);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleUpdate(processId, field, value) {
|
|
||||||
if (!this.processData[processId]) {
|
|
||||||
this.processData[processId] = {};
|
|
||||||
}
|
|
||||||
this.processData[processId][field] = value;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this._idCallback = (value) => {
|
socketService.subscribe("system_process", "_id", {}, (val) => {
|
||||||
const ids = [...new Set(Array.isArray(value) ? value : [value])];
|
val.forEach((element) => {
|
||||||
ids.forEach((id) => this.handleNewProcessId(id));
|
let curProcess = new Process({ id: element }).makeReactive();
|
||||||
};
|
curProcess.subscribe(["name", "description"]);
|
||||||
WebSocketService.subscribe("process", "_id", {}, this._idCallback);
|
this.objectData.push(curProcess);
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount() {
|
|
||||||
Object.entries(this.callbacks).forEach(([processId, fieldMap]) => {
|
|
||||||
Object.entries(fieldMap).forEach(([field, callback]) => {
|
|
||||||
WebSocketService.unsubscribe(
|
|
||||||
"process",
|
|
||||||
field,
|
|
||||||
{ id: processId },
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
/*this.wsHandler = getObject(
|
||||||
WebSocketService.unsubscribe("process", "_id", {}, this._idCallback);
|
"system_process",
|
||||||
|
["name", "description"],
|
||||||
|
(id, updated) => {
|
||||||
|
this.objectData = { ...this.objectData, [id]: updated };
|
||||||
|
}
|
||||||
|
);*/
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container mt-2" style="width: 100vw; height: 100vh">
|
|
||||||
<h1 class="mb-3">{{ processObject.name }}</h1>
|
|
||||||
<label class="form-label mb-0" style="font-size: 14px">Bemerkung</label>
|
|
||||||
<textarea class="form-control-sm mb-3 pb-2 ms-0 me-0" wrap="hard" style="width: 100%" v-model="processObject.description"></textarea>
|
|
||||||
<ProcessTableComponent :viewProp="processObject.views" :tableTitleProp="'Ansichten'"></ProcessTableComponent>
|
|
||||||
<ProcessTableComponent :viewProp="processObject.workflows" :tableTitleProp="'Workflows'"></ProcessTableComponent>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import processes from "@/dummyData/processes.json";
|
|
||||||
import ProcessTableComponent from "@/components/process/ProcessTableComponent.vue";
|
|
||||||
export default {
|
|
||||||
name: "ProcessOverviewComponent",
|
|
||||||
// Verwendete Komponenten
|
|
||||||
components: {
|
|
||||||
ProcessTableComponent,
|
|
||||||
},
|
|
||||||
// Props für die Komponente
|
|
||||||
props: {
|
|
||||||
process: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Daten der Komponente
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
processObject: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Computed Propertiesprocesses.data
|
|
||||||
computed: {},
|
|
||||||
|
|
||||||
// Methoden
|
|
||||||
methods: {
|
|
||||||
getProcess(processId) {
|
|
||||||
if (processId) {
|
|
||||||
let currentProcess = processes.processes.find((processEntry) => processEntry.id === processId);
|
|
||||||
if (currentProcess) {
|
|
||||||
this.processObject = currentProcess;
|
|
||||||
} else {
|
|
||||||
console.error(`Prozess mit ID ${processId} nicht gefunden.`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Kein gültiger Prozess-ID übergeben.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Lifecycle-Hooks
|
|
||||||
mounted() {
|
|
||||||
if (this.process && this.process.id) {
|
|
||||||
this.getProcess(this.process.id);
|
|
||||||
} else {
|
|
||||||
console.error("Prozess ist undefiniert oder hat keine ID.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
66
src/utils/getObject.js.js
Normal file
66
src/utils/getObject.js.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import WebSocketService from "@/services/WebSocketService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronisiert Objekte per WebSocket über ID + Felder
|
||||||
|
* @param {string} subject
|
||||||
|
* @param {string[]} fields
|
||||||
|
* @param {(id: string, data: Object) => void} [onUpdate]
|
||||||
|
* @returns {{ dataMap: Object, unsubscribe: Function }}
|
||||||
|
*/
|
||||||
|
export function getObject(subject, fields = [], onUpdate) {
|
||||||
|
const dataMap = {};
|
||||||
|
const callbacks = {};
|
||||||
|
let idCallback = null;
|
||||||
|
|
||||||
|
idCallback = (value) => {
|
||||||
|
const ids = [...new Set(Array.isArray(value) ? value : [value])];
|
||||||
|
|
||||||
|
ids.forEach((id) => {
|
||||||
|
if (dataMap[id]) return;
|
||||||
|
|
||||||
|
dataMap[id] = {};
|
||||||
|
callbacks[id] = {};
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const callback = (val) => {
|
||||||
|
let safeValue;
|
||||||
|
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
if (val.length === 1) {
|
||||||
|
// Nur ein Wert → direkt den Wert extrahieren
|
||||||
|
safeValue = val[0]?.transientValue;
|
||||||
|
} else {
|
||||||
|
// Mehrere Werte → Liste aus transientValue extrahieren
|
||||||
|
safeValue = val.map((entry) => entry.transientValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
safeValue = undefined; // oder throw error / fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
dataMap[id][field] = safeValue;
|
||||||
|
|
||||||
|
if (typeof onUpdate === "function") {
|
||||||
|
onUpdate(id, { ...dataMap[id] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
callbacks[id][field] = callback;
|
||||||
|
WebSocketService.subscribe(subject, field, { _id: id }, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketService.subscribe(subject, "_id", {}, idCallback);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataMap,
|
||||||
|
unsubscribe: () => {
|
||||||
|
Object.entries(callbacks).forEach(([id, fieldMap]) => {
|
||||||
|
Object.entries(fieldMap).forEach(([field, cb]) => {
|
||||||
|
WebSocketService.unsubscribe(subject, field, { _id: id }, cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
WebSocketService.unsubscribe(subject, "_id", {}, idCallback);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
47
src/utils/getSpecificObject.js
Normal file
47
src/utils/getSpecificObject.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import WebSocketService from "@/services/WebSocketService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt ein spezifisches Objekt per WebSocket anhand eines Filters
|
||||||
|
* @param {string} subject
|
||||||
|
* @param {Object} filter
|
||||||
|
* @param {string[]} fields
|
||||||
|
* @param {(data: Object) => void} [onUpdate]
|
||||||
|
* @returns {{ object: Object, unsubscribe: Function }}
|
||||||
|
*/
|
||||||
|
export function getSpecificObject(subject, filter = {}, fields = [], onUpdate) {
|
||||||
|
const object = {};
|
||||||
|
const callbacks = {};
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const callback = (val) => {
|
||||||
|
let safeValue;
|
||||||
|
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
safeValue =
|
||||||
|
val.length === 1
|
||||||
|
? val[0]?.transientValue
|
||||||
|
: val.map((v) => v.transientValue);
|
||||||
|
} else {
|
||||||
|
safeValue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
object[field] = safeValue;
|
||||||
|
|
||||||
|
if (typeof onUpdate === "function") {
|
||||||
|
onUpdate({ ...object });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
callbacks[field] = callback;
|
||||||
|
WebSocketService.subscribe(subject, field, filter, callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
object,
|
||||||
|
unsubscribe: () => {
|
||||||
|
Object.entries(callbacks).forEach(([field, cb]) => {
|
||||||
|
WebSocketService.unsubscribe(subject, field, filter, cb);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user