commit 2c2dd8d0d262d5e410c8c1c88472f251d735c5a4 Author: 0264408 Date: Fri Sep 18 18:19:10 2026 +0800 feat: bootstrap Ordo process designer with PARALLEL swimlanes Co-authored-by: Cursor diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd68f14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1e393e --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# ordo-designer-demo + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +pnpm install +``` + +### Compile and Hot-Reload for Development + +```sh +pnpm dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +pnpm build +``` diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..bf4dffd --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,33 @@ +# Ordo Designer Roadmap + +## P0 能用 + +- 分支边错开(同 from 多出边不再叠线) +- 加载后分层自动排布,分支横向错开 +- 节点卡片展示候选人 / policy / action / due +- 校验错误挂到节点和边上,不只 toast 第一条 +- 草稿 vs 已发布:可丢弃本地、对比后再打开 +- 切定义不清空 Pinia,避免闪旧图 +- 脏标记 + 离开未保存提示 + +## P1 好用 + +- 撤销 / 重做 +- 框选、多选删除、复制粘贴 +- due.after 用时长控件,禁止手填乱 ISO +- 改步骤 id:失焦提交 + 冲突提示 +- 发布结果区分「新版本」和「图未变」 +- catalog 失败时的空态与可手输 +- 空画布引导;结束节点唯一性提示 +- 历史版本只读或「另存为草稿」 +- 删除确认(有出边时) + +## P2 打磨 + +- 左右 Handle、回边、网格吸附、对齐 +- @vue-flow/minimap、快捷键说明 +- 引擎 JSON 预览 / 导出 +- 列表里同一 id 的草稿与已发布关系 +- 统一 loading / 网络错误 +- 属性面板 n-form 校验态 +- 自动保存草稿 diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..edd03c5 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Ordo Designer + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ef0bb48 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "ordo-designer-demo", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "@lucide/vue": "^1.46.0", + "@tailwindcss/vite": "^4.3.3", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "naive-ui": "^2.45.3", + "pinia": "^4.0.2", + "tailwindcss": "^4.3.3", + "vue": "^3.5.40", + "vue-router": "^5.2.0" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-vue-devtools": "^8.1.5", + "vue-tsc": "^3.3.7" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..5e314d5 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2625 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@lucide/vue': + specifier: ^1.46.0 + version: 1.46.0(vue@3.5.42(typescript@6.0.3)) + '@tailwindcss/vite': + specifier: ^4.3.3 + version: 4.3.3(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + '@vue-flow/background': + specifier: ^1.3.2 + version: 1.3.2(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3)) + '@vue-flow/controls': + specifier: ^1.1.3 + version: 1.1.3(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3)) + '@vue-flow/core': + specifier: ^1.48.2 + version: 1.48.2(vue@3.5.42(typescript@6.0.3)) + '@vue-flow/minimap': + specifier: ^1.5.4 + version: 1.5.4(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3)) + naive-ui: + specifier: ^2.45.3 + version: 2.45.3(vue@3.5.42(typescript@6.0.3)) + pinia: + specifier: ^4.0.2 + version: 4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)) + tailwindcss: + specifier: ^4.3.3 + version: 4.3.3 + vue: + specifier: ^3.5.40 + version: 3.5.42(typescript@6.0.3) + vue-router: + specifier: ^5.2.0 + version: 5.3.1(@vue/compiler-sfc@3.5.42)(pinia@4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)))(rolldown@1.2.8)(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3)) + devDependencies: + '@tsconfig/node24': + specifier: ^24.0.4 + version: 24.0.5 + '@types/node': + specifier: ^24.13.3 + version: 24.13.5 + '@vitejs/plugin-vue': + specifier: ^6.0.8 + version: 6.0.9(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3)) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)) + npm-run-all2: + specifier: ^9.0.2 + version: 9.0.3 + typescript: + specifier: ~6.0.0 + version: 6.0.3 + vite: + specifier: ^8.1.5 + version: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + vite-plugin-vue-devtools: + specifier: ^8.1.5 + version: 8.2.1(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3)) + vue-tsc: + specifier: ^3.3.7 + version: 3.3.11(typescript@6.0.3) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.29.7': + resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.29.7': + resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@css-render/plugin-bem@0.15.14': + resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==} + peerDependencies: + css-render: ~0.15.14 + + '@css-render/vue3-ssr@0.15.14': + resolution: {integrity: sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==} + peerDependencies: + vue: ^3.0.11 + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@juggle/resize-observer@3.4.0': + resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + + '@lucide/vue@1.46.0': + resolution: {integrity: sha512-xp4mUamXKM/jYCve8TOcEcuikidFh3Jn9gs0THCeEKn/fAdeU0mKhPqk0fmIwn42fI4CwGLRWrLneLA69h8Nqw==} + peerDependencies: + vue: '>=3.0.1' + + '@oxc-project/types@0.149.0': + resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/binding-android-arm-eabi@1.2.8': + resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.8': + resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.8': + resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.8': + resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.8': + resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.8': + resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.8': + resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.8': + resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.8': + resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.8': + resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tsconfig/node24@24.0.5': + resolution: {integrity: sha512-DWC54fGPuhZgg3Ighg4Y/A/bA3ZdB5hnzAuv/MkQ/X85sVsBsMQxu8t7khutdYeiNksBbFdDyeqv1HSW2Zl8aA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} + + '@types/node@24.13.5': + resolution: {integrity: sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@vitejs/plugin-vue@6.0.9': + resolution: {integrity: sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue-flow/background@1.3.2': + resolution: {integrity: sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==} + peerDependencies: + '@vue-flow/core': ^1.23.0 + vue: ^3.3.0 + + '@vue-flow/controls@1.1.3': + resolution: {integrity: sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==} + peerDependencies: + '@vue-flow/core': ^1.23.0 + vue: ^3.3.0 + + '@vue-flow/core@1.48.2': + resolution: {integrity: sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==} + peerDependencies: + vue: ^3.3.0 + + '@vue-flow/minimap@1.5.4': + resolution: {integrity: sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==} + peerDependencies: + '@vue-flow/core': ^1.23.0 + vue: ^3.3.0 + + '@vue-macros/common@3.1.4': + resolution: {integrity: sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==} + engines: {node: '>=20.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.42': + resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} + + '@vue/compiler-dom@3.5.42': + resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} + + '@vue/compiler-sfc@3.5.42': + resolution: {integrity: sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==} + + '@vue/compiler-ssr@3.5.42': + resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==} + + '@vue/devtools-api@8.2.1': + resolution: {integrity: sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==} + + '@vue/devtools-core@8.2.1': + resolution: {integrity: sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA==} + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@8.2.1': + resolution: {integrity: sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==} + + '@vue/devtools-shared@8.2.1': + resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} + + '@vue/language-core@3.3.11': + resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==} + + '@vue/reactivity@3.5.42': + resolution: {integrity: sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==} + + '@vue/runtime-core@3.5.42': + resolution: {integrity: sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==} + + '@vue/runtime-dom@3.5.42': + resolution: {integrity: sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==} + + '@vue/server-renderer@3.5.42': + resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==} + + '@vue/shared@3.5.42': + resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} + + '@vue/tsconfig@0.9.1': + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@10.11.1': + resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} + + '@vueuse/metadata@10.11.1': + resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} + + '@vueuse/shared@10.11.1': + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + ansi-styles@7.0.0: + resolution: {integrity: sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew==} + engines: {node: '>=22'} + + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-walker-scope@0.9.0: + resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} + engines: {node: '>=20.19.0'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + baseline-browser-mapping@2.11.24: + resolution: {integrity: sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==} + engines: {node: '>=6.0.0'} + hasBin: true + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + birpc@4.2.0: + resolution: {integrity: sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q==} + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-render@0.15.14: + resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==} + + csstype@3.0.11: + resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + date-fns-tz@3.2.0: + resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==} + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.430: + resolution: {integrity: sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==} + + enhanced-resolve@5.25.1: + resolution: {integrity: sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==} + engines: {node: '>=10.13.0'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + evtd@0.2.4: + resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} + + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + highlight.js@11.12.0: + resolution: {integrity: sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==} + engines: {node: '>=12.0.0'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@6.0.0: + resolution: {integrity: sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + naive-ui@2.45.3: + resolution: {integrity: sha512-D83gJ+6zUdkQ5DuUbmlfyaM1t8pV5WMPa6KmL9mByqQuiL12qBdA0n/iEIth4dQw1x/duNGpOS2anpt5FKO1kw==} + engines: {node: '>=20'} + peerDependencies: + vue: ^3.0.0 + + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.55: + resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==} + engines: {node: '>=18'} + + nostics@1.2.0: + resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} + + npm-normalize-package-bin@6.0.0: + resolution: {integrity: sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + npm-run-all2@9.0.3: + resolution: {integrity: sha512-BQAEdU1PtYc48qYRdghW2BVTQT3VqWCoFQmO87NlM1h1PYwMCKQpUWaNyB20V26caNzFsXQDfyOdznLlHCih6g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0, npm: '>= 10'} + hasBin: true + + obug@2.2.1: + resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} + engines: {node: '>=12.20.0'} + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} + engines: {node: '>=20'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pidtree@1.0.0: + resolution: {integrity: sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==} + engines: {node: '>=18'} + hasBin: true + + pinia@4.0.3: + resolution: {integrity: sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==} + peerDependencies: + '@vue/devtools-api': ^8.1.5 + typescript: '>=5.6.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + powershell-utils@0.2.1: + resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} + engines: {node: '>=20'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + read-package-json-fast@6.0.0: + resolution: {integrity: sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} + + rolldown@1.2.8: + resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + seemly@0.3.10: + resolution: {integrity: sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + treemate@0.3.11: + resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unplugin-utils@0.3.2: + resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} + engines: {node: '>=20.19.0'} + + unplugin@3.3.0: + resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true + + update-browserslist-db@1.3.3: + resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vdirs@0.1.8: + resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==} + peerDependencies: + vue: ^3.0.11 + + vite-dev-rpc@2.0.0: + resolution: {integrity: sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0 + + vite-hot-client@2.2.0: + resolution: {integrity: sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0 + + vite-plugin-inspect@11.4.1: + resolution: {integrity: sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 || ^8.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@8.2.1: + resolution: {integrity: sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA==} + engines: {node: '>=v14.21.3'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: '*' + + vite-plugin-vue-inspector@6.0.0: + resolution: {integrity: sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + vite@8.3.0: + resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vooks@0.2.12: + resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==} + peerDependencies: + vue: ^3.0.0 + + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@5.3.1: + resolution: {integrity: sha512-GDBZzgmILxA/kFnkFbJjQZdZ2QQbngnIMMuoUcjhZIfH1RGMaPjPwX5ASnV38qamuA9uhO0RDjSBHTDNG2uXyQ==} + peerDependencies: + '@pinia/colada': '>=0.21.2' + '@vue/compiler-sfc': ^3.5.34 || ^4.0.0 + pinia: ^3.0.4 || ^4.0.2 + vite: ^7.3.0 || ^8.0.0 + vue: ^3.5.34 || ^4.0.0 + peerDependenciesMeta: + '@pinia/colada': + optional: true + '@vue/compiler-sfc': + optional: true + pinia: + optional: true + vite: + optional: true + + vue-tsc@3.3.11: + resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.42: + resolution: {integrity: sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vueuc@0.4.66: + resolution: {integrity: sha512-hHrZl+0AsrUvqYPBXSXQ2QaSrO3razjeUseLAhJWlOS/Hdfw/Y2MU05fOTP1qn1A3121gMxsHLaEz2V6CkWdTg==} + peerDependencies: + vue: ^3.0.11 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@7.0.0: + resolution: {integrity: sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + hasBin: true + + wsl-utils@1.0.0: + resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} + engines: {node: '>=20'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@css-render/plugin-bem@0.15.14(css-render@0.15.14)': + dependencies: + css-render: 0.15.14 + + '@css-render/vue3-ssr@0.15.14(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue: 3.5.42(typescript@6.0.3) + + '@emotion/hash@0.8.0': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@juggle/resize-observer@3.4.0': {} + + '@lucide/vue@1.46.0(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue: 3.5.42(typescript@6.0.3) + + '@oxc-project/types@0.149.0': {} + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/binding-android-arm-eabi@1.2.8': + optional: true + + '@rolldown/binding-android-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.8': + optional: true + + '@rolldown/binding-darwin-x64@1.2.8': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.8': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.8': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.8': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.8': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.8': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.8': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.8': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.25.1 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/vite@4.3.3(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + + '@tsconfig/node24@24.0.5': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.25 + + '@types/lodash@4.17.25': {} + + '@types/node@24.13.5': + dependencies: + undici-types: 7.18.2 + + '@types/web-bluetooth@0.0.20': {} + + '@vitejs/plugin-vue@6.0.9(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + vue: 3.5.42(typescript@6.0.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28(typescript@6.0.3)': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + optionalDependencies: + typescript: 6.0.3 + + '@vue-flow/background@1.3.2(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vue-flow/core': 1.48.2(vue@3.5.42(typescript@6.0.3)) + vue: 3.5.42(typescript@6.0.3) + + '@vue-flow/controls@1.1.3(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vue-flow/core': 1.48.2(vue@3.5.42(typescript@6.0.3)) + vue: 3.5.42(typescript@6.0.3) + + '@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vueuse/core': 10.11.1(vue@3.5.42(typescript@6.0.3)) + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + vue: 3.5.42(typescript@6.0.3) + transitivePeerDependencies: + - '@vue/composition-api' + + '@vue-flow/minimap@1.5.4(@vue-flow/core@1.48.2(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vue-flow/core': 1.48.2(vue@3.5.42(typescript@6.0.3)) + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + vue: 3.5.42(typescript@6.0.3) + + '@vue-macros/common@3.1.4(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vue/compiler-sfc': 3.5.42 + ast-kit: 2.2.0 + local-pkg: 1.2.1 + magic-string-ast: 1.0.3 + unplugin-utils: 0.3.2 + optionalDependencies: + vue: 3.5.42(typescript@6.0.3) + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.7) + '@vue/shared': 3.5.42 + optionalDependencies: + '@babel/core': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.7)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/parser': 7.29.8 + '@vue/compiler-sfc': 3.5.42 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.42 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.42': + dependencies: + '@vue/compiler-core': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/compiler-sfc@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.42 + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-ssr': 3.5.42 + '@vue/shared': 3.5.42 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.28 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.42': + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/devtools-api@8.2.1': + dependencies: + '@vue/devtools-kit': 8.2.1 + + '@vue/devtools-core@8.2.1(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@vue/devtools-kit': 8.2.1 + '@vue/devtools-shared': 8.2.1 + vue: 3.5.42(typescript@6.0.3) + + '@vue/devtools-kit@8.2.1': + dependencies: + '@vue/devtools-shared': 8.2.1 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@8.2.1': {} + + '@vue/language-core@3.3.11': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.7 + + '@vue/reactivity@3.5.42': + dependencies: + '@vue/shared': 3.5.42 + + '@vue/runtime-core@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/runtime-dom@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/runtime-core': 3.5.42 + '@vue/shared': 3.5.42 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.42': + dependencies: + '@vue/compiler-ssr': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/shared@3.5.42': {} + + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3))': + optionalDependencies: + typescript: 6.0.3 + vue: 3.5.42(typescript@6.0.3) + + '@vueuse/core@10.11.1(vue@3.5.42(typescript@6.0.3))': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.11.1 + '@vueuse/shared': 10.11.1(vue@3.5.42(typescript@6.0.3)) + vue-demi: 0.14.10(vue@3.5.42(typescript@6.0.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@10.11.1': {} + + '@vueuse/shared@10.11.1(vue@3.5.42(typescript@6.0.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.42(typescript@6.0.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + acorn@8.18.0: {} + + alien-signals@3.2.1: {} + + ansi-styles@7.0.0: {} + + ansis@4.3.1: {} + + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.29.8 + pathe: 2.0.3 + + ast-walker-scope@0.9.0: + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + ast-kit: 2.2.0 + + async-validator@4.2.5: {} + + baseline-browser-mapping@2.11.24: {} + + birpc@2.9.0: {} + + birpc@4.2.0: {} + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.24 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.430 + node-releases: 2.0.55 + update-browserslist-db: 1.3.3(browserslist@4.28.8) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + caniuse-lite@1.0.30001810: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-render@0.15.14: + dependencies: + '@emotion/hash': 0.8.0 + csstype: 3.0.11 + + csstype@3.0.11: {} + + csstype@3.2.3: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + date-fns-tz@3.2.0(date-fns@4.4.0): + dependencies: + date-fns: 4.4.0 + + date-fns@4.4.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + default-browser-id@5.0.1: {} + + default-browser@5.5.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.430: {} + + enhanced-resolve@5.25.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@7.0.1: {} + + error-stack-parser-es@1.0.5: {} + + escalade@3.2.0: {} + + estree-walker@2.0.2: {} + + evtd@0.2.4: {} + + exsolve@1.1.1: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + graceful-fs@4.2.11: {} + + highlight.js@11.12.0: {} + + hookable@5.5.3: {} + + is-docker@3.0.0: {} + + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + isexe@4.0.0: {} + + jiti@2.7.0: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-parse-even-better-errors@6.0.0: {} + + json5@2.2.3: {} + + kolorist@1.8.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + + lodash-es@4.18.1: {} + + lodash@4.18.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + memorystream@0.3.1: {} + + mlly@1.8.2: + dependencies: + acorn: 8.18.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + naive-ui@2.45.3(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@css-render/plugin-bem': 0.15.14(css-render@0.15.14) + '@css-render/vue3-ssr': 0.15.14(vue@3.5.42(typescript@6.0.3)) + '@types/lodash': 4.17.25 + '@types/lodash-es': 4.17.12 + async-validator: 4.2.5 + css-render: 0.15.14 + csstype: 3.2.3 + date-fns: 4.4.0 + date-fns-tz: 3.2.0(date-fns@4.4.0) + evtd: 0.2.4 + highlight.js: 11.12.0 + lodash: 4.18.1 + lodash-es: 4.18.1 + seemly: 0.3.10 + treemate: 0.3.11 + vdirs: 0.1.8(vue@3.5.42(typescript@6.0.3)) + vooks: 0.2.12(vue@3.5.42(typescript@6.0.3)) + vue: 3.5.42(typescript@6.0.3) + vueuc: 0.4.66(vue@3.5.42(typescript@6.0.3)) + + nanoid@3.3.19: {} + + node-releases@2.0.55: {} + + nostics@1.2.0: {} + + npm-normalize-package-bin@6.0.0: {} + + npm-run-all2@9.0.3: + dependencies: + ansi-styles: 7.0.0 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + picomatch: 4.0.7 + pidtree: 1.0.0 + read-package-json-fast: 6.0.0 + shell-quote: 1.10.0 + which: 7.0.0 + + obug@2.2.1: {} + + ohash@2.0.12: {} + + open@11.0.4: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.2.1 + wsl-utils: 1.0.0 + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pidtree@1.0.0: {} + + pinia@4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@vue/devtools-api': 8.2.1 + nostics: 1.2.0 + vue: 3.5.42(typescript@6.0.3) + optionalDependencies: + typescript: 6.0.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.1.1 + pathe: 2.0.3 + + postcss@8.5.28: + dependencies: + nanoid: 3.3.19 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + powershell-utils@0.1.0: {} + + powershell-utils@0.2.1: {} + + quansync@0.2.11: {} + + read-package-json-fast@6.0.0: + dependencies: + json-parse-even-better-errors: 6.0.0 + npm-normalize-package-bin: 6.0.0 + + readdirp@5.1.1: {} + + rolldown@1.2.8: + dependencies: + '@oxc-project/types': 0.149.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.8 + '@rolldown/binding-android-arm64': 1.2.8 + '@rolldown/binding-darwin-arm64': 1.2.8 + '@rolldown/binding-darwin-x64': 1.2.8 + '@rolldown/binding-freebsd-x64': 1.2.8 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 + '@rolldown/binding-linux-arm64-gnu': 1.2.8 + '@rolldown/binding-linux-arm64-musl': 1.2.8 + '@rolldown/binding-linux-ppc64-gnu': 1.2.8 + '@rolldown/binding-linux-s390x-gnu': 1.2.8 + '@rolldown/binding-linux-x64-gnu': 1.2.8 + '@rolldown/binding-linux-x64-musl': 1.2.8 + '@rolldown/binding-openharmony-arm64': 1.2.8 + '@rolldown/binding-win32-arm64-msvc': 1.2.8 + '@rolldown/binding-win32-x64-msvc': 1.2.8 + + run-applescript@7.1.0: {} + + scule@1.3.0: {} + + seemly@0.3.10: {} + + semver@6.3.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.10.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + totalist@3.0.1: {} + + treemate@0.3.11: {} + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + undici-types@7.18.2: {} + + unplugin-utils@0.3.2: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.7 + + unplugin@3.3.0(rolldown@1.2.8)(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)): + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.7 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + rolldown: 1.2.8 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + + update-browserslist-db@1.3.3(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + vdirs@0.1.8(vue@3.5.42(typescript@6.0.3)): + dependencies: + evtd: 0.2.4 + vue: 3.5.42(typescript@6.0.3) + + vite-dev-rpc@2.0.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)): + dependencies: + birpc: 4.2.0 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + vite-hot-client: 2.2.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + + vite-hot-client@2.2.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)): + dependencies: + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + + vite-plugin-inspect@11.4.1(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)): + dependencies: + ansis: 4.3.1 + error-stack-parser-es: 1.0.5 + obug: 2.2.1 + ohash: 2.0.12 + open: 11.0.4 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.2 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + vite-dev-rpc: 2.0.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + + vite-plugin-vue-devtools@8.2.1(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@vue/devtools-core': 8.2.1(vue@3.5.42(typescript@6.0.3)) + '@vue/devtools-kit': 8.2.1 + '@vue/devtools-shared': 8.2.1 + sirv: 3.0.2 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + vite-plugin-inspect: 11.4.1(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + vite-plugin-vue-inspector: 6.0.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + vue: 3.5.42(typescript@6.0.3) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + + vite-plugin-vue-inspector@6.0.0(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.7) + '@vue/compiler-dom': 3.5.42 + kolorist: 1.8.0 + magic-string: 0.30.21 + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + transitivePeerDependencies: + - supports-color + + vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.8 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.5 + fsevents: 2.3.3 + jiti: 2.7.0 + + vooks@0.2.12(vue@3.5.42(typescript@6.0.3)): + dependencies: + evtd: 0.2.4 + vue: 3.5.42(typescript@6.0.3) + + vscode-uri@3.2.0: {} + + vue-demi@0.14.10(vue@3.5.42(typescript@6.0.3)): + dependencies: + vue: 3.5.42(typescript@6.0.3) + + vue-router@5.3.1(@vue/compiler-sfc@3.5.42)(pinia@4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)))(rolldown@1.2.8)(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0))(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@vue-macros/common': 3.1.4(vue@3.5.42(typescript@6.0.3)) + '@vue/devtools-api': 8.2.1 + ast-walker-scope: 0.9.0 + chokidar: 5.0.0 + confbox: 0.2.4 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + muggle-string: 0.4.1 + nostics: 1.2.0 + pathe: 2.0.3 + picomatch: 4.0.7 + scule: 1.3.0 + tinyglobby: 0.2.17 + unplugin: 3.3.0(rolldown@1.2.8)(vite@8.3.0(@types/node@24.13.5)(jiti@2.7.0)) + unplugin-utils: 0.3.2 + vue: 3.5.42(typescript@6.0.3) + optionalDependencies: + '@vue/compiler-sfc': 3.5.42 + pinia: 4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.42(typescript@6.0.3)) + vite: 8.3.0(@types/node@24.13.5)(jiti@2.7.0) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rolldown + - rollup + - unloader + - webpack + + vue-tsc@3.3.11(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28(typescript@6.0.3) + '@vue/language-core': 3.3.11 + typescript: 6.0.3 + + vue@3.5.42(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-sfc': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/server-renderer': 3.5.42 + '@vue/shared': 3.5.42 + optionalDependencies: + typescript: 6.0.3 + + vueuc@0.4.66(vue@3.5.42(typescript@6.0.3)): + dependencies: + '@css-render/vue3-ssr': 0.15.14(vue@3.5.42(typescript@6.0.3)) + '@juggle/resize-observer': 3.4.0 + css-render: 0.15.14 + evtd: 0.2.4 + seemly: 0.3.10 + vdirs: 0.1.8(vue@3.5.42(typescript@6.0.3)) + vooks: 0.2.12(vue@3.5.42(typescript@6.0.3)) + vue: 3.5.42(typescript@6.0.3) + + webpack-virtual-modules@0.6.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@7.0.0: + dependencies: + isexe: 4.0.0 + + wsl-utils@1.0.0: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + + yallist@3.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..6923ab1 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + vue-demi: false diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..c4f6e4a --- /dev/null +++ b/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/api/ordo.ts b/src/api/ordo.ts new file mode 100644 index 0000000..1c7c99d --- /dev/null +++ b/src/api/ordo.ts @@ -0,0 +1,75 @@ +import type { CatalogItem, PageResponse, ProcessDefinitionDocument } from '@/types/definition' + +async function request(url: string, init?: RequestInit): Promise { + const res = await fetch(url, { + ...init, + headers: { + 'Content-Type': 'application/json', + ...(init?.headers ?? {}), + }, + }) + if (!res.ok) { + let message = res.statusText + try { + const body = (await res.json()) as { message?: string } + if (body.message) { + message = body.message + } + } catch { + /* ignore */ + } + throw new Error(message) + } + if (res.status === 204) { + return undefined as T + } + return (await res.json()) as T +} + +export function parseDefinition(doc: ProcessDefinitionDocument) { + return request('/ordo/definitions/parse', { + method: 'POST', + body: JSON.stringify(doc), + }) +} + +export function publishDefinition(doc: ProcessDefinitionDocument) { + return request('/ordo/definitions', { + method: 'POST', + body: JSON.stringify(doc), + }) +} + +export function listDefinitions(page = 0, size = 50) { + return request>( + `/ordo/definitions?page=${page}&size=${size}`, + ) +} + +export function getDefinition(id: string) { + return request(`/ordo/definitions/${encodeURIComponent(id)}`) +} + +export function listDefinitionVersions(id: string, page = 0, size = 50) { + return request>( + `/ordo/definitions/${encodeURIComponent(id)}/versions?page=${page}&size=${size}`, + ) +} + +export function getDefinitionVersion(id: string, version: number) { + return request( + `/ordo/definitions/${encodeURIComponent(id)}/versions/${version}`, + ) +} + +export function listConditions() { + return request('/ordo/catalog/conditions') +} + +export function listActions() { + return request('/ordo/catalog/actions') +} + +export function listAssignees() { + return request('/ordo/catalog/assignees') +} diff --git a/src/components/designer/ConditionWhenEditor.vue b/src/components/designer/ConditionWhenEditor.vue new file mode 100644 index 0000000..2e2ae8d --- /dev/null +++ b/src/components/designer/ConditionWhenEditor.vue @@ -0,0 +1,235 @@ + + + diff --git a/src/components/designer/DraftConflictModal.vue b/src/components/designer/DraftConflictModal.vue new file mode 100644 index 0000000..46d9372 --- /dev/null +++ b/src/components/designer/DraftConflictModal.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/components/designer/DurationAfter.vue b/src/components/designer/DurationAfter.vue new file mode 100644 index 0000000..156f5b3 --- /dev/null +++ b/src/components/designer/DurationAfter.vue @@ -0,0 +1,44 @@ + + + diff --git a/src/components/designer/GraphErrorList.vue b/src/components/designer/GraphErrorList.vue new file mode 100644 index 0000000..deb3d1e --- /dev/null +++ b/src/components/designer/GraphErrorList.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/components/designer/NodePalette.vue b/src/components/designer/NodePalette.vue new file mode 100644 index 0000000..d084bf8 --- /dev/null +++ b/src/components/designer/NodePalette.vue @@ -0,0 +1,82 @@ + + + diff --git a/src/components/designer/PredicateBuilder.vue b/src/components/designer/PredicateBuilder.vue new file mode 100644 index 0000000..d706aaa --- /dev/null +++ b/src/components/designer/PredicateBuilder.vue @@ -0,0 +1,175 @@ + + + diff --git a/src/components/designer/PropertyPanel.vue b/src/components/designer/PropertyPanel.vue new file mode 100644 index 0000000..c178ceb --- /dev/null +++ b/src/components/designer/PropertyPanel.vue @@ -0,0 +1,388 @@ + + + diff --git a/src/components/designer/edges/BranchEdge.vue b/src/components/designer/edges/BranchEdge.vue new file mode 100644 index 0000000..5a584e2 --- /dev/null +++ b/src/components/designer/edges/BranchEdge.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/components/designer/nodes/ActionNode.vue b/src/components/designer/nodes/ActionNode.vue new file mode 100644 index 0000000..e5b63bc --- /dev/null +++ b/src/components/designer/nodes/ActionNode.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/components/designer/nodes/ApprovalNode.vue b/src/components/designer/nodes/ApprovalNode.vue new file mode 100644 index 0000000..bc0bdd9 --- /dev/null +++ b/src/components/designer/nodes/ApprovalNode.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/components/designer/nodes/EndNode.vue b/src/components/designer/nodes/EndNode.vue new file mode 100644 index 0000000..2072976 --- /dev/null +++ b/src/components/designer/nodes/EndNode.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/components/designer/nodes/NodeHandles.vue b/src/components/designer/nodes/NodeHandles.vue new file mode 100644 index 0000000..42ad33e --- /dev/null +++ b/src/components/designer/nodes/NodeHandles.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/components/designer/nodes/ParallelNode.vue b/src/components/designer/nodes/ParallelNode.vue new file mode 100644 index 0000000..4124700 --- /dev/null +++ b/src/components/designer/nodes/ParallelNode.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/composables/useAsync.ts b/src/composables/useAsync.ts new file mode 100644 index 0000000..6145e1c --- /dev/null +++ b/src/composables/useAsync.ts @@ -0,0 +1,21 @@ +import { ref } from 'vue' + +export function useAsync() { + const loading = ref(false) + const error = ref('') + + async function run(fn: () => Promise): Promise { + loading.value = true + error.value = '' + try { + return await fn() + } catch (e) { + error.value = e instanceof Error ? e.message : 'Request failed' + throw e + } finally { + loading.value = false + } + } + + return { loading, error, run } +} diff --git a/src/composables/useCatalogLabels.ts b/src/composables/useCatalogLabels.ts new file mode 100644 index 0000000..ba0fc8c --- /dev/null +++ b/src/composables/useCatalogLabels.ts @@ -0,0 +1,31 @@ +import { useCatalogStore } from '@/stores/catalog' +import type { CatalogItem } from '@/types/definition' + +function labelOf(items: CatalogItem[], key: string) { + return items.find((i) => i.key === key)?.label ?? key +} + +export function useCatalogLabels() { + const catalog = useCatalogStore() + + function assignee(key: string) { + return labelOf(catalog.assignees, key) + } + + function action(key: string) { + return labelOf(catalog.actions, key) + } + + function assignees(keys: string[]) { + if (keys.length === 0) { + return 'Unset' + } + const labels = keys.map(assignee) + if (labels.length <= 2) { + return labels.join('、') + } + return `${labels.slice(0, 2).join('、')} +${labels.length - 2}` + } + + return { assignee, action, assignees } +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a2b16ef --- /dev/null +++ b/src/main.ts @@ -0,0 +1,19 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import naive from 'naive-ui' +import '@vue-flow/core/dist/style.css' +import '@vue-flow/core/dist/theme-default.css' +import '@vue-flow/controls/dist/style.css' +import '@vue-flow/minimap/dist/style.css' +import './styles/main.css' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) +app.use(naive) + +app.mount('#app') diff --git a/src/mapper/duration.ts b/src/mapper/duration.ts new file mode 100644 index 0000000..84522fe --- /dev/null +++ b/src/mapper/duration.ts @@ -0,0 +1,29 @@ +export type DurationUnit = 'minutes' | 'hours' | 'days' + +export function parseIsoDuration(iso: string | undefined): { value: number; unit: DurationUnit } { + const raw = iso?.trim() ?? '' + const day = /^P(\d+)D$/i.exec(raw) + if (day) { + return { value: Number(day[1]), unit: 'days' } + } + const hour = /^PT(\d+)H$/i.exec(raw) + if (hour) { + return { value: Number(hour[1]), unit: 'hours' } + } + const min = /^PT(\d+)M$/i.exec(raw) + if (min) { + return { value: Number(min[1]), unit: 'minutes' } + } + return { value: 48, unit: 'hours' } +} + +export function toIsoDuration(value: number, unit: DurationUnit): string { + const n = Math.max(1, Math.floor(value) || 1) + if (unit === 'days') { + return `P${n}D` + } + if (unit === 'hours') { + return `PT${n}H` + } + return `PT${n}M` +} diff --git a/src/mapper/edgePath.ts b/src/mapper/edgePath.ts new file mode 100644 index 0000000..d2a0e8b --- /dev/null +++ b/src/mapper/edgePath.ts @@ -0,0 +1,227 @@ +export type HandleSide = 'top' | 'bottom' | 'left' | 'right' + +export interface ObstacleRect { + x: number + y: number + width: number + height: number +} + +export interface OrthogonalPathParams { + sourceX: number + sourceY: number + targetX: number + targetY: number + sourceSide?: HandleSide + targetSide?: HandleSide + /** Stagger when several edges share a corridor. */ + corridorOffset?: number + obstacles?: ObstacleRect[] +} + +export type OrthogonalPathResult = { + path: string + labelX: number + labelY: number +} + +export function sideFromHandleId(handleId?: string | null): HandleSide | undefined { + if (!handleId) { + return undefined + } + if (handleId.endsWith('-top') || handleId === 'top') { + return 'top' + } + if (handleId.endsWith('-bottom') || handleId === 'bottom') { + return 'bottom' + } + if (handleId.endsWith('-left') || handleId === 'left') { + return 'left' + } + if (handleId.endsWith('-right') || handleId === 'right') { + return 'right' + } + return undefined +} + +function isHorizontal(side: HandleSide) { + return side === 'left' || side === 'right' +} + +const NODE_CLEAR = 56 + +function hitsVertical(x: number, y0: number, y1: number, rects: ObstacleRect[], margin = 0) { + const top = Math.min(y0, y1) + 3 + const bot = Math.max(y0, y1) - 3 + if (bot <= top) { + return false + } + return rects.some( + (r) => + x > r.x - margin && + x < r.x + r.width + margin && + bot > r.y && + top < r.y + r.height, + ) +} + +function hitsHorizontal(y: number, x0: number, x1: number, rects: ObstacleRect[], margin = 0) { + const left = Math.min(x0, x1) + 3 + const right = Math.max(x0, x1) - 3 + if (right <= left) { + return false + } + return rects.some( + (r) => + y > r.y - margin && + y < r.y + r.height + margin && + right > r.x && + left < r.x + r.width, + ) +} + +function routeBottomTopAround( + sx: number, + sy: number, + tx: number, + ty: number, + corridor: number, + obstacles: ObstacleRect[], +): OrthogonalPathResult { + const preferRight = tx >= sx + const blocked = obstacles.filter( + (r) => hitsVertical(sx, sy, ty, [r]) || hitsVertical(tx, sy, ty, [r]), + ) + const basis = blocked.length ? blocked : obstacles + let lane = preferRight + ? Math.max(sx, ...basis.map((r) => r.x + r.width)) + NODE_CLEAR + : Math.min(sx, ...basis.map((r) => r.x)) - NODE_CLEAR + lane += preferRight ? corridor : -corridor + const y0 = Math.min(sy, ty) + const y1Span = Math.max(sy, ty) + for (let i = 0; i < 16 && hitsVertical(lane, y0, y1Span, obstacles, NODE_CLEAR); i += 1) { + lane += preferRight ? 18 : -18 + } + + const below = basis.map((r) => r.y).filter((y) => y > sy + 4) + const firstTop = below.length ? Math.min(...below) : sy + 64 + const yTurn = Math.max(sy + 14, Math.min(sy + 32, firstTop - 16)) + let yEnter = ty - 28 + if (hitsHorizontal(yEnter, lane, tx, obstacles)) { + yEnter = ty - 14 + } + if (yEnter < yTurn + 16) { + yEnter = yTurn + 16 + } + + return { + path: `M ${sx},${sy} L ${sx},${yTurn} L ${lane},${yTurn} L ${lane},${yEnter} L ${tx},${yEnter} L ${tx},${ty}`, + labelX: (sx + lane) / 2, + labelY: yTurn, + } +} + +export function getOrthogonalPath(params: OrthogonalPathParams): OrthogonalPathResult { + const sx = params.sourceX + const sy = params.sourceY + const tx = params.targetX + const ty = params.targetY + const corridor = params.corridorOffset ?? 0 + const sourceSide = params.sourceSide ?? (ty + 8 < sy ? 'left' : 'bottom') + const targetSide = params.targetSide ?? (ty + 8 < sy ? 'left' : 'top') + + if (Math.abs(sx - tx) < 2 && Math.abs(sy - ty) < 2) { + return { path: `M ${sx},${sy}`, labelX: sx, labelY: sy } + } + + // Side → side (back-edge / loop): out horizontally, vertical, in horizontally. + if (isHorizontal(sourceSide) && isHorizontal(targetSide)) { + const pad = 48 + Math.abs(corridor) + const outLeft = sourceSide === 'left' + const inLeft = targetSide === 'left' + let hx1 = outLeft ? sx - pad : sx + pad + let hx2 = inLeft ? tx - pad : tx + pad + if (outLeft === inLeft) { + const shared = outLeft ? Math.min(sx, tx) - pad : Math.max(sx, tx) + pad + hx1 = shared + corridor + hx2 = hx1 + return { + path: `M ${sx},${sy} L ${hx1},${sy} L ${hx1},${ty} L ${tx},${ty}`, + labelX: hx1, + labelY: (sy + ty) / 2, + } + } + hx1 += corridor + hx2 += corridor + const midY = (sy + ty) / 2 + return { + path: `M ${sx},${sy} L ${hx1},${sy} L ${hx1},${midY} L ${hx2},${midY} L ${hx2},${ty} L ${tx},${ty}`, + labelX: (hx1 + hx2) / 2, + labelY: midY, + } + } + + // Bottom → Top (default downward flow). + if (sourceSide === 'bottom' && targetSide === 'top') { + const obstacles = params.obstacles ?? [] + if (Math.abs(sx - tx) < 2) { + if (!hitsVertical(sx, sy, ty, obstacles)) { + return { + path: `M ${sx},${sy} L ${tx},${ty}`, + labelX: (sx + tx) / 2, + labelY: (sy + ty) / 2, + } + } + return routeBottomTopAround(sx, sy, tx, ty, corridor, obstacles) + } + const dy = ty - sy + const minDrop = 36 + let midY = sy + Math.min(Math.max(dy / 2, minDrop), Math.max(minDrop, dy - minDrop)) + midY += corridor + const lo = sy + 20 + const hi = ty - 20 + if (hi > lo) { + midY = Math.min(hi, Math.max(lo, midY)) + } + const blocked = + hitsVertical(sx, sy, midY, obstacles) || + hitsHorizontal(midY, sx, tx, obstacles) || + hitsVertical(tx, midY, ty, obstacles) + if (blocked) { + return routeBottomTopAround(sx, sy, tx, ty, corridor, obstacles) + } + return { + path: `M ${sx},${sy} L ${sx},${midY} L ${tx},${midY} L ${tx},${ty}`, + labelX: (sx + tx) / 2, + labelY: midY, + } + } + + // Bottom → side + if (sourceSide === 'bottom' && isHorizontal(targetSide)) { + const midY = sy + 36 + corridor + return { + path: `M ${sx},${sy} L ${sx},${midY} L ${tx},${midY} L ${tx},${ty}`, + labelX: (sx + tx) / 2, + labelY: midY, + } + } + + // Side → top + if (isHorizontal(sourceSide) && targetSide === 'top') { + const midY = ty - 36 - Math.abs(corridor) + return { + path: `M ${sx},${sy} L ${sx},${midY} L ${tx},${midY} L ${tx},${ty}`, + labelX: (sx + tx) / 2, + labelY: midY, + } + } + + // Fallback: simple elbow + const midY = (sy + ty) / 2 + corridor + return { + path: `M ${sx},${sy} L ${sx},${midY} L ${tx},${midY} L ${tx},${ty}`, + labelX: (sx + tx) / 2, + labelY: midY, + } +} diff --git a/src/mapper/graph.ts b/src/mapper/graph.ts new file mode 100644 index 0000000..66ecebd --- /dev/null +++ b/src/mapper/graph.ts @@ -0,0 +1,292 @@ +import type { + ActionNodeData, + ApprovalNodeData, + DesignerEdgeData, + DesignerNodeData, + ProcessDefinitionDocument, + StepDocument, +} from '@/types/definition' +import { defaultActionData, defaultApprovalData, defaultEndData } from '@/types/definition' +import { formatWhen } from '@/mapper/when' +import { approvalStepDocument, branchesOf, laneOf, materializeParallel } from '@/mapper/parallel' + +export const APPROVAL_TYPE = 'approval' +export const ACTION_TYPE = 'action' +export const PARALLEL_TYPE = 'parallel' +export const END_TYPE = 'end' +export const END_NODE_ID = '__end__' + +export interface DesignerNode { + id: string + type: string + position: { x: number; y: number } + data: DesignerNodeData + parentNode?: string + extent?: 'parent' + style?: { width: string; height: string } + zIndex?: number + sourceHandle?: string + targetHandle?: string +} + +export interface DesignerEdge { + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + label?: string + data: DesignerEdgeData +} + +export function edgeLabel(data: DesignerEdgeData | undefined): string { + const when = formatWhen(data?.when) + return when || 'default' +} + +export function toDocument( + id: string, + name: string, + startStepId: string, + nodes: DesignerNode[], + edges: DesignerEdge[], +): ProcessDefinitionDocument { + const top = nodes.filter((node) => node.type !== END_TYPE && !node.parentNode) + const start = top.find((node) => node.id === startStepId) ?? top[0] + const rest = top.filter((node) => node.id !== start?.id) + const ordered = start ? [start, ...rest] : rest + + const steps: StepDocument[] = ordered.map((node) => { + const data = node.data + if (data?.kind === 'ACTION') { + return { id: node.id, name: data.name, kind: 'ACTION', action: data.action } + } + if (data?.kind === 'PARALLEL') { + return { + id: node.id, + name: data.name, + kind: 'PARALLEL', + branches: branchesOf(node.id, data.lanes, nodes, edges), + } + } + return approvalStepDocument(data as ApprovalNodeData, node.id) + }) + + const transitions = edges + .filter((edge) => { + const source = nodes.find((node) => node.id === edge.source) + return source && !source.parentNode + }) + .map((edge) => { + const target = nodes.find((node) => node.id === edge.target) + const data = edge.data ?? { priority: 0 } + return { + from: edge.source, + to: target?.type === END_TYPE || target?.parentNode ? null : edge.target, + ...(data.when ? { when: data.when } : {}), + priority: data.priority ?? 0, + } + }) + + return { + id, + name, + startStep: start?.id, + steps, + transitions, + } +} + +export function fromDocument( + doc: ProcessDefinitionDocument, + layout: Record = {}, +): { nodes: DesignerNode[]; edges: DesignerEdge[]; startStepId: string } { + const nodes: DesignerNode[] = [] + const innerEdges: DesignerEdge[] = [] + doc.steps.forEach((step, index) => { + const position = layout[step.id] ?? { x: 220, y: 40 + index * 140 } + if (step.kind === 'PARALLEL') { + const built = materializeParallel(step.id, step.name, position, step.branches ?? []) + nodes.push(built.parent, ...built.children) + innerEdges.push(...built.edges) + return + } + if (step.kind === 'ACTION') { + const data: ActionNodeData = { + ...defaultActionData(), + name: step.name, + action: step.action ?? '', + isStart: false, + } + nodes.push({ id: step.id, type: ACTION_TYPE, position, data }) + return + } + const data: ApprovalNodeData = { + ...defaultApprovalData(), + name: step.name, + candidates: step.candidates ?? [], + policy: step.policy ?? 'ANY', + dueEnabled: Boolean(step.due), + due: step.due + ? { + after: step.due.after, + then: step.due.then, + to: step.due.to ?? undefined, + action: step.due.action ?? undefined, + } + : { after: 'PT48H', then: 'notify' }, + isStart: false, + } + nodes.push({ id: step.id, type: APPROVAL_TYPE, position, data }) + }) + + const hasEnd = doc.transitions.some((t) => t.to == null) + if (hasEnd) { + nodes.push({ + id: END_NODE_ID, + type: END_TYPE, + position: layout[END_NODE_ID] ?? { x: 220, y: 40 + doc.steps.length * 140 }, + data: defaultEndData(), + }) + } + + const startStepId = doc.startStep ?? doc.steps[0]?.id ?? '' + for (const node of nodes) { + if (node.data && node.data.kind !== 'END') { + node.data.isStart = node.id === startStepId + } + } + + const edges: DesignerEdge[] = [ + ...innerEdges, + ...doc.transitions.map((t, i) => { + const data: DesignerEdgeData = { + when: t.when, + priority: t.priority ?? 0, + } + return { + id: `e-${i}-${t.from}-${t.to ?? 'end'}`, + source: t.from, + target: t.to ?? END_NODE_ID, + label: edgeLabel(data), + data, + } + }), + ] + + return { nodes, edges, startStepId } +} + +export function layoutFromNodes(nodes: DesignerNode[]): Record { + const layout: Record = {} + for (const node of nodes) { + layout[node.id] = { x: node.position.x, y: node.position.y } + } + return layout +} + +export interface GraphError { + message: string + nodeId?: string + edgeId?: string +} + +export function comparableDocument(doc: ProcessDefinitionDocument): string { + return JSON.stringify( + { + id: doc.id, + name: doc.name, + startStep: doc.startStep, + steps: doc.steps, + transitions: doc.transitions, + }, + null, + 2, + ) +} + +export function validateGraph( + id: string, + name: string, + startStepId: string, + nodes: DesignerNode[], + edges: DesignerEdge[], +): GraphError[] { + const errors: GraphError[] = [] + if (!id.trim()) { + errors.push({ message: 'Process id is required' }) + } + if (!name.trim()) { + errors.push({ message: 'Process name is required' }) + } + const stepNodes = nodes.filter((n) => n.type !== END_TYPE) + if (stepNodes.length === 0) { + errors.push({ message: 'At least one step is required' }) + } + const ids = new Set() + for (const node of stepNodes) { + if (ids.has(node.id)) { + errors.push({ message: `Duplicate step id: ${node.id}`, nodeId: node.id }) + } + ids.add(node.id) + const data = node.data + if (data?.kind === 'APPROVAL' && data.candidates.length === 0) { + errors.push({ message: `Approval step ${node.id} needs candidates`, nodeId: node.id }) + } + if (data?.kind === 'ACTION' && !data.action.trim()) { + errors.push({ message: `ACTION step ${node.id} needs action`, nodeId: node.id }) + } + if (data?.kind === 'PARALLEL') { + if (data.lanes.length < 2) { + errors.push({ message: `PARALLEL ${node.id} needs at least two branches`, nodeId: node.id }) + } + const seenLanes = new Set() + for (const lane of data.lanes) { + if (!lane.trim() || seenLanes.has(lane)) { + errors.push({ message: `PARALLEL ${node.id} has a bad branch id: ${lane}`, nodeId: node.id }) + } + seenLanes.add(lane) + if (!nodes.some((child) => child.parentNode === node.id && laneOf(child.data) === lane)) { + errors.push({ message: `Branch ${lane} needs a step`, nodeId: node.id }) + } + } + } + const outgoing = edges.filter((e) => e.source === node.id) + if (!node.parentNode && outgoing.length === 0) { + errors.push({ message: `Step ${node.id} has no outgoing edge`, nodeId: node.id }) + } + } + if (startStepId && !ids.has(startStepId)) { + errors.push({ message: `Unknown startStep: ${startStepId}` }) + } + if (nodes.some((node) => node.id === startStepId && node.parentNode)) { + errors.push({ message: 'startStep must not be inside a parallel branch' }) + } + const seenPriority = new Set() + for (const edge of edges) { + const source = nodes.find((n) => n.id === edge.source) + const target = nodes.find((n) => n.id === edge.target) + if (!target) { + errors.push({ message: `Edge target missing: ${edge.target}`, edgeId: edge.id }) + } else if (target.type !== END_TYPE && !ids.has(edge.target)) { + errors.push({ message: `Edge target is not a step: ${edge.target}`, edgeId: edge.id }) + } + if (!source || !ids.has(edge.source)) { + errors.push({ message: `Edge source is not a step: ${edge.source}`, edgeId: edge.id }) + } + if (source?.parentNode || target?.parentNode) { + if (source?.parentNode !== target?.parentNode || laneOf(source?.data) !== laneOf(target?.data)) { + errors.push({ message: `Branch edge must stay in the same branch`, edgeId: edge.id }) + } + } + const key = `${edge.source}:${edge.data?.priority ?? 0}` + if (seenPriority.has(key)) { + errors.push({ + message: `Duplicate outgoing priority: ${edge.source} / ${edge.data?.priority ?? 0}`, + edgeId: edge.id, + }) + } + seenPriority.add(key) + } + return errors +} diff --git a/src/mapper/layout.ts b/src/mapper/layout.ts new file mode 100644 index 0000000..e4c751d --- /dev/null +++ b/src/mapper/layout.ts @@ -0,0 +1,265 @@ +import { END_TYPE, type DesignerEdge, type DesignerNode } from '@/mapper/graph' +import { fitParallelFrames } from '@/mapper/parallel' + +const RANK_Y = 220 +const NODE_GAP_X = 320 +const ORIGIN_X = 80 +const ORIGIN_Y = 40 + +function buildOutgoing(edges: DesignerEdge[]) { + const outgoing = new Map() + for (const edge of edges) { + const list = outgoing.get(edge.source) ?? [] + list.push(edge) + outgoing.set(edge.source, list) + } + for (const list of outgoing.values()) { + list.sort((a, b) => (a.data?.priority ?? 0) - (b.data?.priority ?? 0) || a.target.localeCompare(b.target)) + } + return outgoing +} + +/** Edges that close a cycle (feedback arcs); excluded from rank / column inheritance. */ +function findBackEdgeIds(nodeIds: string[], edges: DesignerEdge[], startId?: string): Set { + const ids = new Set(nodeIds) + const back = new Set() + const visited = new Set() + const stack = new Set() + const outgoing = buildOutgoing(edges) + + function dfs(u: string) { + visited.add(u) + stack.add(u) + for (const edge of outgoing.get(u) ?? []) { + if (!ids.has(edge.target)) { + continue + } + if (stack.has(edge.target)) { + back.add(edge.id) + continue + } + if (!visited.has(edge.target)) { + dfs(edge.target) + } + } + stack.delete(u) + } + + const roots = startId && ids.has(startId) ? [startId] : nodeIds + for (const root of roots) { + if (!visited.has(root)) { + dfs(root) + } + } + for (const id of nodeIds) { + if (!visited.has(id)) { + dfs(id) + } + } + return back +} + +/** Longest-path ranks on the DAG after removing feedback arcs. */ +function computeRanks( + nodeIds: string[], + edges: DesignerEdge[], + backEdgeIds: Set, +): Map { + const rank = new Map() + for (const id of nodeIds) { + rank.set(id, 0) + } + const forward = edges.filter((e) => !backEdgeIds.has(e.id)) + let changed = true + let guard = 0 + while (changed && guard < nodeIds.length + 4) { + changed = false + guard += 1 + for (const edge of forward) { + if (!rank.has(edge.source) || !rank.has(edge.target)) { + continue + } + const next = (rank.get(edge.source) ?? 0) + 1 + if (next > (rank.get(edge.target) ?? 0)) { + rank.set(edge.target, next) + changed = true + } + } + } + return rank +} + +function assignColumns( + nodeIds: string[], + startId: string | undefined, + rank: Map, + outgoing: Map, + backEdgeIds: Set, +): Map { + const col = new Map() + let nextCol = 0 + const ordered = [...nodeIds].sort( + (a, b) => (rank.get(a) ?? 0) - (rank.get(b) ?? 0) || a.localeCompare(b), + ) + + if (startId && rank.has(startId)) { + col.set(startId, 0) + nextCol = 1 + } + + for (const id of ordered) { + if (!col.has(id)) { + col.set(id, nextCol) + nextCol += 1 + } + const parentCol = col.get(id) ?? 0 + let inherited = false + const seen = new Set() + for (const edge of outgoing.get(id) ?? []) { + if (backEdgeIds.has(edge.id) || seen.has(edge.target) || !rank.has(edge.target)) { + continue + } + seen.add(edge.target) + if (col.has(edge.target)) { + continue + } + if (!inherited) { + col.set(edge.target, parentCol) + inherited = true + } else { + col.set(edge.target, nextCol) + nextCol += 1 + } + } + } + + for (const id of nodeIds) { + if (!col.has(id)) { + col.set(id, nextCol) + nextCol += 1 + } + } + return col +} + +/** Map legacy dual side-handle ids onto the single left/right handles. */ +export function normalizeHandleId(handleId?: string | null): string | undefined { + if (!handleId) { + return undefined + } + if (handleId === 'source-left' || handleId === 'target-left' || handleId === 'left') { + return 'left' + } + if (handleId === 'source-right' || handleId === 'target-right' || handleId === 'right') { + return 'right' + } + if (handleId === 'source-bottom' || handleId === 'bottom') { + return 'source-bottom' + } + if (handleId === 'target-top' || handleId === 'top') { + return 'target-top' + } + return handleId +} + +/** Pick handles from node positions only (avoids rank ties marking every edge as a back-edge). */ +export function pickHandlesForEdge( + source: DesignerNode, + target: DesignerNode, +): { sourceHandle: string; targetHandle: string } { + const dy = target.position.y - source.position.y + const dx = target.position.x - source.position.x + const down = 64 + const up = -64 + + if (dy >= down) { + return { sourceHandle: 'source-bottom', targetHandle: 'target-top' } + } + if (dy <= up) { + const side = dx > 0 ? 'right' : 'left' + return { sourceHandle: side, targetHandle: side } + } + // Same row: exit toward the target, enter from the facing side. + if (dx >= 0) { + return { sourceHandle: 'right', targetHandle: 'left' } + } + return { sourceHandle: 'left', targetHandle: 'right' } +} + +/** Recompute every edge's handles from current node positions. */ +export function assignEdgeHandles(nodes: DesignerNode[], edges: DesignerEdge[]): DesignerEdge[] { + const byId = new Map(nodes.map((n) => [n.id, n])) + return edges.map((edge) => { + const source = byId.get(edge.source) + const target = byId.get(edge.target) + if (!source || !target) { + return { + ...edge, + sourceHandle: normalizeHandleId(edge.sourceHandle) ?? 'source-bottom', + targetHandle: normalizeHandleId(edge.targetHandle) ?? 'target-top', + } + } + return { ...edge, ...pickHandlesForEdge(source, target) } + }) +} + +/** @deprecated use assignEdgeHandles — kept name for draft reopen path */ +export function fillMissingEdgeHandles(nodes: DesignerNode[], edges: DesignerEdge[]): DesignerEdge[] { + return assignEdgeHandles(nodes, edges) +} + +export function applyLayeredLayout( + nodes: DesignerNode[], + edges: DesignerEdge[], + startStepId: string, +): { nodes: DesignerNode[]; edges: DesignerEdge[] } { + const parked = nodes.filter((node) => node.parentNode) + if (parked.length > 0) { + const top = nodes.filter((node) => !node.parentNode) + const topIds = new Set(top.map((node) => node.id)) + const topEdges = edges.filter((edge) => topIds.has(edge.source) && topIds.has(edge.target)) + const laid = applyLayeredLayout(top, topEdges, startStepId) + const merged = fitParallelFrames([...laid.nodes, ...parked]) + return { nodes: merged, edges: assignEdgeHandles(merged, edges) } + } + if (nodes.length === 0) { + return { nodes, edges } + } + + const byId = new Map(nodes.map((n) => [n.id, n])) + const nodeIds = nodes.map((n) => n.id) + const outgoing = buildOutgoing(edges) + const start = byId.has(startStepId) + ? startStepId + : (nodes.find((n) => n.type !== END_TYPE)?.id ?? nodes[0]?.id) + + const backEdgeIds = findBackEdgeIds(nodeIds, edges, start) + const rank = computeRanks(nodeIds, edges, backEdgeIds) + if (start && (rank.get(start) ?? 0) > 0) { + const shift = rank.get(start) ?? 0 + for (const id of nodeIds) { + rank.set(id, Math.max(0, (rank.get(id) ?? 0) - shift)) + } + } + + const col = assignColumns(nodeIds, start, rank, outgoing, backEdgeIds) + + const laidOut = nodes.map((node) => { + const r = rank.get(node.id) ?? 0 + const c = col.get(node.id) ?? 0 + return { + ...node, + position: { + x: ORIGIN_X + c * NODE_GAP_X, + y: ORIGIN_Y + r * RANK_Y, + }, + } + }) + + return { + nodes: laidOut, + edges: assignEdgeHandles(laidOut, edges), + } +} + +export { RANK_Y, NODE_GAP_X } diff --git a/src/mapper/parallel.ts b/src/mapper/parallel.ts new file mode 100644 index 0000000..1232bf0 --- /dev/null +++ b/src/mapper/parallel.ts @@ -0,0 +1,327 @@ +import type { + ActionNodeData, + ApprovalNodeData, + BranchDocument, + DesignerNodeData, + ParallelNodeData, + StepDocument, + TransitionDocument, +} from '@/types/definition' +import { defaultActionData, defaultApprovalData } from '@/types/definition' +import { formatWhen } from '@/mapper/when' +import type { RoutingWhen } from '@/mapper/when' + +export interface CanvasNode { + id: string + type: string + position: { x: number; y: number } + data: DesignerNodeData + parentNode?: string + extent?: 'parent' + style?: { width: string; height: string } + zIndex?: number +} + +export interface CanvasEdge { + id: string + source: string + target: string + label?: string + data: { when?: RoutingWhen; priority: number } +} + +export const LANE_HEADER = 44 +export const LANE_H = 128 +export const LANE_LABEL_W = 76 +export const LANE_STEP_W = 220 +export const LANE_STEP_GAP = 28 + +export function laneOf(data: DesignerNodeData | undefined): string | undefined { + if (!data || (data.kind !== 'APPROVAL' && data.kind !== 'ACTION')) { + return undefined + } + return data.laneId +} + +export function parallelSize(laneCount: number, maxSteps: number): { width: number; height: number } { + return { + width: LANE_LABEL_W + 16 + Math.max(1, maxSteps) * (LANE_STEP_W + LANE_STEP_GAP) + 12, + height: LANE_HEADER + Math.max(1, laneCount) * LANE_H + 10, + } +} + +export function laneStepPosition(laneIndex: number, stepIndex: number): { x: number; y: number } { + return { + x: LANE_LABEL_W + 8 + stepIndex * (LANE_STEP_W + LANE_STEP_GAP), + y: LANE_HEADER + laneIndex * LANE_H + 16, + } +} + +export function fitParallelFrames(nodes: CanvasNode[]): CanvasNode[] { + return nodes.map((node) => { + if (node.data?.kind !== 'PARALLEL') { + return node + } + const lanes = node.data.lanes + const children = nodes.filter((child) => child.parentNode === node.id) + const maxSteps = Math.max(1, ...lanes.map((lane) => children.filter((child) => laneOf(child.data) === lane).length)) + const size = parallelSize(lanes.length, maxSteps) + return { + ...node, + zIndex: 0, + style: { width: `${size.width}px`, height: `${size.height}px` }, + } + }) +} + +export function settleLaneNodes(nodes: CanvasNode[]): CanvasNode[] { + const next = nodes.map((node) => { + if (!node.parentNode) { + return node + } + const parent = nodes.find((item) => item.id === node.parentNode) + if (!parent || parent.data?.kind !== 'PARALLEL') { + return node + } + const lanes = parent.data.lanes + const laneIndex = Math.min( + lanes.length - 1, + Math.max(0, Math.floor((node.position.y - LANE_HEADER) / LANE_H)), + ) + const lane = lanes[laneIndex] ?? lanes[0] + if (!lane) { + return node + } + const data = node.data + if (data.kind !== 'APPROVAL' && data.kind !== 'ACTION') { + return node + } + return { + ...node, + zIndex: 1, + data: { ...data, laneId: lane }, + position: { + x: Math.max(LANE_LABEL_W, node.position.x), + y: LANE_HEADER + laneIndex * LANE_H + 16, + }, + } + }) + return fitParallelFrames(next) +} + +export function approvalStepDocument(data: ApprovalNodeData, id: string): StepDocument { + const step: StepDocument = { + id, + name: data.name, + kind: 'APPROVAL', + candidates: data.candidates, + policy: data.policy, + } + if (data.dueEnabled && data.due.after && data.due.then) { + step.due = { + after: data.due.after, + then: data.due.then, + ...(data.due.to ? { to: data.due.to } : {}), + ...(data.due.action ? { action: data.due.action } : {}), + } + } + return step +} + +export function branchesOf( + parallelId: string, + lanes: string[], + nodes: CanvasNode[], + edges: CanvasEdge[], +): BranchDocument[] { + return lanes.map((lane) => { + const steps = nodes + .filter((node) => node.parentNode === parallelId && laneOf(node.data) === lane) + .sort((a, b) => a.position.x - b.position.x || a.id.localeCompare(b.id)) + const ids = new Set(steps.map((step) => step.id)) + const transitions: TransitionDocument[] = edges + .filter((edge) => ids.has(edge.source) && ids.has(edge.target)) + .map((edge) => ({ + from: edge.source, + to: edge.target, + ...(edge.data.when ? { when: edge.data.when } : {}), + priority: edge.data.priority ?? 0, + })) + for (const step of steps) { + if (!edges.some((edge) => edge.source === step.id && ids.has(edge.target))) { + transitions.push({ from: step.id, to: null, priority: 0 }) + } + } + return { + id: lane, + steps: steps.map((node) => { + if (node.data.kind === 'ACTION') { + return { id: node.id, name: node.data.name, kind: 'ACTION' as const, action: node.data.action } + } + return approvalStepDocument(node.data as ApprovalNodeData, node.id) + }), + transitions, + } + }) +} + +export function spawnLaneStep( + parallelId: string, + laneId: string, + laneIndex: number, + stepIndex: number, + id: string, +): CanvasNode { + const data: ApprovalNodeData = { ...defaultApprovalData(), name: 'Approval', laneId } + return { + id, + type: 'approval', + parentNode: parallelId, + extent: 'parent', + zIndex: 1, + position: laneStepPosition(laneIndex, stepIndex), + data, + } +} + +export function materializeParallel( + id: string, + name: string, + position: { x: number; y: number }, + branches: BranchDocument[], + isStart = false, +): { parent: CanvasNode; children: CanvasNode[]; edges: CanvasEdge[] } { + const lanes = branches.map((branch) => branch.id) + const data: ParallelNodeData = { kind: 'PARALLEL', name, lanes, isStart } + const maxSteps = Math.max(1, ...branches.map((branch) => branch.steps.length)) + const size = parallelSize(lanes.length, maxSteps) + const parent: CanvasNode = { + id, + type: 'parallel', + position, + zIndex: 0, + style: { width: `${size.width}px`, height: `${size.height}px` }, + data, + } + const children: CanvasNode[] = [] + const edges: CanvasEdge[] = [] + branches.forEach((branch, laneIndex) => { + branch.steps.forEach((step, stepIndex) => { + children.push(stepNode(step, id, branch.id, laneIndex, stepIndex)) + }) + branch.transitions.forEach((transition, index) => { + if (!transition.to) { + return + } + const data = { when: transition.when, priority: transition.priority ?? 0 } + edges.push({ + id: `e-${id}-${branch.id}-${index}`, + source: transition.from, + target: transition.to, + label: formatWhen(transition.when) || 'default', + data, + }) + }) + }) + return { parent, children, edges } +} + +function stepNode( + step: StepDocument, + parallelId: string, + laneId: string, + laneIndex: number, + stepIndex: number, +): CanvasNode { + if (step.kind === 'ACTION') { + const data: ActionNodeData = { ...defaultActionData(), name: step.name, action: step.action ?? '', laneId } + return { + id: step.id, + type: 'action', + parentNode: parallelId, + extent: 'parent', + zIndex: 1, + position: laneStepPosition(laneIndex, stepIndex), + data, + } + } + const data: ApprovalNodeData = { + ...defaultApprovalData(), + name: step.name, + candidates: step.candidates ?? [], + policy: step.policy ?? 'ANY', + dueEnabled: Boolean(step.due), + due: step.due + ? { after: step.due.after, then: step.due.then, to: step.due.to ?? undefined, action: step.due.action ?? undefined } + : { after: 'PT48H', then: 'notify' }, + laneId, + } + return { + id: step.id, + type: 'approval', + parentNode: parallelId, + extent: 'parent', + zIndex: 1, + position: laneStepPosition(laneIndex, stepIndex), + data, + } +} + +export function dropCrossLaneEdges(nodes: CanvasNode[], edges: CanvasEdge[]): CanvasEdge[] { + const byId = new Map(nodes.map((node) => [node.id, node])) + return edges.filter((edge) => { + const source = byId.get(edge.source) + const target = byId.get(edge.target) + if (!source?.parentNode && !target?.parentNode) { + return true + } + if (!source?.parentNode || !target?.parentNode) { + return false + } + return source.parentNode === target.parentNode && laneOf(source.data) === laneOf(target.data) + }) +} + +export function reflowLanes(nodes: CanvasNode[], parallelId: string): CanvasNode[] { + const parent = nodes.find((node) => node.id === parallelId) + if (!parent || parent.data.kind !== 'PARALLEL') { + return nodes + } + const placed = new Map() + parent.data.lanes.forEach((lane, laneIndex) => { + const steps = nodes + .filter((node) => node.parentNode === parallelId && laneOf(node.data) === lane) + .sort((a, b) => a.position.x - b.position.x || a.id.localeCompare(b.id)) + steps.forEach((step, stepIndex) => { + const data = step.data + if (data.kind !== 'APPROVAL' && data.kind !== 'ACTION') { + return + } + placed.set(step.id, { + ...step, + position: laneStepPosition(laneIndex, stepIndex), + data: { ...data, laneId: lane }, + }) + }) + }) + return fitParallelFrames(nodes.map((node) => placed.get(node.id) ?? node)) +} + +export function expandLegacyParallel( + nodes: CanvasNode[], + edges: CanvasEdge[], +): { nodes: CanvasNode[]; edges: CanvasEdge[] } { + const next: CanvasNode[] = [] + const extra: CanvasEdge[] = [] + for (const node of nodes) { + const data = node.data as ParallelNodeData & { branches?: BranchDocument[] } + if (data?.kind === 'PARALLEL' && data.branches?.length && !data.lanes) { + const built = materializeParallel(node.id, data.name, node.position, data.branches, Boolean(data.isStart)) + next.push(built.parent, ...built.children) + extra.push(...built.edges) + continue + } + next.push(node) + } + return { nodes: next, edges: [...extra, ...edges] } +} diff --git a/src/mapper/when.ts b/src/mapper/when.ts new file mode 100644 index 0000000..ae66401 --- /dev/null +++ b/src/mapper/when.ts @@ -0,0 +1,216 @@ +export type CompareOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' + +export type RoutingWhen = + | { ref: string; args?: Record } + | { and: RoutingWhen[] } + | { or: RoutingWhen[] } + | { not: RoutingWhen } + | { eq: [string, unknown] } + | { ne: [string, unknown] } + | { gt: [string, unknown] } + | { gte: [string, unknown] } + | { lt: [string, unknown] } + | { lte: [string, unknown] } + | { in: [string, unknown[]] } + +const COMPARE_OPS: CompareOp[] = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in'] + +export function isRefWhen(when: RoutingWhen | undefined): when is { ref: string; args?: Record } { + return Boolean(when && typeof when === 'object' && 'ref' in when) +} + +export function formatWhen(when: RoutingWhen | undefined): string { + if (!when) { + return '' + } + if (isRefWhen(when)) { + const args = when.args + if (!args || Object.keys(args).length === 0) { + return when.ref + } + const body = Object.entries(args) + .map(([k, v]) => `${k}=${String(v)}`) + .join(',') + return `${when.ref}(${body})` + } + if ('and' in when) { + return when.and.map(formatWhen).join(' AND ') + } + if ('or' in when) { + return when.or.map((part) => `(${formatWhen(part)})`).join(' OR ') + } + if ('not' in when) { + return `NOT (${formatWhen(when.not)})` + } + if ('in' in when) { + return `${when.in[0]} in [${when.in[1].map(String).join(', ')}]` + } + for (const op of COMPARE_OPS) { + if (op === 'in') { + continue + } + if (op in when) { + const pair = (when as Record)[op] + const symbol = + op === 'eq' ? '=' : op === 'ne' ? '!=' : op === 'gt' ? '>' : op === 'gte' ? '>=' : op === 'lt' ? '<' : '<=' + return `${pair?.[0]} ${symbol} ${String(pair?.[1])}` + } + } + return '' +} + +export function defaultLeaf(): RoutingWhen { + return { gt: ['', 0] } +} + +export const MAX_PREDICATE_DEPTH = 8 +export const MAX_PREDICATE_LEAVES = 32 + +export function isLogicGroup( + when: RoutingWhen, +): when is { and: RoutingWhen[] } | { or: RoutingWhen[] } { + return 'and' in when || 'or' in when +} + +export function isNotWhen(when: RoutingWhen): when is { not: RoutingWhen } { + return 'not' in when +} + +export function groupChildren(when: RoutingWhen): RoutingWhen[] { + if ('and' in when) { + return when.and + } + if ('or' in when) { + return when.or + } + return [] +} + +export function leafOp(when: RoutingWhen): CompareOp { + for (const op of COMPARE_OPS) { + if (op in when) { + return op + } + } + return 'gt' +} + +export function leafVar(when: RoutingWhen): string { + const pair = (when as Record)[leafOp(when)] + return pair?.[0] ?? '' +} + +export function leafValueText(when: RoutingWhen): string { + const op = leafOp(when) + const pair = (when as Record)[op] + const value = pair?.[1] + if (op === 'in' && Array.isArray(value)) { + return value.map(String).join(', ') + } + return value == null ? '' : String(value) +} + +export function makeLeaf(op: CompareOp, variable: string, text: string): RoutingWhen { + if (op === 'in') { + const values = text + .split(',') + .map((part) => part.trim()) + .filter(Boolean) + .map(coerceScalar) + return { in: [variable, values] } + } + return { [op]: [variable, coerceScalar(text)] } as RoutingWhen +} + +function coerceScalar(text: string): unknown { + if (text === 'true') { + return true + } + if (text === 'false') { + return false + } + if (text !== '' && !Number.isNaN(Number(text))) { + return Number(text) + } + return text +} + +export function countLeaves(when: RoutingWhen | undefined): number { + if (!when || isRefWhen(when)) { + return 0 + } + if (isLogicGroup(when)) { + return groupChildren(when).reduce((sum, child) => sum + countLeaves(child), 0) + } + if (isNotWhen(when)) { + return countLeaves(when.not) + } + return 1 +} + +export function predicateDepth(when: RoutingWhen | undefined): number { + if (!when || isRefWhen(when)) { + return 0 + } + if (isLogicGroup(when)) { + return 1 + groupChildren(when).reduce((max, child) => Math.max(max, predicateDepth(child)), 0) + } + if (isNotWhen(when)) { + return 1 + predicateDepth(when.not) + } + return 1 +} + +export function validatePredicate(when: RoutingWhen | undefined): string | null { + if (!when || isRefWhen(when)) { + return 'Add at least one condition' + } + if (predicateDepth(when) > MAX_PREDICATE_DEPTH) { + return `Nesting cannot exceed ${MAX_PREDICATE_DEPTH} levels` + } + if (countLeaves(when) > MAX_PREDICATE_LEAVES) { + return `At most ${MAX_PREDICATE_LEAVES} conditions` + } + return validateNode(when) +} + +function validateNode(when: RoutingWhen): string | null { + if (isLogicGroup(when)) { + const children = groupChildren(when) + if (children.length === 0) { + return 'A group needs at least one condition' + } + for (const child of children) { + const error = validateNode(child) + if (error) { + return error + } + } + return null + } + if (isNotWhen(when)) { + return validateNode(when.not) + } + if (!leafVar(when).trim()) { + return 'Variable is required' + } + if (leafOp(when) === 'in' && leafValueText(when).trim() === '') { + return 'in requires at least one value, comma-separated' + } + return null +} + +/** Single-child AND/OR is equivalent to its child; keep published JSON flat. */ +export function collapseTrivialGroups(when: RoutingWhen): RoutingWhen { + if (isLogicGroup(when)) { + const children = groupChildren(when).map(collapseTrivialGroups) + if (children.length === 1) { + return children[0] ?? defaultLeaf() + } + return 'and' in when ? { and: children } : { or: children } + } + if (isNotWhen(when)) { + return { not: collapseTrivialGroups(when.not) } + } + return when +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..a5f4ed2 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,13 @@ +import { createRouter, createWebHistory } from 'vue-router' +import DefinitionListView from '@/views/DefinitionListView.vue' +import DesignerView from '@/views/DesignerView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { path: '/', name: 'definitions', component: DefinitionListView }, + { path: '/designer/:definitionId?', name: 'designer', component: DesignerView }, + ], +}) + +export default router diff --git a/src/stores/catalog.ts b/src/stores/catalog.ts new file mode 100644 index 0000000..5aeb4ab --- /dev/null +++ b/src/stores/catalog.ts @@ -0,0 +1,35 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import * as ordo from '@/api/ordo' +import type { CatalogItem } from '@/types/definition' + +export const useCatalogStore = defineStore('catalog', () => { + const conditions = ref([]) + const actions = ref([]) + const assignees = ref([]) + const loaded = ref(false) + const error = ref(null) + + async function load() { + error.value = null + try { + const [c, a, s] = await Promise.all([ + ordo.listConditions(), + ordo.listActions(), + ordo.listAssignees(), + ]) + conditions.value = c + actions.value = a + assignees.value = s + loaded.value = true + } catch (e) { + conditions.value = [] + actions.value = [] + assignees.value = [] + loaded.value = false + error.value = e instanceof Error ? e.message : 'Catalog unavailable' + } + } + + return { conditions, actions, assignees, loaded, error, load } +}) diff --git a/src/stores/designer.ts b/src/stores/designer.ts new file mode 100644 index 0000000..c8b0927 --- /dev/null +++ b/src/stores/designer.ts @@ -0,0 +1,795 @@ +import { computed, ref } from 'vue' +import { defineStore } from 'pinia' +import type { Connection } from '@vue-flow/core' +import { + ACTION_TYPE, + APPROVAL_TYPE, + END_TYPE, + PARALLEL_TYPE, + END_NODE_ID, + edgeLabel, + fromDocument, + layoutFromNodes, + toDocument, + type DesignerEdge, + type DesignerNode, + validateGraph, +} from '@/mapper/graph' +import { dropCrossLaneEdges, expandLegacyParallel, fitParallelFrames, laneOf, materializeParallel, reflowLanes, settleLaneNodes, spawnLaneStep } from '@/mapper/parallel' +import { applyLayeredLayout, assignEdgeHandles, normalizeHandleId } from '@/mapper/layout' +import { defaultActionData, defaultApprovalData, defaultEndData } from '@/types/definition' +import type { DesignerEdgeData, DesignerNodeData, ProcessDefinitionDocument } from '@/types/definition' + +let seq = 0 +function nextId(prefix: string) { + seq += 1 + return `${prefix}-${Date.now().toString(36)}-${seq}` +} + +interface GraphSnapshot { + definitionId: string + name: string + startStepId: string + nodes: DesignerNode[] + edges: DesignerEdge[] +} + +interface ClipboardPayload { + nodes: DesignerNode[] + edges: DesignerEdge[] +} + +function slimNode(node: DesignerNode): DesignerNode { + return { + id: node.id, + type: node.type, + position: { x: node.position.x, y: node.position.y }, + parentNode: node.parentNode, + extent: node.extent, + style: node.style ? { ...node.style } : undefined, + zIndex: node.zIndex, + data: JSON.parse(JSON.stringify(node.data)) as DesignerNodeData, + } +} + +function slimEdge(edge: DesignerEdge): DesignerEdge { + return { + id: edge.id, + source: edge.source, + target: edge.target, + sourceHandle: edge.sourceHandle, + targetHandle: edge.targetHandle, + label: edge.label, + data: { ...(edge.data ?? { priority: 0 }) }, + } +} + +export const useDesignerStore = defineStore('designer', () => { + const definitionId = ref('') + const name = ref('') + const startStepId = ref('') + const publishedVersion = ref(null) + const loadedFromDraft = ref(false) + const nodes = ref([]) + const edges = ref([]) + const selectedNodeId = ref(null) + const selectedEdgeId = ref(null) + const selectedNodeIds = ref([]) + const selectedEdgeIds = ref([]) + const baseline = ref('') + const undoStack = ref([]) + const redoStack = ref([]) + let historyPaused = false + let clipboard: ClipboardPayload | null = null + + const selectedNode = computed(() => + nodes.value.find((n) => n.id === selectedNodeId.value), + ) + const selectedEdge = computed(() => + edges.value.find((e) => e.id === selectedEdgeId.value), + ) + const graphErrors = computed(() => + validateGraph(definitionId.value, name.value, startStepId.value, nodes.value, edges.value), + ) + const canUndo = computed(() => undoStack.value.length > 0) + const canRedo = computed(() => redoStack.value.length > 0) + const canPaste = computed(() => Boolean(clipboard?.nodes.length)) + + function snapshot() { + return JSON.stringify({ + id: definitionId.value, + name: name.value, + startStepId: startStepId.value, + doc: toDocument(definitionId.value, name.value, startStepId.value, nodes.value, edges.value), + layout: layoutFromNodes(nodes.value), + }) + } + + function captureBaseline() { + baseline.value = snapshot() + } + + const isDirty = computed(() => snapshot() !== baseline.value) + + function cloneGraph(): GraphSnapshot { + return { + definitionId: definitionId.value, + name: name.value, + startStepId: startStepId.value, + nodes: nodes.value.map(slimNode), + edges: edges.value.map(slimEdge), + } + } + + function resetHistory() { + undoStack.value = [] + redoStack.value = [] + } + + function pushHistory() { + if (historyPaused) { + return + } + undoStack.value = [...undoStack.value, cloneGraph()].slice(-50) + redoStack.value = [] + } + + function applySnapshot(snap: GraphSnapshot) { + historyPaused = true + definitionId.value = snap.definitionId + name.value = snap.name + startStepId.value = snap.startStepId + nodes.value = snap.nodes.map(slimNode) + edges.value = snap.edges.map(slimEdge) + selectedNodeId.value = null + selectedEdgeId.value = null + selectedNodeIds.value = [] + selectedEdgeIds.value = [] + historyPaused = false + } + + function undo() { + const prev = undoStack.value.at(-1) + if (!prev) { + return false + } + const current = cloneGraph() + undoStack.value = undoStack.value.slice(0, -1) + redoStack.value = [...redoStack.value, current] + applySnapshot(prev) + return true + } + + function redo() { + const next = redoStack.value.at(-1) + if (!next) { + return false + } + const current = cloneGraph() + redoStack.value = redoStack.value.slice(0, -1) + undoStack.value = [...undoStack.value, current] + applySnapshot(next) + return true + } + + function syncStartFlags() { + nodes.value = nodes.value.map((node) => { + if (!node.data || node.data.kind === 'END') { + return node + } + return { + ...node, + data: { ...node.data, isStart: node.id === startStepId.value }, + } + }) + } + + function clearSelection() { + selectedNodeId.value = null + selectedEdgeId.value = null + selectedNodeIds.value = [] + selectedEdgeIds.value = [] + } + + function clear() { + definitionId.value = '' + name.value = '' + startStepId.value = '' + publishedVersion.value = null + loadedFromDraft.value = false + nodes.value = [] + edges.value = [] + clearSelection() + resetHistory() + captureBaseline() + } + + function resetEmpty(id = `process-${Date.now().toString(36)}`) { + definitionId.value = id + name.value = 'Untitled process' + startStepId.value = '' + publishedVersion.value = null + loadedFromDraft.value = false + nodes.value = [] + edges.value = [] + clearSelection() + resetHistory() + captureBaseline() + } + + function loadDocument(doc: ProcessDefinitionDocument) { + const mapped = fromDocument(doc) + definitionId.value = doc.id + name.value = doc.name + startStepId.value = mapped.startStepId + publishedVersion.value = doc.version ?? null + loadedFromDraft.value = false + const laid = applyLayeredLayout(mapped.nodes, mapped.edges, mapped.startStepId) + nodes.value = laid.nodes + edges.value = laid.edges + clearSelection() + resetHistory() + captureBaseline() + } + + function loadGraph(params: { + id: string + name: string + startStepId: string + nodes: DesignerNode[] + edges: DesignerEdge[] + version?: number | null + fromDraft?: boolean + }) { + definitionId.value = params.id + name.value = params.name + startStepId.value = params.startStepId + publishedVersion.value = params.version ?? null + loadedFromDraft.value = Boolean(params.fromDraft) + const expanded = expandLegacyParallel(params.nodes, params.edges) + nodes.value = fitParallelFrames(expanded.nodes) + edges.value = assignEdgeHandles(expanded.nodes, expanded.edges) + syncStartFlags() + clearSelection() + resetHistory() + captureBaseline() + } + + function relayout() { + pushHistory() + const laid = applyLayeredLayout(nodes.value, edges.value, startStepId.value) + nodes.value = laid.nodes + edges.value = laid.edges + } + + function syncEdgeHandles() { + edges.value = assignEdgeHandles(nodes.value, edges.value) + } + + function settleLanes() { + const settled = settleLaneNodes(nodes.value) + nodes.value = settled + edges.value = assignEdgeHandles(settled, dropCrossLaneEdges(settled, edges.value)) + } + + function addNode(type: string, position: { x: number; y: number }): 'ok' | 'duplicate-end' { + let data: DesignerNodeData + let nodeType = type + if (type === APPROVAL_TYPE) { + data = defaultApprovalData() + } else if (type === ACTION_TYPE) { + data = defaultActionData() + } else if (type === PARALLEL_TYPE) { + pushHistory() + const id = nextId(nodeType) + const built = materializeParallel(id, 'Parallel', position, [ + { id: 'legal', steps: [{ id: nextId('legal'), name: 'Legal', candidates: [], policy: 'ANY' }], transitions: [] }, + { id: 'finance', steps: [{ id: nextId('finance'), name: 'Finance', candidates: [], policy: 'ANY' }], transitions: [] }, + ]) + nodes.value = fitParallelFrames([...nodes.value, built.parent, ...built.children]) + if (!startStepId.value) { + startStepId.value = id + syncStartFlags() + } + selectedNodeId.value = id + selectedNodeIds.value = [id] + selectedEdgeId.value = null + selectedEdgeIds.value = [] + return 'ok' + } else { + nodeType = END_TYPE + if (nodes.value.some((n) => n.type === END_TYPE)) { + return 'duplicate-end' + } + data = defaultEndData() + } + pushHistory() + const id = nodeType === END_TYPE ? END_NODE_ID : nextId(nodeType) + const node: DesignerNode = { id, type: nodeType, position, data } + nodes.value = [...nodes.value, node] + if (!startStepId.value && nodeType !== END_TYPE) { + startStepId.value = id + syncStartFlags() + } + selectedNodeId.value = id + selectedNodeIds.value = [id] + selectedEdgeId.value = null + selectedEdgeIds.value = [] + return 'ok' + } + + function setStart(id: string) { + const node = nodes.value.find((n) => n.id === id) + if (!node || node.type === END_TYPE || node.parentNode) { + return + } + pushHistory() + startStepId.value = id + syncStartFlags() + } + + function renameNode(oldId: string, newId: string): boolean { + const trimmed = newId.trim() + if (!trimmed || trimmed === oldId) { + return true + } + if (nodes.value.some((n) => n.id === trimmed)) { + return false + } + pushHistory() + nodes.value = nodes.value.map((n) => (n.id === oldId ? { ...n, id: trimmed } : n)) + edges.value = edges.value.map((e) => ({ + ...e, + source: e.source === oldId ? trimmed : e.source, + target: e.target === oldId ? trimmed : e.target, + })) + if (startStepId.value === oldId) { + startStepId.value = trimmed + } + if (selectedNodeId.value === oldId) { + selectedNodeId.value = trimmed + } + selectedNodeIds.value = selectedNodeIds.value.map((id) => (id === oldId ? trimmed : id)) + return true + } + + function patchNodeData(id: string, patch: Record) { + pushHistory() + nodes.value = nodes.value.map((n) => { + if (n.id !== id) { + return n + } + return { ...n, data: { ...n.data, ...patch } as DesignerNodeData } + }) + } + + function patchEdgeData(id: string, patch: Partial) { + pushHistory() + edges.value = edges.value.map((e) => { + if (e.id !== id) { + return e + } + const data = { ...(e.data ?? { priority: 0 }), ...patch } + if ('when' in patch && patch.when === undefined) { + delete data.when + } + return { ...e, data, label: edgeLabel(data) } + }) + } + + function onConnect(connection: Connection) { + if (!connection.source || !connection.target) { + return + } + const sourceNode = nodes.value.find((n) => n.id === connection.source) + const targetNode = nodes.value.find((n) => n.id === connection.target) + if (sourceNode?.type === END_TYPE || targetNode?.type === PARALLEL_TYPE) { + return + } + const inner = Boolean(sourceNode?.parentNode || targetNode?.parentNode) + if (inner && (sourceNode?.parentNode !== targetNode?.parentNode || laneOf(sourceNode?.data) !== laneOf(targetNode?.data))) { + return + } + pushHistory() + const siblings = edges.value.filter((e) => e.source === connection.source) + const used = new Set(siblings.map((e) => e.data?.priority ?? 0)) + let priority = 0 + while (used.has(priority)) { + priority += 1 + } + const data: DesignerEdgeData = { priority } + const edge: DesignerEdge = { + id: nextId('e'), + source: connection.source, + target: connection.target, + sourceHandle: normalizeHandleId(connection.sourceHandle) ?? 'source-bottom', + targetHandle: normalizeHandleId(connection.targetHandle) ?? 'target-top', + label: edgeLabel(data), + data, + } + edges.value = [...edges.value, edge] + edges.value = assignEdgeHandles(nodes.value, edges.value) + } + + function selectNode(id: string | null) { + selectedNodeId.value = id + selectedNodeIds.value = id ? [id] : [] + if (id) { + selectedEdgeId.value = null + selectedEdgeIds.value = [] + } + } + + function selectEdge(id: string | null) { + selectedEdgeId.value = id + selectedEdgeIds.value = id ? [id] : [] + if (id) { + selectedNodeId.value = null + selectedNodeIds.value = [] + } + } + + function setSelection(nodeIds: string[], edgeIds: string[]) { + selectedNodeIds.value = nodeIds + selectedEdgeIds.value = edgeIds + selectedNodeId.value = nodeIds.length === 1 ? (nodeIds[0] ?? null) : nodeIds[0] ?? null + if (nodeIds.length > 1) { + selectedNodeId.value = nodeIds[0] ?? null + } + selectedEdgeId.value = nodeIds.length ? null : (edgeIds[0] ?? null) + } + + function hasOutgoing(id: string) { + return edges.value.some((e) => e.source === id) + } + + function hasOutgoingAmong(ids: string[]) { + return ids.some((id) => hasOutgoing(id)) + } + + function removeNode(id: string) { + const node = nodes.value.find((n) => n.id === id) + if (!node) { + return + } + const drop = new Set([id, ...nodes.value.filter((n) => n.parentNode === id).map((n) => n.id)]) + pushHistory() + nodes.value = nodes.value.filter((n) => !drop.has(n.id)) + edges.value = edges.value.filter((e) => !drop.has(e.source) && !drop.has(e.target)) + selectedNodeIds.value = selectedNodeIds.value.filter((n) => !drop.has(n)) + if (selectedNodeId.value && drop.has(selectedNodeId.value)) { + selectedNodeId.value = selectedNodeIds.value[0] ?? null + } + if (drop.has(startStepId.value)) { + const next = nodes.value.find((n) => n.type !== END_TYPE && !n.parentNode) + startStepId.value = next?.id ?? '' + syncStartFlags() + } + } + + function removeEdge(id: string) { + pushHistory() + edges.value = edges.value.filter((e) => e.id !== id) + selectedEdgeIds.value = selectedEdgeIds.value.filter((e) => e !== id) + if (selectedEdgeId.value === id) { + selectedEdgeId.value = selectedEdgeIds.value[0] ?? null + } + } + + function removeNodes(ids: string[]) { + const set = new Set(ids) + for (const node of nodes.value) { + if (node.parentNode && set.has(node.parentNode)) { + set.add(node.id) + } + } + if (!set.size) { + return + } + pushHistory() + nodes.value = nodes.value.filter((n) => !set.has(n.id)) + edges.value = edges.value.filter((e) => !set.has(e.source) && !set.has(e.target)) + selectedNodeIds.value = [] + selectedNodeId.value = null + if (set.has(startStepId.value)) { + const next = nodes.value.find((n) => n.type !== END_TYPE && !n.parentNode) + startStepId.value = next?.id ?? '' + syncStartFlags() + } + } + + function removeEdges(ids: string[]) { + const set = new Set(ids) + if (!set.size) { + return + } + pushHistory() + edges.value = edges.value.filter((e) => !set.has(e.id)) + selectedEdgeIds.value = [] + selectedEdgeId.value = null + } + + function removeSelected() { + if (selectedNodeIds.value.length) { + removeNodes(selectedNodeIds.value) + return + } + if (selectedNodeId.value) { + removeNode(selectedNodeId.value) + return + } + if (selectedEdgeIds.value.length) { + removeEdges(selectedEdgeIds.value) + return + } + if (selectedEdgeId.value) { + removeEdge(selectedEdgeId.value) + } + } + + function withDescendants(ids: string[]) { + const set = new Set(ids) + let grew = true + while (grew) { + grew = false + for (const node of nodes.value) { + if (node.parentNode && set.has(node.parentNode) && !set.has(node.id)) { + set.add(node.id) + grew = true + } + } + } + return [...set] + } + + function copySelection() { + const ids = selectedNodeIds.value.length + ? selectedNodeIds.value + : selectedNodeId.value + ? [selectedNodeId.value] + : [] + if (!ids.length) { + clipboard = null + return false + } + const idSet = new Set(withDescendants(ids)) + clipboard = { + nodes: nodes.value.filter((n) => idSet.has(n.id)).map(slimNode), + edges: edges.value + .filter((e) => idSet.has(e.source) && idSet.has(e.target)) + .map(slimEdge), + } + return true + } + + function pasteClipboard() { + if (!clipboard?.nodes.length) { + return false + } + pushHistory() + const hasEnd = nodes.value.some((n) => n.type === END_TYPE) + const idMap = new Map() + const pastedNodes: DesignerNode[] = [] + const ordered = [...clipboard.nodes].sort((a, b) => Number(Boolean(a.parentNode)) - Number(Boolean(b.parentNode))) + for (const node of ordered) { + if (node.type === END_TYPE && hasEnd) { + continue + } + const id = node.type === END_TYPE ? END_NODE_ID : nextId(node.type) + idMap.set(node.id, id) + const parentMoved = Boolean(node.parentNode && idMap.has(node.parentNode)) + pastedNodes.push({ + ...slimNode(node), + id, + parentNode: node.parentNode ? (idMap.get(node.parentNode) ?? node.parentNode) : undefined, + position: parentMoved || !node.parentNode + ? node.parentNode + ? { ...node.position } + : { x: node.position.x + 40, y: node.position.y + 40 } + : { x: node.position.x + 40, y: node.position.y }, + }) + } + const pastedEdges: DesignerEdge[] = [] + for (const edge of clipboard.edges) { + const source = idMap.get(edge.source) + const target = idMap.get(edge.target) + if (!source || !target) { + continue + } + const data = { ...(edge.data ?? { priority: 0 }) } + pastedEdges.push({ + ...slimEdge(edge), + id: nextId('e'), + source, + target, + data, + label: edgeLabel(data), + }) + } + nodes.value = fitParallelFrames([...nodes.value, ...pastedNodes]) + edges.value = [...edges.value, ...pastedEdges] + selectedNodeIds.value = pastedNodes.map((n) => n.id) + selectedNodeId.value = pastedNodes[0]?.id ?? null + selectedEdgeId.value = null + selectedEdgeIds.value = [] + return pastedNodes.length > 0 + } + + function alignSelected(mode: 'left' | 'center' | 'top'): boolean { + const ids = selectedNodeIds.value.length >= 2 ? selectedNodeIds.value : [] + if (ids.length < 2) { + return false + } + const set = new Set(ids) + const selected = nodes.value.filter((n) => set.has(n.id)) + pushHistory() + if (mode === 'left') { + const x = Math.min(...selected.map((n) => n.position.x)) + for (const n of selected) { + n.position = { ...n.position, x } + } + } else if (mode === 'center') { + const xs = selected.map((n) => n.position.x) + const x = (Math.min(...xs) + Math.max(...xs)) / 2 + for (const n of selected) { + n.position = { ...n.position, x } + } + } else { + const y = Math.min(...selected.map((n) => n.position.y)) + for (const n of selected) { + n.position = { ...n.position, y } + } + } + nodes.value = settleLaneNodes(nodes.value) + return true + } + + function laneName(lanes: string[]) { + let index = lanes.length + 1 + let id = `branch-${index}` + while (lanes.includes(id)) { + index += 1 + id = `branch-${index}` + } + return id + } + + function addLane(parallelId: string) { + const parent = nodes.value.find((node) => node.id === parallelId) + if (!parent || parent.data.kind !== 'PARALLEL') { + return + } + pushHistory() + const lane = laneName(parent.data.lanes) + const step = spawnLaneStep(parallelId, lane, parent.data.lanes.length, 0, nextId(lane)) + nodes.value = fitParallelFrames([ + ...nodes.value.map((node) => + node.id === parallelId && node.data.kind === 'PARALLEL' + ? { ...node, data: { ...node.data, lanes: [...node.data.lanes, lane] } } + : node, + ), + step, + ]) + } + + function removeLane(parallelId: string, lane: string) { + const parent = nodes.value.find((node) => node.id === parallelId) + if (!parent || parent.data.kind !== 'PARALLEL' || parent.data.lanes.length <= 2) { + return + } + pushHistory() + const drop = new Set( + nodes.value.filter((node) => node.parentNode === parallelId && laneOf(node.data) === lane).map((node) => node.id), + ) + nodes.value = reflowLanes( + nodes.value + .filter((node) => !drop.has(node.id)) + .map((node) => + node.id === parallelId && node.data.kind === 'PARALLEL' + ? { ...node, data: { ...node.data, lanes: node.data.lanes.filter((item: string) => item !== lane) } } + : node, + ), + parallelId, + ) + edges.value = edges.value.filter((edge) => !drop.has(edge.source) && !drop.has(edge.target)) + } + + function addLaneStep(parallelId: string, lane: string) { + const parent = nodes.value.find((node) => node.id === parallelId) + if (!parent || parent.data.kind !== 'PARALLEL' || !parent.data.lanes.includes(lane)) { + return + } + pushHistory() + const laneIndex = parent.data.lanes.indexOf(lane) + const stepIndex = nodes.value.filter((node) => node.parentNode === parallelId && laneOf(node.data) === lane).length + nodes.value = fitParallelFrames([...nodes.value, spawnLaneStep(parallelId, lane, laneIndex, stepIndex, nextId(lane))]) + } + + function renameLane(parallelId: string, from: string, to: string): boolean { + const trimmed = to.trim() + const parent = nodes.value.find((node) => node.id === parallelId) + if (!parent || parent.data.kind !== 'PARALLEL' || !trimmed || trimmed === from) { + return trimmed === from + } + if (parent.data.lanes.includes(trimmed)) { + return false + } + pushHistory() + nodes.value = nodes.value.map((node) => { + if (node.id === parallelId && node.data.kind === 'PARALLEL') { + return { ...node, data: { ...node.data, lanes: node.data.lanes.map((lane: string) => (lane === from ? trimmed : lane)) } } + } + if (node.parentNode === parallelId && laneOf(node.data) === from && (node.data.kind === 'APPROVAL' || node.data.kind === 'ACTION')) { + return { ...node, data: { ...node.data, laneId: trimmed } } + } + return node + }) + return true + } + + function document(): ProcessDefinitionDocument { + return toDocument(definitionId.value, name.value, startStepId.value, nodes.value, edges.value) + } + + function localErrors() { + return graphErrors.value + } + + return { + definitionId, + name, + startStepId, + publishedVersion, + loadedFromDraft, + nodes, + edges, + selectedNodeId, + selectedEdgeId, + selectedNodeIds, + selectedEdgeIds, + selectedNode, + selectedEdge, + graphErrors, + isDirty, + canUndo, + canRedo, + canPaste, + clear, + resetEmpty, + loadDocument, + loadGraph, + addNode, + setStart, + renameNode, + patchNodeData, + patchEdgeData, + onConnect, + selectNode, + selectEdge, + setSelection, + removeNode, + removeEdge, + removeNodes, + removeEdges, + removeSelected, + hasOutgoing, + hasOutgoingAmong, + copySelection, + pasteClipboard, + undo, + redo, + pushHistory, + alignSelected, + document, + localErrors, + captureBaseline, + relayout, + syncEdgeHandles, + settleLanes, + syncStartFlags, + addLane, + removeLane, + addLaneStep, + renameLane, + } +}) diff --git a/src/stores/drafts.ts b/src/stores/drafts.ts new file mode 100644 index 0000000..01fbd54 --- /dev/null +++ b/src/stores/drafts.ts @@ -0,0 +1,82 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { ProcessDefinitionDocument } from '@/types/definition' +import { layoutFromNodes, type DesignerEdge, type DesignerNode } from '@/mapper/graph' + +export interface DesignerDraft { + id: string + name: string + startStepId: string + nodes: DesignerNode[] + edges: DesignerEdge[] + layout: Record + updatedAt: number +} + +const STORAGE_KEY = 'ordo-designer-drafts' + +function readAll(): Record { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) { + return {} + } + return JSON.parse(raw) as Record + } catch { + return {} + } +} + +function writeAll(drafts: Record) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(drafts)) +} + +export const useDraftsStore = defineStore('drafts', () => { + const revision = ref(0) + + function list(): DesignerDraft[] { + return Object.values(readAll()).sort((a, b) => b.updatedAt - a.updatedAt) + } + + function get(id: string): DesignerDraft | undefined { + return readAll()[id] + } + + function save(params: { + id: string + name: string + startStepId: string + nodes: DesignerNode[] + edges: DesignerEdge[] + }) { + const drafts = readAll() + drafts[params.id] = { + id: params.id, + name: params.name, + startStepId: params.startStepId, + nodes: params.nodes, + edges: params.edges, + layout: layoutFromNodes(params.nodes), + updatedAt: Date.now(), + } + writeAll(drafts) + revision.value += 1 + } + + function layoutOf(id: string): Record { + return readAll()[id]?.layout ?? {} + } + + function remove(id: string) { + const drafts = readAll() + delete drafts[id] + writeAll(drafts) + revision.value += 1 + } + + function fromPublished(doc: ProcessDefinitionDocument): DesignerDraft | undefined { + return get(doc.id) + } + + return { revision, list, get, save, layoutOf, remove, fromPublished } +}) diff --git a/src/styles/main.css b/src/styles/main.css new file mode 100644 index 0000000..906ff12 --- /dev/null +++ b/src/styles/main.css @@ -0,0 +1,30 @@ +@import "tailwindcss"; + +html, +body, +#app { + height: 100%; + margin: 0; +} + +.vue-flow { + background: + radial-gradient(circle at 1px 1px, #e2e8f0 1px, transparent 0) 0 0 / 20px 20px, + #f8fafc; +} + +.vue-flow__edge-path { + fill: none; +} + +.vue-flow__handle { + transition: transform 0.15s ease; +} + +.vue-flow__handle:hover { + transform: scale(1.25); +} + +.pointer-events-all { + pointer-events: all; +} diff --git a/src/types/definition.ts b/src/types/definition.ts new file mode 100644 index 0000000..f90d337 --- /dev/null +++ b/src/types/definition.ts @@ -0,0 +1,156 @@ +import type { RoutingWhen } from '@/mapper/when' + +export type StepKind = 'APPROVAL' | 'ACTION' | 'PARALLEL' +export type ApprovalPolicy = 'ANY' | 'ALL' +export type DueThen = 'reassign' | 'notify' | 'goto' + +export interface StepDueDocument { + after: string + then: DueThen + to?: string | null + action?: string | null +} + +export interface BranchDocument { + id: string + steps: StepDocument[] + transitions: TransitionDocument[] +} + +export interface StepDocument { + id: string + name: string + kind?: StepKind + candidates?: string[] + policy?: ApprovalPolicy + action?: string + due?: StepDueDocument | null + branches?: BranchDocument[] +} + +export interface TransitionDocument { + from: string + to: string | null + when?: RoutingWhen + priority?: number +} + +export interface ProcessDefinitionDocument { + id: string + name: string + version?: number + startStep?: string + steps: StepDocument[] + transitions: TransitionDocument[] +} + +export interface CatalogParam { + name: string + type: 'number' | 'string' | 'boolean' + label: string +} + +export interface CatalogItem { + key: string + label: string + params?: CatalogParam[] +} + +export interface PageResponse { + content: T[] + totalElements: number + page: number + size: number + totalPages: number + hasNext: boolean +} + +export type NodeKind = 'APPROVAL' | 'ACTION' | 'PARALLEL' | 'END' + +export interface ApprovalNodeData { + kind: 'APPROVAL' + name: string + candidates: string[] + policy: ApprovalPolicy + dueEnabled: boolean + due: StepDueDocument + isStart?: boolean + laneId?: string +} + +export interface ActionNodeData { + kind: 'ACTION' + name: string + action: string + isStart?: boolean + laneId?: string +} + +export interface ParallelNodeData { + kind: 'PARALLEL' + name: string + lanes: string[] + isStart?: boolean +} + +export interface EndNodeData { + kind: 'END' + name: string +} + +export type DesignerNodeData = ApprovalNodeData | ActionNodeData | ParallelNodeData | EndNodeData + +export interface DesignerEdgeData { + when?: RoutingWhen + priority: number +} + +export const LEAVE_REQUEST_EXAMPLE: ProcessDefinitionDocument = { + id: 'leave-request-routed', + name: 'Leave request', + startStep: 'notify-submitted', + steps: [ + { id: 'notify-submitted', name: 'Notify submitted', kind: 'ACTION', action: 'leave-submitted-mail' }, + { id: 'manager', name: 'Manager approval', candidates: ['maria'], policy: 'ANY' }, + { id: 'hr', name: 'HR approval', candidates: ['henry'], policy: 'ANY' }, + { id: 'notify-approved', name: 'Notify approved', kind: 'ACTION', action: 'leave-approved-mail' }, + ], + transitions: [ + { from: 'notify-submitted', to: 'manager' }, + { from: 'manager', to: 'hr', when: { gt: ['days', 3] }, priority: 0 }, + { from: 'manager', to: 'notify-approved', priority: 1 }, + { from: 'hr', to: 'notify-approved' }, + { from: 'notify-approved', to: null }, + ], +} + +export function defaultApprovalData(): ApprovalNodeData { + return { + kind: 'APPROVAL', + name: 'Approval', + candidates: [], + policy: 'ANY', + dueEnabled: false, + due: { after: 'PT48H', then: 'notify' }, + } +} + +export function defaultActionData(): ActionNodeData { + return { + kind: 'ACTION', + name: 'Action', + action: '', + } +} + +export function defaultParallelData(): ParallelNodeData { + return { + kind: 'PARALLEL', + name: 'Parallel', + lanes: ['legal', 'finance'], + } +} + +export function defaultEndData(): EndNodeData { + return { kind: 'END', name: 'End' } +} diff --git a/src/views/DefinitionListView.vue b/src/views/DefinitionListView.vue new file mode 100644 index 0000000..1f7e123 --- /dev/null +++ b/src/views/DefinitionListView.vue @@ -0,0 +1,237 @@ + + + diff --git a/src/views/DesignerView.vue b/src/views/DesignerView.vue new file mode 100644 index 0000000..df8d3f6 --- /dev/null +++ b/src/views/DesignerView.vue @@ -0,0 +1,748 @@ + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..c0f2d86 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..c9b2bad --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,27 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ebe5caf --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,28 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' +import tailwindcss from '@tailwindcss/vite' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + tailwindcss(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + proxy: { + '/ordo': { + target: 'http://localhost:8080', + changeOrigin: true, + }, + }, + }, +})