-
@@ -31,12 +54,42 @@ export default {
data() {
return {
menuItems: [
- { icon: "carbon:home", text: "Dashboard", componentName: "DevelopmentDashboardComponent", menuItem: "home" },
- { 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" },
+ {
+ icon: "carbon:home",
+ text: "Dashboard",
+ componentName: "DevelopmentDashboardComponent",
+ menuItem: "home",
+ },
+ {
+ 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(),
popoverVisible: false,
@@ -48,7 +101,9 @@ export default {
// Computed Properties
computed: {
activeMenuItem() {
- const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
+ const activeComponent = this.uiStore.openComponents.find(
+ (comp) => comp.active
+ );
return activeComponent ? activeComponent.menuItem : null;
},
},
@@ -66,7 +121,9 @@ export default {
top: `${rect.top + window.scrollY + 10}px`, // Höhe relativ zur Seite
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.popoverVisible = true;
@@ -110,7 +167,7 @@ export default {
.popover-box {
position: absolute;
- background: var(--bs-primary);
+ background: #142330;
color: white;
padding: 6px;
border-radius: 5px;
diff --git a/src/layouts/DevelopmentLayout.vue b/src/layouts/DevelopmentLayout.vue
index a902b60..bff9a7b 100644
--- a/src/layouts/DevelopmentLayout.vue
+++ b/src/layouts/DevelopmentLayout.vue
@@ -2,10 +2,13 @@
-
test
+
-
+
Lade Seite...
@@ -35,18 +38,32 @@ export default {
computed: {
activePageComponent() {
const components = {
- Dashboard: defineAsyncComponent(() => import("@/pages/DevelopmentDashboard.vue")),
- TestComponent: defineAsyncComponent(() => import("@/pages/TestComponent.vue")),
- ProcessOverviewComponent: defineAsyncComponent(() => import("@/pages/ProcessOverviewComponent.vue")),
- FormDesignerLayout: defineAsyncComponent(() => import("@/layouts/FormDesignerLayout.vue")),
+ Dashboard: defineAsyncComponent(() =>
+ import("@/pages/DevelopmentDashboard.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() {
- const activeComponent = this.uiStore.openComponents.find((comp) => comp.active);
+ const activeComponent = this.uiStore.openComponents.find(
+ (comp) => comp.active
+ );
return activeComponent?.props || {};
},
},
diff --git a/src/layouts/FormDesignerLayout.vue b/src/layouts/FormDesignerLayout.vue
deleted file mode 100644
index 826e363..0000000
--- a/src/layouts/FormDesignerLayout.vue
+++ /dev/null
@@ -1,272 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main.js b/src/main.js
index 4f81ae2..2f34877 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,8 +5,13 @@ import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import "@/assets/custom-bootstrap.css";
-const app = createApp(App);
-const pinia = createPinia();
+import { client } from "@/modules/shared/services/WebSocketService";
-app.use(pinia);
-app.mount("#app");
+(async () => {
+ await client.connect();
+
+ const app = createApp(App);
+ const pinia = createPinia();
+ app.use(pinia);
+ app.mount("#app");
+})();
diff --git a/src/modules/process/components/formDesigner/ComponentRenderer.vue b/src/modules/process/components/formDesigner/ComponentRenderer.vue
new file mode 100644
index 0000000..99e063f
--- /dev/null
+++ b/src/modules/process/components/formDesigner/ComponentRenderer.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/process/components/formDesigner/FormDesignerToolsComponent.vue b/src/modules/process/components/formDesigner/FormDesignerToolsComponent.vue
new file mode 100644
index 0000000..3c23ebe
--- /dev/null
+++ b/src/modules/process/components/formDesigner/FormDesignerToolsComponent.vue
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/formDesigner/FormDesignerTreeViewComponent.vue b/src/modules/process/components/formDesigner/FormDesignerTreeViewComponent.vue
similarity index 100%
rename from src/components/formDesigner/FormDesignerTreeViewComponent.vue
rename to src/modules/process/components/formDesigner/FormDesignerTreeViewComponent.vue
diff --git a/src/components/formDesigner/FormRendererComponent.vue b/src/modules/process/components/formDesigner/FormRendererComponent.vue
similarity index 60%
rename from src/components/formDesigner/FormRendererComponent.vue
rename to src/modules/process/components/formDesigner/FormRendererComponent.vue
index 68f9f38..5d36cdd 100644
--- a/src/components/formDesigner/FormRendererComponent.vue
+++ b/src/modules/process/components/formDesigner/FormRendererComponent.vue
@@ -1,9 +1,13 @@
-
+
+
+
diff --git a/src/components/formDesigner/formRenderer/InputDateRenderer.vue b/src/modules/process/components/formDesigner/renderer/InputDateRenderer.vue
similarity index 100%
rename from src/components/formDesigner/formRenderer/InputDateRenderer.vue
rename to src/modules/process/components/formDesigner/renderer/InputDateRenderer.vue
diff --git a/src/components/formDesigner/formRenderer/InputNumberRenderer.vue b/src/modules/process/components/formDesigner/renderer/InputNumberRenderer.vue
similarity index 100%
rename from src/components/formDesigner/formRenderer/InputNumberRenderer.vue
rename to src/modules/process/components/formDesigner/renderer/InputNumberRenderer.vue
diff --git a/src/components/formDesigner/formRenderer/InputTextRenderer.vue b/src/modules/process/components/formDesigner/renderer/InputTextRenderer.vue
similarity index 100%
rename from src/components/formDesigner/formRenderer/InputTextRenderer.vue
rename to src/modules/process/components/formDesigner/renderer/InputTextRenderer.vue
diff --git a/src/components/formDesigner/formRenderer/LabelRenderer.vue b/src/modules/process/components/formDesigner/renderer/LabelRenderer.vue
similarity index 68%
rename from src/components/formDesigner/formRenderer/LabelRenderer.vue
rename to src/modules/process/components/formDesigner/renderer/LabelRenderer.vue
index 2294be3..d0e1621 100644
--- a/src/components/formDesigner/formRenderer/LabelRenderer.vue
+++ b/src/modules/process/components/formDesigner/renderer/LabelRenderer.vue
@@ -1,12 +1,12 @@
-
+
diff --git a/src/modules/process/components/process/ProcessTableComponent.vue b/src/modules/process/components/process/ProcessTableComponent.vue
new file mode 100644
index 0000000..54499e0
--- /dev/null
+++ b/src/modules/process/components/process/ProcessTableComponent.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+ {{ tableTitleProp }} ({{ viewProp?.length || 0 }})
+
+
+
+
+
+
+
+
+
+ | Name |
+ Änderungsdatum |
+
+
+
+
+ | {{ row.name }} |
+ {{ row.changed }} |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/process/layouts/FormDesignerLayout.vue b/src/modules/process/layouts/FormDesignerLayout.vue
new file mode 100644
index 0000000..4dee8bf
--- /dev/null
+++ b/src/modules/process/layouts/FormDesignerLayout.vue
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/process/models/formDesigner/FormBlock.js b/src/modules/process/models/formDesigner/FormBlock.js
new file mode 100644
index 0000000..b14596f
--- /dev/null
+++ b/src/modules/process/models/formDesigner/FormBlock.js
@@ -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;
+ }
+}
diff --git a/src/modules/process/models/formDesigner/FormBlockFactory.js b/src/modules/process/models/formDesigner/FormBlockFactory.js
new file mode 100644
index 0000000..a818384
--- /dev/null
+++ b/src/modules/process/models/formDesigner/FormBlockFactory.js
@@ -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}`);
+ }
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/ButtonBlock.js b/src/modules/process/models/formDesigner/blocks/ButtonBlock.js
new file mode 100644
index 0000000..b39cdc8
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/ButtonBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/FlexLayoutBlock.js b/src/modules/process/models/formDesigner/blocks/FlexLayoutBlock.js
new file mode 100644
index 0000000..5a24865
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/FlexLayoutBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/InputDateBlock.js b/src/modules/process/models/formDesigner/blocks/InputDateBlock.js
new file mode 100644
index 0000000..d17ad5e
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/InputDateBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/InputNumberBlock.js b/src/modules/process/models/formDesigner/blocks/InputNumberBlock.js
new file mode 100644
index 0000000..a85a5d0
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/InputNumberBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/InputTextBlock.js b/src/modules/process/models/formDesigner/blocks/InputTextBlock.js
new file mode 100644
index 0000000..6727fd5
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/InputTextBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/formDesigner/blocks/LabelBlock.js b/src/modules/process/models/formDesigner/blocks/LabelBlock.js
new file mode 100644
index 0000000..8443a86
--- /dev/null
+++ b/src/modules/process/models/formDesigner/blocks/LabelBlock.js
@@ -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");
+ }
+}
diff --git a/src/modules/process/models/process/Process.js b/src/modules/process/models/process/Process.js
new file mode 100644
index 0000000..4f080ef
--- /dev/null
+++ b/src/modules/process/models/process/Process.js
@@ -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"];
+ }
+}
diff --git a/src/modules/process/pages/ProcessOverviewComponent.vue b/src/modules/process/pages/ProcessOverviewComponent.vue
new file mode 100644
index 0000000..bef3530
--- /dev/null
+++ b/src/modules/process/pages/ProcessOverviewComponent.vue
@@ -0,0 +1,76 @@
+
+
+
{{ processObject.name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/shared/models/Basemodel.js b/src/modules/shared/models/Basemodel.js
new file mode 100644
index 0000000..7db6686
--- /dev/null
+++ b/src/modules/shared/models/Basemodel.js
@@ -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()]);
+ }
+}
diff --git a/src/modules/shared/services/SubscriptionManager.js b/src/modules/shared/services/SubscriptionManager.js
new file mode 100644
index 0000000..a751260
--- /dev/null
+++ b/src/modules/shared/services/SubscriptionManager.js
@@ -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];
+ }
+ }
+}
diff --git a/src/modules/shared/services/WebSocketClient.js b/src/modules/shared/services/WebSocketClient.js
new file mode 100644
index 0000000..61666b6
--- /dev/null
+++ b/src/modules/shared/services/WebSocketClient.js
@@ -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);
+ }
+}
diff --git a/src/modules/shared/services/WebSocketService.js b/src/modules/shared/services/WebSocketService.js
new file mode 100644
index 0000000..e21226d
--- /dev/null
+++ b/src/modules/shared/services/WebSocketService.js
@@ -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;
diff --git a/src/pages/DevelopmentDashboard.vue b/src/pages/DevelopmentDashboard.vue
index d5a7412..e151e04 100644
--- a/src/pages/DevelopmentDashboard.vue
+++ b/src/pages/DevelopmentDashboard.vue
@@ -1,32 +1,17 @@
-
- processes
-
-
+
Prozesse
- {{ processData[process.id]?.name || "Loading..." }}
+ {{ object.name || "Loading..." }}
- {{ process.description }}
+ {{ object.description }}
@@ -53,17 +38,16 @@
diff --git a/src/pages/ProcessOverviewComponent.vue b/src/pages/ProcessOverviewComponent.vue
deleted file mode 100644
index 2dc1acc..0000000
--- a/src/pages/ProcessOverviewComponent.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
{{ processObject.name }}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/utils/getObject.js.js b/src/utils/getObject.js.js
new file mode 100644
index 0000000..3805d56
--- /dev/null
+++ b/src/utils/getObject.js.js
@@ -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);
+ },
+ };
+}
diff --git a/src/utils/getSpecificObject.js b/src/utils/getSpecificObject.js
new file mode 100644
index 0000000..82944db
--- /dev/null
+++ b/src/utils/getSpecificObject.js
@@ -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);
+ });
+ },
+ };
+}