From 1946deb75138f5ea9f58f53cd3cec8c236c1f78f Mon Sep 17 00:00:00 2001 From: zichun <26684461+reporkey@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:11:51 +0800 Subject: [PATCH] feat: 模型可视化 + 结构化/源码编辑工作台 --- engine/src/main/java/com/onto/engine/model/ModelRepository.java | 36 ++++++++++++++++++++++++++++++++++++ engine/src/main/java/com/onto/engine/web/MetaController.java | 44 +++++++++++++++++++++++++++++++++++++++----- web/package-lock.json | 700 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- web/package.json | 10 +++++++++- web/src/App.tsx | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------ web/src/api.ts | 10 ++++++++-- web/src/model/ModelEditor.tsx | 559 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/model/editSession.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/ontology/bpmn.ts | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/ontology/build.ts | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/ontology/types.ts | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/viz/ERDiagram.tsx | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/viz/NetworkGraph.tsx | 614 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/viz/OntologyCanvas.tsx | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/src/viz/ProcessDiagram.tsx | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ web/vite.config.ts | 2 +- 16 files changed, 3187 insertions(+), 85 deletions(-) create mode 100644 web/src/model/ModelEditor.tsx create mode 100644 web/src/model/editSession.ts create mode 100644 web/src/ontology/bpmn.ts create mode 100644 web/src/ontology/build.ts create mode 100644 web/src/ontology/types.ts create mode 100644 web/src/viz/ERDiagram.tsx create mode 100644 web/src/viz/NetworkGraph.tsx create mode 100644 web/src/viz/OntologyCanvas.tsx create mode 100644 web/src/viz/ProcessDiagram.tsx diff --git a/engine/src/main/java/com/onto/engine/model/ModelRepository.java b/engine/src/main/java/com/onto/engine/model/ModelRepository.java index 65db532..94d14e7 100644 --- a/engine/src/main/java/com/onto/engine/model/ModelRepository.java +++ b/engine/src/main/java/com/onto/engine/model/ModelRepository.java @@ -96,6 +96,42 @@ public class ModelRepository { /** 返回某层解析后的原始对象(供模型查看器/前端"运行原理"面板展示)。 */ public Object rawLayer(String code) { return layers.get(code); } + /** 返回某层 YAML 源文件的原始文本(保留注释/顺序,供模型编辑器编辑)。 */ + public String rawFileText(String code) { + String fileName = LAYER_FILES.get(code); + if (fileName == null) return null; + Path dir = resolvedDir != null ? resolvedDir : resolveModelsDir(); + try { + return Files.readString(dir.resolve(fileName)); + } catch (Exception ex) { + log.warn("读取模型源文件失败: {} -> {}", code, ex.getMessage()); + return null; + } + } + + /** + * 覆盖写入某层 YAML 源文件(模型编辑器保存用)。先校验能被解析,避免把源文件写坏、 + * 导致后续 reload 全线失败。写入后需由调用方触发 reload 才会生效。返回写入的文件路径。 + */ + public synchronized Path writeRawLayer(String code, String yamlText) { + String fileName = LAYER_FILES.get(code); + if (fileName == null) throw new IllegalArgumentException("未知模型层: " + code); + try { + new Yaml(new LoaderOptions()).load(yamlText); // 解析校验:不通过则拒绝写入 + } catch (Exception ex) { + throw new IllegalArgumentException("YAML 解析失败,未写入: " + ex.getMessage(), ex); + } + Path dir = resolvedDir != null ? resolvedDir : resolveModelsDir(); + Path f = dir.resolve(fileName); + try { + Files.writeString(f, yamlText); + } catch (Exception ex) { + throw new IllegalStateException("写入模型文件失败: " + f + " -> " + ex.getMessage(), ex); + } + log.info("模型层已保存: {} -> {}", code, f.toAbsolutePath()); + return f; + } + @SuppressWarnings("unchecked") static Map asMap(Object o) { return o instanceof Map ? (Map) o : Collections.emptyMap(); diff --git a/engine/src/main/java/com/onto/engine/web/MetaController.java b/engine/src/main/java/com/onto/engine/web/MetaController.java index 1fde2ea..920f809 100644 --- a/engine/src/main/java/com/onto/engine/web/MetaController.java +++ b/engine/src/main/java/com/onto/engine/web/MetaController.java @@ -90,6 +90,35 @@ public class MetaController { out.put("code", code); out.put("fileName", models.fileNameOf(code)); out.put("content", models.rawLayer(code)); + out.put("raw", models.rawFileText(code)); // 源文件原文(保留注释),供模型编辑器编辑 + return out; + } + + /** + * 保存某一层模型 YAML 源文件(模型编辑器用),写入后立即热重载生效。 + * 请求体:{ "content": "<完整 YAML 文本>" }。与热重载同级:仅管理员可保存。 + */ + @PutMapping("/model/{code}") + public Map saveModel(@PathVariable String code, + @RequestBody Map body, + @RequestHeader(value = "X-Principal", required = false) String principal) { + requireAdmin(principal, "保存模型"); + Object content = body == null ? null : body.get("content"); + if (!(content instanceof String yaml) || yaml.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "缺少 content(YAML 文本)"); + } + try { + models.writeRawLayer(code, yaml); + } catch (IllegalArgumentException ex) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getMessage()); + } + models.reload(); // 写入后立即生效(含 M1/M3 变更 → 补表) + schemaInitializer.refresh(); + Map out = new LinkedHashMap<>(); + out.put("saved", true); + out.put("code", code); + out.put("fileName", models.fileNameOf(code)); + out.put("message", "模型 " + code + " 已保存并热重载生效"); return out; } @@ -99,11 +128,7 @@ public class MetaController { */ @PostMapping("/reload") public Map reload(@RequestHeader(value = "X-Principal", required = false) String principal) { - List admins = asList(models.frontGlobalConfig().get("dragRolePermission")); - if (!admins.isEmpty() && !admins.contains(principal)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "仅管理员 " + admins + " 可热重载模型(当前主体:" + principal + ")"); - } + requireAdmin(principal, "热重载模型"); models.reload(); schemaInitializer.refresh(); Map out = new LinkedHashMap<>(); @@ -113,6 +138,15 @@ public class MetaController { return out; } + /** 仅 M8 dragRolePermission 声明的管理员可执行敏感操作(保存/热重载)。 */ + private void requireAdmin(String principal, String action) { + List admins = asList(models.frontGlobalConfig().get("dragRolePermission")); + if (!admins.isEmpty() && !admins.contains(principal)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "仅管理员 " + admins + " 可" + action + "(当前主体:" + principal + ")"); + } + } + @SuppressWarnings("unchecked") private static List asList(Object o) { return o instanceof List ? (List) o : java.util.Collections.emptyList(); diff --git a/web/package-lock.json b/web/package-lock.json index 4dc5f3d..8241323 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -9,10 +9,18 @@ "version": "2.0.0", "dependencies": { "@ant-design/icons": "^5.5.1", + "@dagrejs/dagre": "^3.0.0", + "@xyflow/react": "^12.11.2", "antd": "^5.21.6", + "bpmn-elk-layout": "^1.4.0", + "bpmn-js": "^18.22.0", + "cytoscape": "^3.34.0", + "cytoscape-fcose": "^2.2.0", "dayjs": "^1.11.13", + "js-yaml": "^4.3.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "yaml": "^2.9.0" }, "devDependencies": { "@types/react": "^18.3.12", @@ -410,6 +418,31 @@ "node": ">=6.9.0" } }, + "node_modules/@bpmn-io/diagram-js-ui": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@bpmn-io/diagram-js-ui/-/diagram-js-ui-0.2.4.tgz", + "integrity": "sha512-I678ZGtoH7z7FgzFhzzppM9ThsJ3Lh83kW3GjhjG3xyYciIPYHMaHK1MABP8F69H9pFZpViM6t4qUo8dwrVz0w==", + "license": "MIT", + "dependencies": { + "htm": "^3.1.1", + "preact": "^10.29.2" + } + }, + "node_modules/@dagrejs/dagre": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-3.0.0.tgz", + "integrity": "sha512-ZzhnTy1rfuoew9Ez3EIw4L2znPGnYYhfn8vc9c4oB8iw6QAsszbiU0vRhlxWPFnmmNSFAkrYeF1PhM5m4lAN0Q==", + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "4.0.1" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-4.0.1.tgz", + "integrity": "sha512-IvcV6FduIIAmLwnH+yun+QtV36SC7mERqa86aClNqmMN09WhmPPYU8ckHrZBozErf+UvHPWOTJYaGYiIcs0DgA==", + "license": "MIT" + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", @@ -1451,6 +1484,55 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1462,14 +1544,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1480,7 +1562,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -1507,6 +1589,48 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@xyflow/react": { + "version": "12.11.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.2.tgz", + "integrity": "sha512-eLAlDWJfWnQEhJwGMjlWdAXO9eYllKpliUmPQlAmOLxz6mExXuzMVDUKLMquixgkrtmMFFtug3jGKmYYld12cA==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.79", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", + "react": ">=17", + "react-dom": ">=17" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.79", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.79.tgz", + "integrity": "sha512-czLyOh91NF0hIzbNzwi8I6GlqG23BHh2435OddfI6uiaLH3xdrdygO93gqgH1Bv9mhy8XPFQJOBn1FTq4LvEWA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/antd": { "version": "5.29.3", "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", @@ -1572,6 +1696,12 @@ "react-dom": ">=16.9.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/baseline-browser-mapping": { "version": "2.10.43", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", @@ -1585,6 +1715,113 @@ "node": ">=6.0.0" } }, + "node_modules/bpmn-elk-layout": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/bpmn-elk-layout/-/bpmn-elk-layout-1.4.0.tgz", + "integrity": "sha512-FKcZIbSa2PPCV/KbLiSCg4fqvHf2BUBPVp2nX3+dC5+gWpKIg2ICQZ4xdkIdi+IV9cwzQQ4M7d+7hZsZRPAT+Q==", + "license": "MIT", + "dependencies": { + "bpmn-moddle": "^9.0.1", + "commander": "^12.1.0", + "elkjs": "^0.9.3", + "kiwi.js": "1.1.3", + "pathfinding": "0.4.18", + "web-worker": "1.5.0" + }, + "bin": { + "bpmn-elk-layout": "dist/bin/bpmn-elk-layout.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bpmn-js": { + "version": "18.22.0", + "resolved": "https://registry.npmjs.org/bpmn-js/-/bpmn-js-18.22.0.tgz", + "integrity": "sha512-hM5aOiyLVdVGA80nveJLXFVQMpsaST7RilErjeKfXMhpZQJTnzU+yLDaenIJsq2f90+9inyktVPqsS1Ersik2A==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "bpmn-moddle": "^10.0.0", + "diagram-js": "^15.23.0", + "diagram-js-direct-editing": "^3.5.1", + "ids": "^3.0.2", + "inherits-browser": "^0.1.0", + "min-dash": "^5.1.0", + "min-dom": "^5.3.0", + "tiny-svg": "^4.1.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bpmn-js/node_modules/bpmn-moddle": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-10.0.0.tgz", + "integrity": "sha512-vXePD5jkatcILmM3zwJG/m6IIHIghTGB7WvgcdEraEw8E8VdJHrTgrvBUhbzqaXJpnsGQz15QS936xeBY6l9aA==", + "license": "MIT", + "dependencies": { + "min-dash": "^5.0.0", + "moddle": "^8.0.0", + "moddle-xml": "^12.0.0" + }, + "engines": { + "node": ">= 20.12" + } + }, + "node_modules/bpmn-js/node_modules/moddle": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/moddle/-/moddle-8.2.0.tgz", + "integrity": "sha512-okP9kfyOJrUxCVkwy9xpTQ54UmrEkMkjNWsyZNU9+uhdjYO43BLftUrgmaOBBBGZ7hUbJSevJFfRamxiCzaQlw==", + "license": "MIT", + "dependencies": { + "min-dash": "^5.1.0" + } + }, + "node_modules/bpmn-js/node_modules/moddle-xml": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/moddle-xml/-/moddle-xml-12.1.0.tgz", + "integrity": "sha512-+m50j9/MFGhvWbLJoKSSjQj3uv7SYm2w5i2lfv6goWarlWW1Nd0A31NYUXmKuWnEZ8Mm/bH1RZYtuf4x8ptY7g==", + "license": "MIT", + "dependencies": { + "min-dash": "^5.1.0", + "saxen": "^11.1.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "moddle": ">= 6.2.0" + } + }, + "node_modules/bpmn-js/node_modules/saxen": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-11.1.0.tgz", + "integrity": "sha512-GOxBOAmiWVAytOHBuMlgFMZ4MAk+2Ny5QJpzM9I4IozfPLXq3FsDdIHNviTQGZGIx7G2mBkA+uySiJlYmSU1Gg==", + "license": "MIT", + "engines": { + "node": ">= 20.12" + } + }, + "node_modules/bpmn-moddle": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-9.0.4.tgz", + "integrity": "sha512-dr5s3vtOG8NkVSwa8CC55XBIKKwajomSZRb0RiMOOOF6TpqZBZvtbDjpzWICvdd/plDF6uOtaRfSgblPQLAioQ==", + "license": "MIT", + "dependencies": { + "min-dash": "^4.2.1", + "moddle": "^7.0.0", + "moddle-xml": "^11.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bpmn-moddle/node_modules/min-dash": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-4.2.3.tgz", + "integrity": "sha512-VLMYQI5+FcD9Ad24VcB08uA83B07OhueAlZ88jBK6PyupTvEJwllTMUqMy0wPGYs7pZUEtEEMWdHB63m3LtEcg==", + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.28.6", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", @@ -1640,12 +1877,36 @@ ], "license": "CC-BY-4.0" }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -1668,12 +1929,147 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dayjs": { "version": "1.11.21", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", @@ -1698,6 +2094,63 @@ } } }, + "node_modules/diagram-js": { + "version": "15.23.0", + "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-15.23.0.tgz", + "integrity": "sha512-cCgL+4Ep0xrLe6TtdvRVgo6bWYjUbrQg+ES/9rpltqmTD2YaPoC+NYcRsKYw/fDO+1RN0q16NzErP1svVZhNQg==", + "license": "MIT", + "dependencies": { + "@bpmn-io/diagram-js-ui": "^0.2.4", + "clsx": "^2.1.1", + "didi": "^11.0.0", + "inherits-browser": "^0.1.0", + "min-dash": "^5.1.0", + "min-dom": "^5.3.0", + "object-refs": "^0.4.0", + "path-intersection": "^4.1.0", + "tiny-svg": "^4.1.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/diagram-js-direct-editing": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/diagram-js-direct-editing/-/diagram-js-direct-editing-3.5.1.tgz", + "integrity": "sha512-fEQCQ/x5oKVV0m7Nmgv7i85Vhw+kJkKNXF8zcV5Vm0KwF8x/8e3Ax0aJgdXt8/fDPymEkY8HmZweXIipCeeh+g==", + "license": "MIT", + "dependencies": { + "min-dash": "^5.0.0", + "min-dom": "^5.3.0" + }, + "engines": { + "node": "*" + }, + "peerDependencies": { + "diagram-js": "*" + } + }, + "node_modules/didi": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/didi/-/didi-11.0.0.tgz", + "integrity": "sha512-PzCfRzQttvFpVcYMbSF7h8EsWjeJpVjWH4qDhB5LkMi1ILvHq4Ob0vhM2wLFziPkbUBi+PAo7ODbe2sacR7nJQ==", + "license": "MIT", + "engines": { + "node": ">= 20.12" + } + }, + "node_modules/domify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domify/-/domify-3.0.0.tgz", + "integrity": "sha512-bs2yO68JDFOm6rKv8f0EnrM2cENduhRkpqOtt/s5l5JBA/eqGBZCzLPmdYoHtJ6utgLGgcBajFsEQbl12pT0lQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.393", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", @@ -1705,6 +2158,12 @@ "dev": true, "license": "ISC" }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "license": "EPL-2.0" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -1779,12 +2238,60 @@ "node": ">=6.9.0" } }, + "node_modules/heap": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.5.tgz", + "integrity": "sha512-G7HLD+WKcrOyJP5VQwYZNC3Z6FcQ7YYjEFiFoIj8PfEr73mu421o8B1N5DKUcc8K37EsJ2XXWA8DtrDz/2dReg==" + }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "license": "Apache-2.0" + }, + "node_modules/ids": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ids/-/ids-3.0.2.tgz", + "integrity": "sha512-t6YJP4mdC+GHF96Nbis/4FEANhP/8VWmYMvUuYpXvSdrhg5hpIVbq2XZlOA3UWTbtdwPCi0q7jEXOdHkAnqOnw==", + "license": "MIT", + "engines": { + "node": ">= 20.12" + } + }, + "node_modules/inherits-browser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/inherits-browser/-/inherits-browser-0.1.0.tgz", + "integrity": "sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==", + "license": "ISC" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -1820,6 +2327,18 @@ "node": ">=6" } }, + "node_modules/kiwi.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/kiwi.js/-/kiwi.js-1.1.3.tgz", + "integrity": "sha512-aBA32N0LjuF0RIeV+OVhT8wnGAwdLYynKxkHbRvatI7yajOBM3OUjzTuzt3w9UnY9TYt7U65oe7U8EXlUHuTrQ==", + "license": "BSD-3-Clause" + }, + "node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1842,6 +2361,59 @@ "yallist": "^3.0.2" } }, + "node_modules/min-dash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-5.1.0.tgz", + "integrity": "sha512-HAvN6XzmCQj+js43G9sJRRw8kj/7DvoxN7qhoIN9Xo5ZN1NMljtpyKs/NunXmw0QSWwlgZzNv6tLeEa4xaZ0tg==", + "license": "MIT" + }, + "node_modules/min-dom": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/min-dom/-/min-dom-5.3.0.tgz", + "integrity": "sha512-0w5FEBgPAyHhmFojW3zxd7we3D+m5XYS3E/06OyvxmbHJoiQVa4Nagj6RWvoAKYRw5xth6cP5TMePc5cR1M9hA==", + "license": "MIT", + "dependencies": { + "domify": "^3.0.0", + "min-dash": "^5.0.0" + } + }, + "node_modules/moddle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/moddle/-/moddle-7.2.0.tgz", + "integrity": "sha512-x1+JREThy7JBOBR3g2hbOnOfrlC/YAWXX9RzrSZS5HhqeuBly9H/PCtOBtcQs+Y2sjRAXF+WTNSgHvn8Uq+6Yw==", + "license": "MIT", + "dependencies": { + "min-dash": "^4.2.1" + } + }, + "node_modules/moddle-xml": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/moddle-xml/-/moddle-xml-11.0.0.tgz", + "integrity": "sha512-L3Sseepfcq9Uy0iIfqEDTXSoYLva1Y/JGbN/4AMOeQ6cqbu8Ma/SDJIdOFm7smsAa64j2z3SwCGG3FIilQVnUg==", + "license": "MIT", + "dependencies": { + "min-dash": "^4.0.0", + "saxen": "^10.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "moddle": ">= 6.2.0" + } + }, + "node_modules/moddle-xml/node_modules/min-dash": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-4.2.3.tgz", + "integrity": "sha512-VLMYQI5+FcD9Ad24VcB08uA83B07OhueAlZ88jBK6PyupTvEJwllTMUqMy0wPGYs7pZUEtEEMWdHB63m3LtEcg==", + "license": "MIT" + }, + "node_modules/moddle/node_modules/min-dash": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-4.2.3.tgz", + "integrity": "sha512-VLMYQI5+FcD9Ad24VcB08uA83B07OhueAlZ88jBK6PyupTvEJwllTMUqMy0wPGYs7pZUEtEEMWdHB63m3LtEcg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1878,6 +2450,32 @@ "node": ">=18" } }, + "node_modules/object-refs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-refs/-/object-refs-0.4.0.tgz", + "integrity": "sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/path-intersection": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-4.1.0.tgz", + "integrity": "sha512-urUP6WvhnxbHPdHYl6L7Yrc6+1ny6uOFKPCzPxTSUSYGHG0o94RmI7SvMMaScNAM5RtTf08bg4skc6/kjfne3A==", + "license": "MIT", + "engines": { + "node": ">= 14.20" + } + }, + "node_modules/pathfinding": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/pathfinding/-/pathfinding-0.4.18.tgz", + "integrity": "sha512-R0TGEQ9GRcFCDvAWlJAWC+KGJ9SLbW4c0nuZRcioVlXVTlw+F5RvXQ8SQgSqI9KXWC1ew95vgmIiyaWTlCe9Ag==", + "dependencies": { + "heap": "0.2.5" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1914,6 +2512,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.29.7", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz", + "integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "preact-render-to-string": ">=5" + }, + "peerDependenciesMeta": { + "preact-render-to-string": { + "optional": true + } + } + }, "node_modules/rc-cascader": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", @@ -2612,6 +3228,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/saxen": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-10.0.0.tgz", + "integrity": "sha512-RXsmWok/SAWqOG/f5ADEz51DN9WtZEzqih3e08ranldcaXekxjx8NBKjGh/y5hlowjo0JH/LekBu6gtPFD1G6g==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -2671,6 +3296,15 @@ "node": ">=12.22" } }, + "node_modules/tiny-svg": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tiny-svg/-/tiny-svg-4.1.4.tgz", + "integrity": "sha512-cBaEACCbouYrQc9RG+eTXnPYosX1Ijqty/I6DdXovwDd89Pwu4jcmpOR7BuFEF9YCcd7/AWwasE0207WMK7hdw==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", @@ -2722,6 +3356,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -2782,12 +3425,61 @@ } } }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "license": "Apache-2.0" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/web/package.json b/web/package.json index b7e4ed6..3f88f33 100644 --- a/web/package.json +++ b/web/package.json @@ -11,10 +11,18 @@ }, "dependencies": { "@ant-design/icons": "^5.5.1", + "@dagrejs/dagre": "^3.0.0", + "@xyflow/react": "^12.11.2", "antd": "^5.21.6", + "bpmn-elk-layout": "^1.4.0", + "bpmn-js": "^18.22.0", + "cytoscape": "^3.34.0", + "cytoscape-fcose": "^2.2.0", "dayjs": "^1.11.13", + "js-yaml": "^4.3.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "yaml": "^2.9.0" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/web/src/App.tsx b/web/src/App.tsx index 99f54f0..5975334 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,15 +1,35 @@ -import { useEffect, useState } from 'react' -import { Layout, Card, Tabs, Button, Space, Typography, Tag, message, Spin, Modal, Select } from 'antd' +import { useEffect, useState, lazy, Suspense } from 'react' +import { Layout, Card, Tabs, Button, Space, Typography, Tag, message, Spin, Modal, Select, Menu } from 'antd' import { ReloadOutlined, ThunderboltOutlined, BranchesOutlined, UserOutlined } from '@ant-design/icons' import { api } from './api' import type { SceneSchema } from './types' import { SceneForm } from './engine/SceneForm' import { TracePanel, ProvenancePanel, ModelViewer, OrdersEventsPanel } from './panels' +// 重量级视图(模型编辑器 + 三张可视化图,含 cytoscape/xyflow/bpmn-js)按需懒加载, +// 让默认「场景运行台」的首屏包保持轻量。 +const ModelEditor = lazy(() => import('./model/ModelEditor').then((m) => ({ default: m.ModelEditor }))) +const OntologyCanvas = lazy(() => import('./viz/OntologyCanvas').then((m) => ({ default: m.OntologyCanvas }))) + +const LazyFallback = () => ( +
+) + const { Header, Sider, Content } = Layout const { Title, Text, Paragraph } = Typography +type View = 'runner' | 'editor' | 'er' | 'network' | 'process' + +const VIEW_ITEMS = [ + { key: 'runner', label: '场景运行台' }, + { key: 'editor', label: '模型编辑器' }, + { key: 'er', label: '数据结构图' }, + { key: 'network', label: '本体网络图' }, + { key: 'process', label: '场景流程图' }, +] + export default function App() { + const [view, setView] = useState('runner') const [scenes, setScenes] = useState([]) const [sceneId, setSceneId] = useState('') // 初始不硬编码,待场景列表返回后取首个可渲染场景 const [schema, setSchema] = useState(null) @@ -17,7 +37,7 @@ export default function App() { const [result, setResult] = useState(null) const [refreshKey, setRefreshKey] = useState(0) const [formKey, setFormKey] = useState(0) - // 演示态主体(M5 principal):随手切换以观察权限"通过/拒绝"分支;管理员方可热重载/新增客户 + // 演示态主体(M5 principal):随手切换以观察权限"通过/拒绝"分支;管理员方可热重载/保存模型/新增客户 const [principal, setPrincipal] = useState('backend_admin') // 拉取场景列表(首个可渲染场景兜底选中;已有选择则保留,以便热重载后停留在当前场景) @@ -35,12 +55,17 @@ export default function App() { api.sceneSchema(sceneId).then(setSchema).catch((e) => message.error('加载场景失败:' + e.message)).finally(() => setLoading(false)) }, [sceneId, formKey]) + // 模型(含热重载/保存)变化后:重拉场景列表 + 重挂当前表单,让改动即时反映 + const afterModelChange = async () => { + await loadScenes() + setFormKey((k) => k + 1) + } + async function reloadModels() { try { const r = await api.reload(principal) message.success(r.message) - await loadScenes() // 重新拉取场景列表:模板增删 / hasTemplate 变化即时反映到侧栏 - setFormKey((k) => k + 1) // 强制重新拉取当前场景 schema + 重挂载表单 + await afterModelChange() } catch (e: any) { message.error('热重载失败:' + e.message) } } @@ -48,79 +73,99 @@ export default function App() { return ( -
+
- 本体驱动 DDD+EDA · 元数据引擎 Demo - 改 YAML → 页面与行为随之变 -
+ 本体驱动 DDD+EDA · 元数据引擎 + setView(e.key as View)} + items={VIEW_ITEMS} + style={{ flex: 1, minWidth: 0, background: 'transparent', borderBottom: 'none' }} + /> - setC('pattern', e.target.value)} placeholder="如 ^\d{11}$" /> +
+
+ 校验提示 patternMessage + setC('patternMessage', e.target.value)} placeholder="不满足正则时的提示语" /> +
+
+ ) + const keys = Object.keys(cons) + return ( + + {keys.length === 0 && } + {keys.filter((k) => k !== 'patternMessage').map((k) => {k}={String(cons[k])})} + {a.refAggregate && → {a.refRoot}.{a.refIdentifier}} + + + + String(i)} dataSource={attrs} + columns={[ + { + title: '字段名', width: 200, render: (_: any, a: any, j: number) => ( + + {loc.node[idField] === a.name && 🔑} + {a.refAggregate && 🔗} + set([...attrsPath, j, 'name'], e.target.value)} /> + + ), + }, + { + title: '类型', width: 130, render: (_: any, a: any, j: number) => ( + set('ruleName', e.target.value)} /> + + set('topic', e.target.value)} /> + + set('consumers', v)} placeholder="回车添加消费方服务" /> + + + + ) +} + +// ============================================================ +// 源码编辑:逐层编辑 YAML 原文,保存即热重载 +// ============================================================ +function SourceEditor({ principal, onReloaded }: { principal: string; onReloaded?: () => void }) { + const [layers, setLayers] = useState<{ code: string; fileName: string }[]>([]) + const [code, setCode] = useState('M1') + const [text, setText] = useState('') + const [orig, setOrig] = useState('') + const [dir, setDir] = useState('') + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + + useEffect(() => { api.models().then((d) => { setLayers(d.layers); setDir(d.modelsDir) }) }, []) + + const loadLayer = (c: string) => { + setLoading(true) + api.model(c).then((d) => { setText(d.raw ?? ''); setOrig(d.raw ?? '') }).finally(() => setLoading(false)) + } + useEffect(() => { if (code) loadLayer(code) }, [code]) + + const dirty = text !== orig + + const switchLayer = (c: string) => { + if (dirty && !window.confirm('当前层有未保存的修改,切换将丢弃。确定切换?')) return + setCode(c) + } + + async function save() { + setSaving(true) + try { + const r = await api.saveModel(code, text, principal) + message.success(r.message || '已保存') + setOrig(text) + onReloaded?.() + } catch (e: any) { + message.error('保存失败:' + e.message) + } finally { setSaving(false) } + } + + return ( +
+ + setHideOrphans(e.target.checked)} style={{ cursor: 'pointer' }} /> 隐藏孤儿节点 + +
+ + +
滚轮缩放 · 拖拽平移 · 单击节点看关系
+
+
zoom(1.25)}>+
+
zoom(1 / 1.25)}>-
+
+
+ +
+ {tip && ( + <> +
+ {TYPE_CN[tip.type]} + {tip.title} +
+ {tip.lines.map((l: any, i: number) => (
{l}
))} + + )} +
+ + + ); +} diff --git a/web/src/viz/OntologyCanvas.tsx b/web/src/viz/OntologyCanvas.tsx new file mode 100644 index 0000000..91bb65b --- /dev/null +++ b/web/src/viz/OntologyCanvas.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react' +import { Spin, Alert, Button, Typography } from 'antd' +import { ReloadOutlined } from '@ant-design/icons' +import { loadOntology } from '../ontology/build' +import type { Ontology } from '../ontology/types' +import NetworkGraph from './NetworkGraph' +import ERDiagram from './ERDiagram' +import ProcessDiagram from './ProcessDiagram' + +const { Title, Text } = Typography + +const META: Record = { + network: { title: '本体网络图', desc: '对象 + 行为 + 规则 + 事件的关联网络(M1/M2/ME/MetaRule 动态合成)' }, + er: { title: '数据结构图(ER)', desc: '聚合根/子实体的字段结构、外键引用与组合关系(M1 动态合成)' }, + process: { title: '场景流程图(BPMN)', desc: '端到端场景流程(M4 flowSteps → bpmn-elk-layout 自动布局 → bpmn-js 渲染)' }, +} + +/** 可视化画布宿主:加载一次本体 IR,全高渲染选中的视图。 */ +export function OntologyCanvas({ which }: { which: 'network' | 'er' | 'process' }) { + const [onto, setOnto] = useState(null) + const [err, setErr] = useState('') + const [key, setKey] = useState(0) + + useEffect(() => { + setOnto(null); setErr('') + loadOntology().then(setOnto).catch((e) => setErr(String(e?.message || e))) + }, [key]) + + const m = META[which] + return ( +
+
+
+ {m.title} + {m.desc} +
+
+ +
+
+ {err ? ( +
+ ) : !onto ? ( +
+ ) : which === 'network' ? ( + + ) : which === 'er' ? ( + + ) : ( + + )} +
+
+ ) +} diff --git a/web/src/viz/ProcessDiagram.tsx b/web/src/viz/ProcessDiagram.tsx new file mode 100644 index 0000000..21f1de8 --- /dev/null +++ b/web/src/viz/ProcessDiagram.tsx @@ -0,0 +1,216 @@ +// 场景流程图(BPMN)视图 —— 通用渲染引擎,数据全在前端合成,无后端依赖。 +// ProcessModel(M4 flowSteps 投影) → toElkBpmn 出 ELK-BPMN JSON(含泳道语义) +// → bpmn-elk-layout(elkjs) 自动横向布局 → 按类型着色 → bpmn-js NavigatedViewer 渲染。 +// 点击任意节点/连线 → 用 detailMap(按 element.id 索引)弹出详情(完整条件、关联行为、网关分支等)。 +// 从 onto-app 的 BpmnDiagram.jsx 移植而来(JSX→TSX);唯一区别:ELK JSON + 详情表由本地 +// toElkBpmn / detailMap 合成,而非后端 api.getBpmn 拉取。 +import React, { useEffect, useRef, useState } from 'react' +// @ts-ignore bpmn-js 的 NavigatedViewer 附带自身类型声明;此处兜底防止解析差异。 +import NavigatedViewer from 'bpmn-js/lib/NavigatedViewer' +import { BpmnElkLayout } from 'bpmn-elk-layout' +import 'bpmn-js/dist/assets/diagram-js.css' +import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' +import type { Ontology } from '../ontology/types' +import { toElkBpmn, detailMap } from '../ontology/bpmn' + +// 按 BPMN 元素类型上色(bpmn-js 认 bioc/color 两套 DI 着色扩展)。配色对齐本体网络图。 +const NODE_COLOR: Record = { + startEvent: { fill: '#e9f7ee', stroke: '#3f9d63' }, + endEvent: { fill: '#fdecef', stroke: '#d6486a' }, + intermediateCatchEvent: { fill: '#fbe3ef', stroke: '#c2528a' }, + userTask: { fill: '#eef2fd', stroke: '#5b7fe0' }, + task: { fill: '#eef2fd', stroke: '#5b7fe0' }, + exclusiveGateway: { fill: '#fff4e3', stroke: '#e0944a' }, + parallelGateway: { fill: '#fff4e3', stroke: '#e0944a' }, +} + +function colorize(xml: string): string { + const doc = new DOMParser().parseFromString(xml, 'application/xml') + const typeOf: Record = {} // 元素 id -> BPMN 类型(从语义部分收集) + for (const el of Array.from(doc.getElementsByTagName('*'))) { + const id = el.getAttribute && el.getAttribute('id') + if (id && NODE_COLOR[el.localName]) typeOf[id] = el.localName + } + for (const el of Array.from(doc.getElementsByTagName('*'))) { + if (el.localName !== 'BPMNShape') continue + const be = el.getAttribute('bpmnElement') + const c = be ? NODE_COLOR[typeOf[be]] : undefined + if (!c) continue // 泳道/池保持白底 + el.setAttribute('bioc:fill', c.fill) + el.setAttribute('bioc:stroke', c.stroke) + el.setAttribute('color:background-color', c.fill) + el.setAttribute('color:border-color', c.stroke) + } + const defs = doc.documentElement + defs.setAttribute('xmlns:bioc', 'http://bpmn.io/schema/bpmn/biocolor/1.0') + defs.setAttribute('xmlns:color', 'http://www.omg.org/spec/BPMN/non-normative/color/1.0') + return new XMLSerializer().serializeToString(doc) +} + +export default function ProcessDiagram({ ontology }: { ontology: Ontology }) { + const boxRef = useRef(null) + const viewerRef = useRef(null) + const xmlRef = useRef('') + const [status, setStatus] = useState<'loading' | 'ok' | 'error'>('loading') // loading | ok | error + const [err, setErr] = useState('') + const [active, setActive] = useState(0) // 当前显示的场景下标 + const [selected, setSelected] = useState(null) // 点中的图元详情(节点/连线) + + const scenes = ontology.processModels || [] + const empty = scenes.length === 0 + + // 切本体时复位到第一张 + useEffect(() => { setActive(0) }, [ontology]) + + useEffect(() => { + if (empty || !boxRef.current) return + let cancelled = false + const viewer: any = new NavigatedViewer({ container: boxRef.current }) + viewerRef.current = viewer + setSelected(null) + ;(async () => { + try { + setStatus('loading') + const pm = scenes[active] + if (!pm) return + const elk = toElkBpmn(pm) // ProcessModel → ELK-BPMN JSON(前端合成,替代后端) + const details = detailMap(pm, ontology) // bpmn 元素 id → 点击详情(前端合成) + const laid = await new BpmnElkLayout().to_bpmn(elk) // elkjs 布局 → 带坐标+泳道的原生横向 BPMN XML + const xml = colorize(laid) // 只按类型着色(标签由布局库摆放) + if (cancelled) return + xmlRef.current = xml + await viewer.importXML(xml) + if (cancelled) return + viewer.get('canvas').zoom('fit-viewport', 'auto') // 'auto' = 缩放后在视口内居中(否则窄图会贴左) + // 点击节点/连线 → 查详情;点空白/泳道(无 details)则关闭面板 + viewer.get('eventBus').on('element.click', (ev: any) => { + const d = details[ev.element?.id] + setSelected(d ? { ...d, _id: ev.element.id } : null) + }) + setStatus('ok') + } catch (e: any) { + if (!cancelled) { setErr(String(e?.message || e)); setStatus('error') } + } + })() + return () => { cancelled = true; viewer.destroy() } + }, [ontology, active, empty]) + + const zoom = (f: number) => { const c = viewerRef.current?.get('canvas'); if (c) c.zoom(c.zoom() * f) } + const fit = () => { const c = viewerRef.current?.get('canvas'); if (c) c.zoom('fit-viewport', 'auto') } + const download = () => { + if (!xmlRef.current) return + const blob = new Blob([xmlRef.current], { type: 'application/xml' }) + const a = document.createElement('a') + a.href = URL.createObjectURL(blob) + a.download = (scenes[active]?.name || 'process') + '.bpmn' + a.click() + URL.revokeObjectURL(a.href) + } + + const btn: React.CSSProperties = { width: 30, height: 30, borderRadius: 8, border: '1px solid #e6e6e6', background: '#fff', color: '#52525b', fontSize: 16, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' } + + // ---- 详情面板 ---- + const KIND_LABEL: Record = { startEvent: '开始事件', endEvent: '结束事件', task: '任务', userTask: '任务', exclusiveGateway: '排他网关(分支判断)', parallelGateway: '并行网关', intermediateCatchEvent: '中间事件', sequenceFlow: '连线 / 分支' } + const dRow = (k: string, v: any, mono?: boolean) => ( +
+
{k}
+
{(v ?? '') !== '' ? v : '—'}
+
+ ) + const dList = (k: string, arr?: string[]) => (arr && arr.length > 0 ? ( +
+
{k}
+ {arr.map((x, i) =>
{x}
)} +
+ ) : null) + const renderDetailBody = (sel: any) => { + if (sel.kind === 'sequenceFlow') { + return (<> + {dRow('流向', `${sel.from} → ${sel.to}`)} + {sel.name ? dRow('分支标签', sel.name) : null} + {dRow('完整条件 (condition)', sel.condition, true)} + {!sel.condition ?
这是一条无条件连线。
: null} + ) + } + if (sel.kind === 'exclusiveGateway' || sel.kind === 'parallelGateway') { + return ( +
+
分支({(sel.branches || []).length})
+ {(sel.branches || []).map((b: any, i: number) => ( +
+
{b.label || '(无标签)'} → {b.to}
+ {b.condition ?
{b.condition}
: null} +
+ ))} +
+ ) + } + const b = sel.behavior + if (b) { + return (<> + {dRow('关联行为', `${b.name}(${b.id})`)} + {dRow('行为类型', b.type === 'COMMAND' ? 'COMMAND(改状态)' : b.type)} + {b.commandType ? dRow('命令类型 (commandType)', b.commandType) : null} + {dRow('归属对象', b.owner)} + {b.description ? dRow('说明', b.description) : null} + {dList('前置条件', b.preconditions)} + {dList('后置赋值', b.effects)} + {dList('产出事件', b.produced_events)} + {dList('适用规则', b.applied_rules)} + ) + } + return
{sel.kind === 'startEvent' ? '流程开始。' : sel.kind === 'endEvent' ? '流程结束。' : '(无更多详情)'}
+ } + + if (empty) { + return ( +
暂无可视化的场景流程
+ ) + } + + return ( +
+
+ + {scenes.length > 1 && ( +
+ {scenes.map((pm, i) => ( +
setActive(i)} title={pm.name} + style={{ padding: '6px 12px', fontSize: 12.5, borderRadius: 8, cursor: 'pointer', border: '1px solid ' + (i === active ? '#18181b' : '#e6e6e6'), background: i === active ? '#18181b' : '#fff', color: i === active ? '#fff' : '#52525b', fontWeight: i === active ? 600 : 500, whiteSpace: 'nowrap', boxShadow: '0 1px 3px rgba(0,0,0,.06)' }}> + {pm.name} +
+ ))} +
+ )} + + {status === 'loading' && ( +
正在生成 BPMN 流程图…
+ )} + {status === 'error' && ( +
流程图渲染失败:{err}
+ )} + +
+
zoom(1.2)}>+
+
zoom(1 / 1.2)}>-
+
+
+
+ + {selected && ( +
+
+
+
{selected.name || selected.kindLabel || KIND_LABEL[selected.kind] || selected.kind}
+
{(selected.kindLabel || KIND_LABEL[selected.kind] || selected.kind)}{selected.lane ? ' · ' + selected.lane : ''}
+
+
setSelected(null)} title="关闭" style={{ cursor: 'pointer', color: '#a0a0a6', fontSize: 18, lineHeight: 1, flex: 'none' }}>✕
+
+ {renderDetailBody(selected)} +
+ )} + +
滚轮缩放 · 拖拽平移 · 点击节点/连线看详情 · 由 bpmn-js 渲染
+
+ ) +} diff --git a/web/vite.config.ts b/web/vite.config.ts index 0206bb1..d42419e 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ port: 5174, proxy: { '/api': { - target: 'http://localhost:8091', + target: 'http://localhost:8080', changeOrigin: true, }, }, -- libgit2 0.22.2