diff --git a/frontend/src/components/componentsPatientEditor/PatientStateForm.vue b/frontend/src/components/componentsPatientEditor/PatientStateForm.vue index 731d3681..be814ec1 100644 --- a/frontend/src/components/componentsPatientEditor/PatientStateForm.vue +++ b/frontend/src/components/componentsPatientEditor/PatientStateForm.vue @@ -8,7 +8,6 @@ (newVal) => { const patientStateFormNode = getNode('patientStateForm') patientStateFormNode?.input(newVal) - console.log(newVal) } ) diff --git a/frontend/src/rete/data/data2.json b/frontend/src/rete/data/data2.json new file mode 100644 index 00000000..cdba342b --- /dev/null +++ b/frontend/src/rete/data/data2.json @@ -0,0 +1,235 @@ +{ + "info": { + "triage": "rot", + "sex": "männlich", + "age": "21", + "address": "Neue Straße, 10101 Burgstadt", + "injury": "Abgetrennter Arm", + "biometrics": "rote Haare, blaue Augen, 1.85m", + "mobility": "initial gefähig", + "preexistingIllnesses": "keine", + "permanentMedication": "keine", + "currentCaseHistory": "keine", + "pretreatment": "Tourniquet" + }, + "flow": [ + { + "id": "9235871077f3ce09", + "type": "State", + "next": null + }, + { + "id": "412dd1fc78142fbe", + "type": "State", + "next": null + }, + { + "type": "Transition", + "next": null + } + ], + "states": [ + { + "id": "9235871077f3ce09", + "airway": "freie Atemwege", + "breathingRate": 1, + "oxygenSaturation": 1, + "breathing": "vertiefte Atmung", + "breathingSound": true, + "breathingLoudness": "sehr leises AG hörbar", + "heartRate": 1, + "pulsePalpable": "peripher tastbar", + "rivaRocci": "1/1", + "consciousness": "wach, orientiert", + "pupils": "mittelweit", + "psyche": "unauffällig", + "skinFining": "trocken", + "skinDiscoloration": "rosig", + "bgaOxy": 601, + "bgaSbh": 652, + "hb": 401, + "bz": 916, + "clotting": 100, + "liver": 111, + "kidney": 120, + "infarct": 134, + "lactate": 144, + "extremities": 523, + "thorax": 340, + "trauma": 108, + "ultraschall": 621, + "ekg": 746, + "zvd": 805 + }, + { + "id": "412dd1fc78142fbe", + "airway": "freie Atemwege", + "breathingRate": 2, + "oxygenSaturation": 1, + "breathing": "vertiefte Atmung", + "breathingSound": true, + "breathingLoudness": "sehr leises AG hörbar", + "heartRate": 1, + "pulsePalpable": "peripher tastbar", + "rivaRocci": "1/1", + "consciousness": "wach, orientiert", + "pupils": "mittelweit", + "psyche": "unauffällig", + "skinFining": "trocken", + "skinDiscoloration": "rosig", + "bgaOxy": 601, + "bgaSbh": 652, + "hb": 401, + "bz": 916, + "clotting": 100, + "liver": 111, + "kidney": 120, + "infarct": 134, + "lactate": 144, + "extremities": 523, + "thorax": 340, + "trauma": 108, + "ultraschall": 621, + "ekg": 746, + "zvd": 805 + } + ], + "transitions": [ + { + "id": "Transition 1", + "flow": [ + { + "id": "86ebeaf375f35d3d", + "type": "Action", + "action": "72d6f3ad-b9a5-4b16-b289-815766b27c4f", + "quantity": 1, + "next": { + "true": "84fceae523792447", + "false": "0adde205c19f0837" + } + }, + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 1, + "next": { + "true": null, + "false": null + } + }, + { + "id": "84fceae523792447", + "type": "Action", + "action": "7613f009-cb28-411d-91a9-26fe438a3bc2", + "quantity": 1, + "next": { + "true": "5974baa226eb4354", + "false": null + } + }, + { + "id": "5974baa226eb4354", + "type": "Action", + "action": "9ed9b301-d4f8-4e81-8e33-f26bbdc24104", + "quantity": 4, + "next": { + "true": "7ac8b0e92ffbf325", + "false": null + } + }, + { + "id": "7ac8b0e92ffbf325", + "type": "Output" + }, + { + "id": "4ec2677dadb4e909", + "type": "Input", + "next": "86ebeaf375f35d3d" + } + ] + }, + { + "id": "Transition 2", + "flow": [ + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 2, + "next": { + "true": null, + "false": null + } + }, + { + "id": "5974baa226eb4354", + "type": "Action", + "action": "9ed9b301-d4f8-4e81-8e33-f26bbdc24104", + "quantity": 2, + "next": { + "true": null, + "false": null + } + } + ] + } + ], + "components": [ + { + "id": "Komponente 1", + "flow": [ + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 1, + "next": { + "true": null, + "false": null + } + }, + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 2, + "next": { + "true": null, + "false": null + } + }, + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 3, + "next": { + "true": null, + "false": null + } + }, + { + "id": "0adde205c19f0837", + "type": "Action", + "action": "cc8a5284-765d-4854-b634-6c9e5a85d916", + "quantity": 4, + "next": { + "true": null, + "false": null + } + }, + { + "id": "5974baa226eb4354", + "type": "Action", + "action": "9ed9b301-d4f8-4e81-8e33-f26bbdc24104", + "quantity": 5, + "next": { + "true": null, + "false": null + } + } + ] + } + ] +} \ No newline at end of file diff --git a/frontend/src/rete/editor.ts b/frontend/src/rete/editor.ts index 3d97d9b5..8d1bc50f 100644 --- a/frontend/src/rete/editor.ts +++ b/frontend/src/rete/editor.ts @@ -51,6 +51,7 @@ export async function createEditor(container: HTMLElement) { ["Input", () => createNode(context, "Input", { key: "key" })], ["Output", () => createNode(context, "Output", { key: "key" })], ["Module", () => createNode(context, "Module", { name: "" })], + ["Transition", () => createNode(context, "Transition", {})], ["Action", () => createNode(context, "Action", {})] ]) }) @@ -151,7 +152,7 @@ export async function createEditor(container: HTMLElement) { async (id, editor) => { const data = transitionModulesData.find((module) => module.id === id)?.flow - if (!data) throw new Error("cannot find module") + if (!data) throw new Error("cannot find module "+id) await importEditor( { ...context, @@ -181,9 +182,10 @@ export async function createEditor(container: HTMLElement) { const context: Context = { editor, area, - modules: transitionModules, dataflow, - process + process, + transitionModules, + componentModules } await process() diff --git a/frontend/src/rete/import.ts b/frontend/src/rete/import.ts index 219dbcd8..c4b99104 100644 --- a/frontend/src/rete/import.ts +++ b/frontend/src/rete/import.ts @@ -7,23 +7,23 @@ import { OutputNode, StateNode, ActionNode, - isInRangeNode + isInRangeNode, + TransitionNode } from "./nodes/index" import { removeConnections } from "./utils" import { ActionIDs } from "./constants" import { DropdownOption } from "./dropdown" import { addState } from "@/components/ModulePatientEditor.vue" -import { State } from "./types" export async function createNode( - { editor, area, dataflow, modules, process }: Context, + { editor, area, dataflow, process, transitionModules, componentModules }: Context, type: string, data: any ) { if (type === "Number") return new NumberNode(data.value, process) if (type === "Add") return new AddNode(process, data) - if (type === "Input") return new InputNode(data.key) - if (type === "Output") return new OutputNode(data.key) + if (type === "Input") return new InputNode(data?.key || "key") + if (type === "Output") return new OutputNode(data?.key || "key") if (type === "Module") { const node = new ModuleNode( data.type, @@ -35,9 +35,21 @@ export async function createNode( } ) await node.update() - + + console.log("Module", node) return node } + if (type === "Transition") { + const transitionNode = new TransitionNode( + transitionModules.findModule, + (id) => removeConnections(editor, id), + (id) => { + area.update("node", id) + process() + } + ) + return transitionNode + } if (type === "State") { const stateNode = new StateNode() addState(stateNode.id) @@ -86,11 +98,17 @@ export async function importEditor(context: Context, nodes: any) { createConnection(source, "false", target2, "in", context) } - if (n.type === "State" || n.type === "Transition" || n.type === "Input") { + if (n.type === "State" || n.type === "Transition") { const source = context.editor.getNode(n.id) const target = context.editor.getNode(n.next) createConnection(source, "next", target, "in", context) } + + if (n.type === "Input") { + const source = context.editor.getNode(n.id) + const target = context.editor.getNode(n.next) + createConnection(source, "out", target, "in", context) + } } } @@ -109,6 +127,8 @@ async function createConnection(source: any, sourceOutput: any, target: any, tar ) await context.editor.addConnection(conn) + } else { + //throw new Error("Invalid connection:" + [source?.id, sourceOutput, target?.id, targetInput]) } } @@ -134,7 +154,14 @@ export function exportEditor(context: Context) { nodes.push({ id: n.id, type: n.label, - //next: n.outputs.get("out").connections[0].input.node.id + next: connections.filter(c => c.source === n.id)[0]?.target || null, + }) + } + + if (n.label === "Output") { + nodes.push({ + id: n.id, + type: n.label, }) } } diff --git a/frontend/src/rete/nodes/index.ts b/frontend/src/rete/nodes/index.ts index 025222ed..55cc3e44 100644 --- a/frontend/src/rete/nodes/index.ts +++ b/frontend/src/rete/nodes/index.ts @@ -5,4 +5,5 @@ export { OutputNode } from "./output" export { ModuleNode } from "./module" export { StateNode } from "./state" export { ActionNode } from "./action.js" -export { isInRangeNode } from "./isInRange" \ No newline at end of file +export { isInRangeNode } from "./isInRange" +export { TransitionNode } from "./transition" \ No newline at end of file diff --git a/frontend/src/rete/nodes/input.ts b/frontend/src/rete/nodes/input.ts index 3354d790..eff3a9c8 100644 --- a/frontend/src/rete/nodes/input.ts +++ b/frontend/src/rete/nodes/input.ts @@ -5,7 +5,7 @@ import { socket } from "../sockets" export class InputNode extends Classic.Node< {}, - { value: Classic.Socket }, + { out: Classic.Socket }, { key: Classic.InputControl<"text"> } > implements DataflowNode { @@ -17,7 +17,7 @@ export class InputNode super("Input") this.addControl("key", new Classic.InputControl("text", { initial })) - this.addOutput("value", new Classic.Output(socket, "Number")) + this.addOutput("out", new Classic.Output(socket, "out")) } data() { diff --git a/frontend/src/rete/nodes/output.ts b/frontend/src/rete/nodes/output.ts index 81d8f2c2..87a623bb 100644 --- a/frontend/src/rete/nodes/output.ts +++ b/frontend/src/rete/nodes/output.ts @@ -1,31 +1,31 @@ -import { ClassicPreset as Classic } from "rete"; -import { DataflowNode } from "rete-engine"; -import { socket } from "../sockets"; +import { ClassicPreset as Classic } from "rete" +import { DataflowNode } from "rete-engine" +import { socket } from "../sockets" export class OutputNode extends Classic.Node< - { value: Classic.Socket }, + { in: Classic.Socket }, {}, { key: Classic.InputControl<"text"> } > implements DataflowNode { - width = 180; - height = 140; + width = 180 + height = 140 constructor(initial: string) { - super("Output"); + super("Output") - this.addControl("key", new Classic.InputControl("text", { initial })); - this.addInput("value", new Classic.Input(socket, "Number")); + this.addControl("key", new Classic.InputControl("text", { initial })) + this.addInput("in", new Classic.Input(socket, "in")) } data() { - return {}; + return {} } serialize() { return { key: this.controls.key.value - }; + } } } diff --git a/frontend/src/rete/nodes/transition.ts b/frontend/src/rete/nodes/transition.ts new file mode 100644 index 00000000..508028f0 --- /dev/null +++ b/frontend/src/rete/nodes/transition.ts @@ -0,0 +1,88 @@ +import { ClassicPreset as Classic, NodeEditor } from "rete" +import { DataflowNode } from "rete-engine" +import { socket } from "../sockets" +import { Module, Modules } from "../modules" +import { Schemes } from "../types" +import { DropdownControl, DropdownOption } from "../dropdown" + +export class TransitionNode + extends Classic.Node< + Record, + Record, + { selection: DropdownControl } + > + implements DataflowNode { + width = 180 + height = 140 + module: null | Module = null + + constructor( + private findModule: (id: string) => null | Module, + private reset: (nodeId: string) => Promise, + updateUI: (nodeId: string) => void, + initialSelection?: DropdownOption, + ) { + super("Transition") + + this.addControl( + "selection", + new DropdownControl( + [ + { name: "Transition 1", value: "Transition 1" }, + { name: "Transition 2", value: "Transition 2" } + ], + initialSelection, + async (value) => { + this.id = value + await this.update() + updateUI(this.id) + } + ) + ) + this.update() + } + + async update() { + this.module = this.findModule(this.path) + + await this.reset(this.id) + if (this.module) { + const editor = new NodeEditor() + await this.module.apply(editor) + + const { inputs, outputs } = Modules.getPorts(editor) + this.syncPorts(inputs, outputs) + } else this.syncPorts([], []) + } + + syncPorts(inputs: string[], outputs: string[]) { + Object.keys(this.inputs).forEach((key: keyof typeof this.inputs) => + this.removeInput(key) + ) + Object.keys(this.outputs).forEach((key: keyof typeof this.outputs) => + this.removeOutput(key) + ) + + inputs.forEach((key) => { + this.addInput(key, new Classic.Input(socket, key)) + }) + outputs.forEach((key) => { + this.addOutput(key, new Classic.Output(socket, key)) + }) + this.height = + 110 + + 25 * (Object.keys(this.inputs).length + Object.keys(this.outputs).length) + } + + async data(inputs: Record) { + const data = await this.module?.exec(inputs) + + return data || {} + } + + serialize() { + return { + name: this.controls.module.value + } + } +} diff --git a/frontend/src/rete/types.ts b/frontend/src/rete/types.ts index fac3bb32..16111583 100644 --- a/frontend/src/rete/types.ts +++ b/frontend/src/rete/types.ts @@ -13,10 +13,11 @@ import { ModuleNode, StateNode, ActionNode, - isInRangeNode + isInRangeNode, + TransitionNode } from "./nodes/index.js" -export type Node = StateNode | AddNode | NumberNode | InputNode | OutputNode | ModuleNode | ActionNode | isInRangeNode; +export type Node = StateNode | AddNode | NumberNode | InputNode | OutputNode | ModuleNode | ActionNode | isInRangeNode | TransitionNode export class Connection extends Classic.Connection {} @@ -35,10 +36,11 @@ export type AreaExtra = export type Context = { process: () => void - modules: Modules editor: NodeEditor area: AreaPlugin dataflow: DataflowEngine + transitionModules: Modules + componentModules: Modules } export interface Editor {