From 595528cb616e97010c0222e061f2e69e39858885 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:06:31 -0400 Subject: [PATCH 01/18] CAMEL-23636: add frontend-maven-plugin build infrastructure for web component Co-Authored-By: Claude Sonnet 4.6 --- components/camel-diagram/pom.xml | 65 +- .../camel-diagram/src/main/frontend/build.mjs | 34 + .../src/main/frontend/package-lock.json | 1943 +++++++++++++++++ .../src/main/frontend/package.json | 17 + .../main/frontend/src/camel-route-diagram.js | 22 + .../camel/diagram/camel-route-diagram.js | 40 + .../camel/diagram/WebComponentBundleTest.java | 44 + 7 files changed, 2163 insertions(+), 2 deletions(-) create mode 100644 components/camel-diagram/src/main/frontend/build.mjs create mode 100644 components/camel-diagram/src/main/frontend/package-lock.json create mode 100644 components/camel-diagram/src/main/frontend/package.json create mode 100644 components/camel-diagram/src/main/frontend/src/camel-route-diagram.js create mode 100644 components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js create mode 100644 components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java diff --git a/components/camel-diagram/pom.xml b/components/camel-diagram/pom.xml index e25ac06f9f729..cd3221780c965 100644 --- a/components/camel-diagram/pom.xml +++ b/components/camel-diagram/pom.xml @@ -17,7 +17,8 @@ limitations under the License. --> - + 4.0.0 @@ -99,7 +100,67 @@ test - + + + + com.mycila + license-maven-plugin + + + + SLASHSTAR_STYLE + + + + + + src/main/frontend/package.json + src/main/frontend/package-lock.json + src/main/frontend/node_modules/** + + src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.15.1 + + src/main/frontend + ${project.build.directory} + + + + install-node-and-npm + + install-node-and-npm + + + v22.14.0 + 10.9.2 + + + + npm-install + npm + + + npm-build + + npm + + + run build + + + + + + + diff --git a/components/camel-diagram/src/main/frontend/build.mjs b/components/camel-diagram/src/main/frontend/build.mjs new file mode 100644 index 0000000000000..b2247963e3b56 --- /dev/null +++ b/components/camel-diagram/src/main/frontend/build.mjs @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as esbuild from 'esbuild'; +import { mkdirSync } from 'fs'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const outfile = resolve(__dirname, '../resources/META-INF/resources/camel/diagram/camel-route-diagram.js'); +mkdirSync(dirname(outfile), { recursive: true }); + +await esbuild.build({ + entryPoints: [resolve(__dirname, 'src/camel-route-diagram.js')], + bundle: true, + minify: true, + format: 'esm', + outfile, +}); + +console.log('Built:', outfile); diff --git a/components/camel-diagram/src/main/frontend/package-lock.json b/components/camel-diagram/src/main/frontend/package-lock.json new file mode 100644 index 0000000000000..8d4298df6e109 --- /dev/null +++ b/components/camel-diagram/src/main/frontend/package-lock.json @@ -0,0 +1,1943 @@ +{ + "name": "camel-route-diagram", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "camel-route-diagram", + "version": "1.0.0", + "dependencies": { + "lit": "^3.2.1" + }, + "devDependencies": { + "esbuild": "^0.24.2", + "vitest": "^2.1.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lit": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", + "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/components/camel-diagram/src/main/frontend/package.json b/components/camel-diagram/src/main/frontend/package.json new file mode 100644 index 0000000000000..3d711b7a8969e --- /dev/null +++ b/components/camel-diagram/src/main/frontend/package.json @@ -0,0 +1,17 @@ +{ + "name": "camel-route-diagram", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "node build.mjs", + "test": "vitest run" + }, + "dependencies": { + "lit": "^3.2.1" + }, + "devDependencies": { + "esbuild": "^0.24.2", + "vitest": "^2.1.8" + } +} diff --git a/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js b/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js new file mode 100644 index 0000000000000..54aaafafa29ff --- /dev/null +++ b/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LitElement, html } from 'lit'; + +export class CamelRouteDiagram extends LitElement { + render() { return html`

camel-route-diagram loading…

`; } +} +customElements.define('camel-route-diagram', CamelRouteDiagram); diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js new file mode 100644 index 0000000000000..0fa1afbfe1af6 --- /dev/null +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -0,0 +1,40 @@ +var H=globalThis,N=H.ShadowRoot&&(H.ShadyCSS===void 0||H.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,G=Symbol(),Z=new WeakMap,M=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==G)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(N&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=Z.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&Z.set(e,t))}return t}toString(){return this.cssText}},Q=r=>new M(typeof r=="string"?r:r+"",void 0,G);var X=(r,t)=>{if(N)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),i=H.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,r.appendChild(s)}},L=N?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return Q(e)})(r):r;var{is:_t,defineProperty:ft,getOwnPropertyDescriptor:mt,getOwnPropertyNames:At,getOwnPropertySymbols:gt,getPrototypeOf:yt}=Object,T=globalThis,Y=T.trustedTypes,vt=Y?Y.emptyScript:"",St=T.reactiveElementPolyfillSupport,b=(r,t)=>r,k={toAttribute(r,t){switch(t){case Boolean:r=r?vt:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},et=(r,t)=>!_t(r,t),tt={attribute:!0,type:String,converter:k,reflect:!1,useDefault:!1,hasChanged:et};Symbol.metadata??=Symbol("metadata"),T.litPropertyMetadata??=new WeakMap;var $=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=tt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&ft(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){let{get:i,set:o}=mt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:i,set(n){let l=i?.call(this);o?.call(this,n),this.requestUpdate(t,l,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??tt}static _$Ei(){if(this.hasOwnProperty(b("elementProperties")))return;let t=yt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(b("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(b("properties"))){let e=this.properties,s=[...At(e),...gt(e)];for(let i of s)this.createProperty(i,e[i])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let i of s)e.unshift(L(i))}else t!==void 0&&e.push(L(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return X(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:k).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){let o=s.getPropertyOptions(i),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:k;this._$Em=i;let l=n.fromAttribute(e,o.type);this[i]=l??this._$Ej?.get(i)??l,this._$Em=null}}requestUpdate(t,e,s,i=!1,o){if(t!==void 0){let n=this.constructor;if(i===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??et)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[i,o]of s){let{wrapped:n}=o,l=this[i];n!==!0||this._$AL.has(i)||l===void 0||this.C(i,void 0,o,l)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};$.elementStyles=[],$.shadowRootOptions={mode:"open"},$[b("elementProperties")]=new Map,$[b("finalized")]=new Map,St?.({ReactiveElement:$}),(T.reactiveElementVersions??=[]).push("2.1.2");var W=globalThis,st=r=>r,R=W.trustedTypes,it=R?R.createPolicy("lit-html",{createHTML:r=>r}):void 0,lt="$lit$",f=`lit$${Math.random().toFixed(9).slice(2)}$`,ct="?"+f,Et=`<${ct}>`,y=document,w=()=>y.createComment(""),x=r=>r===null||typeof r!="object"&&typeof r!="function",q=Array.isArray,bt=r=>q(r)||typeof r?.[Symbol.iterator]=="function",D=`[ +\f\r]`,C=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,rt=/-->/g,ot=/>/g,A=RegExp(`>|${D}(?:([^\\s"'>=/]+)(${D}*=${D}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),nt=/'/g,ht=/"/g,dt=/^(?:script|style|textarea|title)$/i,K=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),pt=K(1),Nt=K(2),Tt=K(3),v=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),at=new WeakMap,g=y.createTreeWalker(y,129);function ut(r,t){if(!q(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return it!==void 0?it.createHTML(t):t}var Ct=(r,t)=>{let e=r.length-1,s=[],i,o=t===2?"":t===3?"":"",n=C;for(let l=0;l"?(n=i??C,a=-1):p[1]===void 0?a=-2:(a=n.lastIndex-p[2].length,c=p[1],n=p[3]===void 0?A:p[3]==='"'?ht:nt):n===ht||n===nt?n=A:n===rt||n===ot?n=C:(n=A,i=void 0);let _=n===A&&r[l+1].startsWith("/>")?" ":"";o+=n===C?h+Et:a>=0?(s.push(c),h.slice(0,a)+lt+h.slice(a)+f+_):h+f+(a===-2?l:_)}return[ut(r,o+(r[e]||"")+(t===2?"":t===3?"":"")),s]},P=class r{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let o=0,n=0,l=t.length-1,h=this.parts,[c,p]=Ct(t,e);if(this.el=r.createElement(c,s),g.currentNode=this.el.content,e===2||e===3){let a=this.el.content.firstChild;a.replaceWith(...a.childNodes)}for(;(i=g.nextNode())!==null&&h.length0){i.textContent=R?R.emptyScript:"";for(let _=0;_2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){let o=this.strings,n=!1;if(o===void 0)t=S(this,t,e,0),n=!x(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{let l=t,h,c;for(t=o[0],h=0;h{let s=e?.renderBefore??t,i=s._$litPart$;if(i===void 0){let o=e?.renderBefore??null;s._$litPart$=i=new U(t.insertBefore(w(),o),o,void 0,e??{})}return i._$AI(r),i};var F=globalThis,m=class extends ${constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=$t(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return v}};m._$litElement$=!0,m.finalized=!0,F.litElementHydrateSupport?.({LitElement:m});var xt=F.litElementPolyfillSupport;xt?.({LitElement:m});(F.litElementVersions??=[]).push("4.2.2");var J=class extends m{render(){return pt`

camel-route-diagram loading…

`}};customElements.define("camel-route-diagram",J);export{J as CamelRouteDiagram}; +/*! Bundled license information: + +@lit/reactive-element/css-tag.js: + (** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +@lit/reactive-element/reactive-element.js: + (** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +lit-html/lit-html.js: + (** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +lit-element/lit-element.js: + (** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +lit-html/is-server.js: + (** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) +*/ diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java new file mode 100644 index 0000000000000..3d0ac58ade44b --- /dev/null +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.diagram; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class WebComponentBundleTest { + + @Test + void bundledJsExistsInClasspath() { + URL url = getClass().getClassLoader() + .getResource("META-INF/resources/camel/diagram/camel-route-diagram.js"); + assertThat(url).as("camel-route-diagram.js must be bundled").isNotNull(); + } + + @Test + void bundledJsIsNonEmpty() throws IOException { + try (InputStream is = getClass().getClassLoader() + .getResourceAsStream("META-INF/resources/camel/diagram/camel-route-diagram.js")) { + assertThat(is).isNotNull(); + assertThat(is.readAllBytes().length).isGreaterThan(1000); + } + } +} From 014a7405ad0f293110dd5d9a36c72e1de17d1cd5 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:07:49 -0400 Subject: [PATCH 02/18] CAMEL-23636: add JS layout algorithm ported from RouteDiagramLayoutEngine Co-Authored-By: Claude Sonnet 4.6 --- .../src/main/frontend/src/layout.js | 172 ++++++++++++++++++ .../src/main/frontend/test/layout.test.js | 113 ++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 components/camel-diagram/src/main/frontend/src/layout.js create mode 100644 components/camel-diagram/src/main/frontend/test/layout.test.js diff --git a/components/camel-diagram/src/main/frontend/src/layout.js b/components/camel-diagram/src/main/frontend/src/layout.js new file mode 100644 index 0000000000000..d79b77ce63a40 --- /dev/null +++ b/components/camel-diagram/src/main/frontend/src/layout.js @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const NODE_W = 180; +export const NODE_H = 36; +export const H_GAP = NODE_W / 2; +export const V_GAP = 40; +export const PADDING = 30; + +const BRANCHING_EIPS = new Set([ + 'choice', 'multicast', 'doTry', 'loadBalance', 'recipientList', 'circuitBreaker', +]); + +/** + * Builds a tree of {info, children, parent, subtreeWidth} nodes from a flat, level-ordered array. + * Faithfully ports RouteDiagramLayoutEngine.buildTree(). + * + * @param {Array<{type:string, id:string, level:number, code:string}>} nodes + * @returns {{info, children, parent, subtreeWidth}|null} + */ +export function buildTree(nodes) { + if (!nodes.length) return null; + const root = { info: nodes[0], children: [], parent: null, subtreeWidth: 0 }; + let current = root; + + for (let i = 1; i < nodes.length; i++) { + const ni = nodes[i]; + const tn = { info: ni, children: [], parent: null, subtreeWidth: 0 }; + + if (ni.level > current.info.level) { + current.children.push(tn); + tn.parent = current; + } else if (ni.level === current.info.level) { + const parent = current.parent ?? root; + parent.children.push(tn); + tn.parent = parent; + } else { + let ancestor = current.parent; + while (ancestor && ancestor.info.level >= ni.level) { + ancestor = ancestor.parent; + } + const target = ancestor ?? root; + target.children.push(tn); + tn.parent = target; + } + current = tn; + } + return root; +} + +/** + * Computes subtreeWidth on every node (bottom-up). + * Branching EIPs: sum of child widths + (n-1)*H_GAP. + * Others: max of child widths. + * + * @param {{info, children, subtreeWidth}} node + * @returns {number} + */ +export function computeSubtreeWidth(node) { + if (!node.children.length) { + node.subtreeWidth = NODE_W; + return NODE_W; + } + if (BRANCHING_EIPS.has(node.info.type)) { + let total = 0; + node.children.forEach((c, i) => { + if (i > 0) total += H_GAP; + total += computeSubtreeWidth(c); + }); + node.subtreeWidth = Math.max(NODE_W, total); + } else { + node.subtreeWidth = Math.max(NODE_W, ...node.children.map(computeSubtreeWidth)); + } + return node.subtreeWidth; +} + +/** + * Walks the tree and populates positions[id] with {x, y, w, h, parentId, type, code, ...}. + * x, y are the top-left corner of the node box in SVG logical pixels. + * + * @param {{info, children, parent, subtreeWidth}} node + * @param {number} x left edge of the available horizontal band + * @param {number} y top of this node + * @param {number} parentWidth width of the parent's horizontal band + * @param {Object} positions output map: id → position record + */ +export function assignPositions(node, x, y, parentWidth, positions) { + const available = Math.max(node.subtreeWidth, parentWidth); + const nodeX = x + (available - NODE_W) / 2; + + positions[node.info.id] = { + x: nodeX, + y, + w: NODE_W, + h: NODE_H, + parentId: node.parent ? node.parent.info.id : null, + type: node.info.type, + code: node.info.code, + description: node.info.description ?? null, + uri: node.info.uri ?? null, + statistics: node.info.statistics ?? null, + }; + + if (!node.children.length) return; + + const childY = y + NODE_H + V_GAP; + + if (BRANCHING_EIPS.has(node.info.type)) { + let childX = x + (available - node.subtreeWidth) / 2; + for (const child of node.children) { + assignPositions(child, childX, childY, child.subtreeWidth, positions); + childX += child.subtreeWidth + H_GAP; + } + } else { + let curY = childY; + for (const child of node.children) { + assignPositions(child, x, curY, available, positions); + curY = subtreeMaxY(child, positions) + V_GAP; + } + } +} + +function subtreeMaxY(node, positions) { + const pos = positions[node.info.id]; + let my = pos ? pos.y + pos.h : 0; + for (const child of node.children) { + my = Math.max(my, subtreeMaxY(child, positions)); + } + return my; +} + +/** + * Main entry: convert one route object from the route-structure JSON to positioned nodes. + * + * @param {{routeId:string, code:Array}} route + * @returns {{positions:Object, width:number, height:number}} + */ +export function layoutRoute(route) { + const nodes = route.code ?? []; + if (!nodes.length) { + return { positions: {}, width: NODE_W + PADDING * 2, height: NODE_H + PADDING * 2 }; + } + + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + + const positions = {}; + assignPositions(tree, PADDING, PADDING, tree.subtreeWidth, positions); + + let maxX = 0; + let maxYVal = 0; + for (const p of Object.values(positions)) { + maxX = Math.max(maxX, p.x + p.w); + maxYVal = Math.max(maxYVal, p.y + p.h); + } + + return { positions, width: maxX + PADDING, height: maxYVal + PADDING }; +} diff --git a/components/camel-diagram/src/main/frontend/test/layout.test.js b/components/camel-diagram/src/main/frontend/test/layout.test.js new file mode 100644 index 0000000000000..0294ecc379ba7 --- /dev/null +++ b/components/camel-diagram/src/main/frontend/test/layout.test.js @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { buildTree, computeSubtreeWidth, assignPositions, NODE_W, H_GAP } from '../src/layout.js'; + +const node = (type, id, level, code = type) => ({ type, id, level, code }); + +describe('buildTree', () => { + it('single root node returns tree with no children', () => { + const tree = buildTree([node('route', 'r1', 0)]); + expect(tree.info.id).toBe('r1'); + expect(tree.children).toHaveLength(0); + }); + + it('flat siblings share the same parent', () => { + const nodes = [ + node('route', 'r1', 0), + node('from', 'f1', 1), + node('log', 'l1', 1), + node('to', 't1', 1), + ]; + const tree = buildTree(nodes); + expect(tree.children).toHaveLength(3); + expect(tree.children.map(c => c.info.id)).toEqual(['f1', 'l1', 't1']); + }); + + it('nested choice/when structure builds correct tree', () => { + const nodes = [ + node('route', 'r1', 0), + node('from', 'f1', 1), + node('choice', 'ch', 1), + node('when', 'wh', 2), + node('to', 't1', 3), + node('otherwise', 'ow', 2), + node('to', 't2', 3), + ]; + const tree = buildTree(nodes); + const choice = tree.children.find(c => c.info.type === 'choice'); + expect(choice).toBeDefined(); + expect(choice.children).toHaveLength(2); + expect(choice.children[0].info.type).toBe('when'); + expect(choice.children[0].children[0].info.type).toBe('to'); + expect(choice.children[1].info.type).toBe('otherwise'); + }); +}); + +describe('computeSubtreeWidth', () => { + it('leaf node width equals NODE_W', () => { + const tree = buildTree([node('log', 'l1', 0)]); + computeSubtreeWidth(tree); + expect(tree.subtreeWidth).toBe(NODE_W); + }); + + it('branching EIP width is sum of branch widths plus gaps', () => { + const nodes = [ + node('choice', 'ch', 0), + node('when', 'w1', 1), + node('otherwise', 'ow', 1), + ]; + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + expect(tree.subtreeWidth).toBe(NODE_W * 2 + H_GAP); + }); +}); + +describe('assignPositions', () => { + it('single-chain route assigns increasing y values', () => { + const nodes = [ + node('route', 'r1', 0), + node('from', 'f1', 1), + node('log', 'l1', 1), + node('to', 't1', 1), + ]; + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + const positions = {}; + assignPositions(tree, 0, 0, tree.subtreeWidth, positions); + + const ys = ['r1', 'f1', 'l1', 't1'].map(id => positions[id].y); + expect(ys[1]).toBeGreaterThan(ys[0]); + expect(ys[2]).toBeGreaterThan(ys[1]); + expect(ys[3]).toBeGreaterThan(ys[2]); + }); + + it('branching EIP children are laid out side-by-side (different x, same y)', () => { + const nodes = [ + node('choice', 'ch', 0), + node('when', 'w1', 1), + node('otherwise', 'ow', 1), + ]; + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + const positions = {}; + assignPositions(tree, 0, 0, tree.subtreeWidth, positions); + + expect(Math.abs(positions['w1'].x - positions['ow'].x)).toBeGreaterThan(0); + expect(positions['w1'].y).toBe(positions['ow'].y); + }); +}); From 29a13ee8baa15545d0784c944e55bdf43d52c72a Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:09:13 -0400 Subject: [PATCH 03/18] CAMEL-23636: implement camel-route-diagram Lit web component with SVG rendering Co-Authored-By: Claude Sonnet 4.6 --- .../main/frontend/src/camel-route-diagram.js | 201 +++++++++++++++++- .../camel/diagram/camel-route-diagram.js | 65 +++++- .../src/test/resources/smoke-test.html | 64 ++++++ 3 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 components/camel-diagram/src/test/resources/smoke-test.html diff --git a/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js b/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js index 54aaafafa29ff..535c6a7fab6e9 100644 --- a/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js +++ b/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js @@ -14,9 +14,204 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LitElement, html } from 'lit'; +import { LitElement, html, svg, css, nothing } from 'lit'; +import { layoutRoute, NODE_W, NODE_H } from './layout.js'; -export class CamelRouteDiagram extends LitElement { - render() { return html`

camel-route-diagram loading…

`; } +const TYPE_COLORS = { + route: 'var(--crd-color-route, #6366f1)', + from: 'var(--crd-color-from, #0ea5e9)', + to: 'var(--crd-color-to, #0ea5e9)', + log: 'var(--crd-color-log, #64748b)', + choice: 'var(--crd-color-choice, #f59e0b)', + when: 'var(--crd-color-when, #fbbf24)', + otherwise: 'var(--crd-color-otherwise, #fbbf24)', + doTry: 'var(--crd-color-doTry, #f59e0b)', + doCatch: 'var(--crd-color-doCatch, #fbbf24)', + doFinally: 'var(--crd-color-doFinally, #fbbf24)', + multicast: 'var(--crd-color-multicast, #8b5cf6)', + circuitBreaker: 'var(--crd-color-circuitBreaker, #ef4444)', +}; + +function nodeColor(type) { + return TYPE_COLORS[type] ?? 'var(--crd-color-default, #6366f1)'; +} + +function truncate(text, maxLen = 28) { + if (!text) return ''; + const clean = text.replace(/^\.+/, ''); + return clean.length > maxLen ? clean.slice(0, maxLen - 1) + '…' : clean; +} + +function formatStat(stats) { + if (!stats) return null; + const total = stats.exchangesTotal ?? 0; + const failed = stats.exchangesFailed ?? 0; + return `✓${total} ✗${failed}`; +} + +/** + * A web component that renders Apache Camel route diagrams as interactive SVG. + * + * Attributes: + * src - URL of the route-structure dev console endpoint (required) + * refresh - polling interval in ms; 0 = disabled (default: 0) + * filter - route ID filter, forwarded as ?filter= query param (default: all routes) + * + * CSS custom properties (all optional): + * --crd-bg, --crd-fg, --crd-edge, --crd-font, --crd-font-size, --crd-stat + * --crd-color-{route,from,to,log,choice,when,otherwise,...,default} + * + * @since 4.21 + */ +class CamelRouteDiagram extends LitElement { + static properties = { + src: { type: String }, + refresh: { type: Number }, + filter: { type: String }, + _data: { state: true }, + _error: { state: true }, + }; + + static styles = css` + :host { + display: block; + font-family: var(--crd-font, system-ui, sans-serif); + font-size: var(--crd-font-size, 12px); + background: var(--crd-bg, transparent); + color: var(--crd-fg, #1e293b); + } + @media (prefers-color-scheme: dark) { + :host { color: var(--crd-fg, #e2e8f0); } + } + .error { color: #ef4444; padding: 8px; } + .loading { opacity: .6; padding: 8px; } + .route-label { + font-weight: 600; + font-size: 0.9em; + padding: 4px 0 2px 0; + opacity: .8; + } + svg { display: block; overflow: visible; } + `; + + #timer = null; + #uid = Math.random().toString(36).slice(2); + + constructor() { + super(); + this.src = ''; + this.refresh = 0; + this.filter = ''; + this._data = null; + this._error = null; + } + + connectedCallback() { + super.connectedCallback(); + this.#doFetch(); + if (this.refresh > 0) { + this.#timer = setInterval(() => this.#doFetch(), this.refresh); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + clearInterval(this.#timer); + this.#timer = null; + } + + async #doFetch() { + if (!this.src) return; + try { + const url = new URL(this.src, location.href); + if (this.filter) url.searchParams.set('filter', this.filter); + url.searchParams.set('metric', 'true'); + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`); + this._data = await res.json(); + this._error = null; + } catch (e) { + this._error = e.message; + } + } + + render() { + if (this._error) return html`

⚠ ${this._error}

`; + if (!this._data) return html`

Loading diagram…

`; + + const markerId = `arrow-${this.#uid}`; + return html` + + + + + + + + ${(this._data.routes ?? []).map(r => this.#renderRoute(r, markerId))} + `; + } + + #renderRoute(route, markerId) { + const { positions, width, height } = layoutRoute(route); + const ids = Object.keys(positions); + return html` +
${route.routeId}
+ + ${ids.map(id => this.#renderEdge(id, positions, markerId))} + ${ids.map(id => this.#renderNode(id, positions[id]))} + + `; + } + + #renderEdge(id, positions, markerId) { + const pos = positions[id]; + if (!pos.parentId) return nothing; + const parent = positions[pos.parentId]; + if (!parent) return nothing; + + const x1 = parent.x + NODE_W / 2; + const y1 = parent.y + NODE_H; + const x2 = pos.x + NODE_W / 2; + const y2 = pos.y; + const my = (y1 + y2) / 2; + + return svg``; + } + + #renderNode(id, pos) { + const label = truncate(pos.description ?? pos.code); + const stat = formatStat(pos.statistics); + const fill = nodeColor(pos.type); + const cx = pos.x + NODE_W / 2; + const textY = pos.y + NODE_H / 2 + 4; + + return svg` + + + + ${label} + + ${stat ? svg` + + ${stat} + ` : nothing} + `; + } } + customElements.define('camel-route-diagram', CamelRouteDiagram); diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index 0fa1afbfe1af6..cc6de5d4603bb 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -1,6 +1,65 @@ -var H=globalThis,N=H.ShadowRoot&&(H.ShadyCSS===void 0||H.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,G=Symbol(),Z=new WeakMap,M=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==G)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(N&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=Z.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&Z.set(e,t))}return t}toString(){return this.cssText}},Q=r=>new M(typeof r=="string"?r:r+"",void 0,G);var X=(r,t)=>{if(N)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),i=H.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,r.appendChild(s)}},L=N?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return Q(e)})(r):r;var{is:_t,defineProperty:ft,getOwnPropertyDescriptor:mt,getOwnPropertyNames:At,getOwnPropertySymbols:gt,getPrototypeOf:yt}=Object,T=globalThis,Y=T.trustedTypes,vt=Y?Y.emptyScript:"",St=T.reactiveElementPolyfillSupport,b=(r,t)=>r,k={toAttribute(r,t){switch(t){case Boolean:r=r?vt:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},et=(r,t)=>!_t(r,t),tt={attribute:!0,type:String,converter:k,reflect:!1,useDefault:!1,hasChanged:et};Symbol.metadata??=Symbol("metadata"),T.litPropertyMetadata??=new WeakMap;var $=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=tt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&ft(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){let{get:i,set:o}=mt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:i,set(n){let l=i?.call(this);o?.call(this,n),this.requestUpdate(t,l,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??tt}static _$Ei(){if(this.hasOwnProperty(b("elementProperties")))return;let t=yt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(b("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(b("properties"))){let e=this.properties,s=[...At(e),...gt(e)];for(let i of s)this.createProperty(i,e[i])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let i of s)e.unshift(L(i))}else t!==void 0&&e.push(L(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return X(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:k).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){let o=s.getPropertyOptions(i),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:k;this._$Em=i;let l=n.fromAttribute(e,o.type);this[i]=l??this._$Ej?.get(i)??l,this._$Em=null}}requestUpdate(t,e,s,i=!1,o){if(t!==void 0){let n=this.constructor;if(i===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??et)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[i,o]of s){let{wrapped:n}=o,l=this[i];n!==!0||this._$AL.has(i)||l===void 0||this.C(i,void 0,o,l)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};$.elementStyles=[],$.shadowRootOptions={mode:"open"},$[b("elementProperties")]=new Map,$[b("finalized")]=new Map,St?.({ReactiveElement:$}),(T.reactiveElementVersions??=[]).push("2.1.2");var W=globalThis,st=r=>r,R=W.trustedTypes,it=R?R.createPolicy("lit-html",{createHTML:r=>r}):void 0,lt="$lit$",f=`lit$${Math.random().toFixed(9).slice(2)}$`,ct="?"+f,Et=`<${ct}>`,y=document,w=()=>y.createComment(""),x=r=>r===null||typeof r!="object"&&typeof r!="function",q=Array.isArray,bt=r=>q(r)||typeof r?.[Symbol.iterator]=="function",D=`[ -\f\r]`,C=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,rt=/-->/g,ot=/>/g,A=RegExp(`>|${D}(?:([^\\s"'>=/]+)(${D}*=${D}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),nt=/'/g,ht=/"/g,dt=/^(?:script|style|textarea|title)$/i,K=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),pt=K(1),Nt=K(2),Tt=K(3),v=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),at=new WeakMap,g=y.createTreeWalker(y,129);function ut(r,t){if(!q(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return it!==void 0?it.createHTML(t):t}var Ct=(r,t)=>{let e=r.length-1,s=[],i,o=t===2?"":t===3?"":"",n=C;for(let l=0;l"?(n=i??C,a=-1):p[1]===void 0?a=-2:(a=n.lastIndex-p[2].length,c=p[1],n=p[3]===void 0?A:p[3]==='"'?ht:nt):n===ht||n===nt?n=A:n===rt||n===ot?n=C:(n=A,i=void 0);let _=n===A&&r[l+1].startsWith("/>")?" ":"";o+=n===C?h+Et:a>=0?(s.push(c),h.slice(0,a)+lt+h.slice(a)+f+_):h+f+(a===-2?l:_)}return[ut(r,o+(r[e]||"")+(t===2?"":t===3?"":"")),s]},P=class r{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let o=0,n=0,l=t.length-1,h=this.parts,[c,p]=Ct(t,e);if(this.el=r.createElement(c,s),g.currentNode=this.el.content,e===2||e===3){let a=this.el.content.firstChild;a.replaceWith(...a.childNodes)}for(;(i=g.nextNode())!==null&&h.length0){i.textContent=R?R.emptyScript:"";for(let _=0;_2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){let o=this.strings,n=!1;if(o===void 0)t=S(this,t,e,0),n=!x(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{let l=t,h,c;for(t=o[0],h=0;h{let s=e?.renderBefore??t,i=s._$litPart$;if(i===void 0){let o=e?.renderBefore??null;s._$litPart$=i=new U(t.insertBefore(w(),o),o,void 0,e??{})}return i._$AI(r),i};var F=globalThis,m=class extends ${constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=$t(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return v}};m._$litElement$=!0,m.finalized=!0,F.litElementHydrateSupport?.({LitElement:m});var xt=F.litElementPolyfillSupport;xt?.({LitElement:m});(F.litElementVersions??=[]).push("4.2.2");var J=class extends m{render(){return pt`

camel-route-diagram loading…

`}};customElements.define("camel-route-diagram",J);export{J as CamelRouteDiagram}; +var M=globalThis,U=M.ShadowRoot&&(M.ShadyCSS===void 0||M.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,L=Symbol(),it=new WeakMap,S=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==L)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(U&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=it.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&it.set(e,t))}return t}toString(){return this.cssText}},rt=r=>new S(typeof r=="string"?r:r+"",void 0,L),B=(r,...t)=>{let e=r.length===1?r[0]:t.reduce((s,i,o)=>s+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[o+1],r[0]);return new S(e,r,L)},ot=(r,t)=>{if(U)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),i=M.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,r.appendChild(s)}},z=U?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return rt(e)})(r):r;var{is:St,defineProperty:xt,getOwnPropertyDescriptor:wt,getOwnPropertyNames:Ct,getOwnPropertySymbols:Pt,getPrototypeOf:Nt}=Object,T=globalThis,nt=T.trustedTypes,Ot=nt?nt.emptyScript:"",Dt=T.reactiveElementPolyfillSupport,x=(r,t)=>r,j={toAttribute(r,t){switch(t){case Boolean:r=r?Ot:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},ht=(r,t)=>!St(r,t),at={attribute:!0,type:String,converter:j,reflect:!1,useDefault:!1,hasChanged:ht};Symbol.metadata??=Symbol("metadata"),T.litPropertyMetadata??=new WeakMap;var f=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=at){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&xt(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){let{get:i,set:o}=wt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:i,set(n){let h=i?.call(this);o?.call(this,n),this.requestUpdate(t,h,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??at}static _$Ei(){if(this.hasOwnProperty(x("elementProperties")))return;let t=Nt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(x("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(x("properties"))){let e=this.properties,s=[...Ct(e),...Pt(e)];for(let i of s)this.createProperty(i,e[i])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let i of s)e.unshift(z(i))}else t!==void 0&&e.push(z(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ot(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:j).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){let o=s.getPropertyOptions(i),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:j;this._$Em=i;let h=n.fromAttribute(e,o.type);this[i]=h??this._$Ej?.get(i)??h,this._$Em=null}}requestUpdate(t,e,s,i=!1,o){if(t!==void 0){let n=this.constructor;if(i===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??ht)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[i,o]of s){let{wrapped:n}=o,h=this[i];n!==!0||this._$AL.has(i)||h===void 0||this.C(i,void 0,o,h)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};f.elementStyles=[],f.shadowRootOptions={mode:"open"},f[x("elementProperties")]=new Map,f[x("finalized")]=new Map,Dt?.({ReactiveElement:f}),(T.reactiveElementVersions??=[]).push("2.1.2");var X=globalThis,lt=r=>r,k=X.trustedTypes,ct=k?k.createPolicy("lit-html",{createHTML:r=>r}):void 0,_t="$lit$",_=`lit$${Math.random().toFixed(9).slice(2)}$`,mt="?"+_,Ht=`<${mt}>`,A=document,C=()=>A.createComment(""),P=r=>r===null||typeof r!="object"&&typeof r!="function",J=Array.isArray,Mt=r=>J(r)||typeof r?.[Symbol.iterator]=="function",G=`[ +\f\r]`,w=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,dt=/-->/g,pt=/>/g,g=RegExp(`>|${G}(?:([^\\s"'>=/]+)(${G}*=${G}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),ut=/'/g,ft=/"/g,gt=/^(?:script|style|textarea|title)$/i,Z=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),D=Z(1),R=Z(2),qt=Z(3),v=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),$t=new WeakMap,y=A.createTreeWalker(A,129);function yt(r,t){if(!J(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return ct!==void 0?ct.createHTML(t):t}var Ut=(r,t)=>{let e=r.length-1,s=[],i,o=t===2?"":t===3?"":"",n=w;for(let h=0;h"?(n=i??w,c=-1):p[1]===void 0?c=-2:(c=n.lastIndex-p[2].length,l=p[1],n=p[3]===void 0?g:p[3]==='"'?ft:ut):n===ft||n===ut?n=g:n===dt||n===pt?n=w:(n=g,i=void 0);let $=n===g&&r[h+1].startsWith("/>")?" ":"";o+=n===w?a+Ht:c>=0?(s.push(l),a.slice(0,c)+_t+a.slice(c)+_+$):a+_+(c===-2?h:$)}return[yt(r,o+(r[e]||"")+(t===2?"":t===3?"":"")),s]},N=class r{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let o=0,n=0,h=t.length-1,a=this.parts,[l,p]=Ut(t,e);if(this.el=r.createElement(l,s),y.currentNode=this.el.content,e===2||e===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(i=y.nextNode())!==null&&a.length0){i.textContent=k?k.emptyScript:"";for(let $=0;$2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){let o=this.strings,n=!1;if(o===void 0)t=b(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{let h=t,a,l;for(t=o[0],a=0;a{let s=e?.renderBefore??t,i=s._$litPart$;if(i===void 0){let o=e?.renderBefore??null;s._$litPart$=i=new O(t.insertBefore(C(),o),o,void 0,e??{})}return i._$AI(r),i};var Q=globalThis,m=class extends f{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=At(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return v}};m._$litElement$=!0,m.finalized=!0,Q.litElementHydrateSupport?.({LitElement:m});var kt=Q.litElementPolyfillSupport;kt?.({LitElement:m});(Q.litElementVersions??=[]).push("4.2.2");var vt=new Set(["choice","multicast","doTry","loadBalance","recipientList","circuitBreaker"]);function Rt(r){if(!r.length)return null;let t={info:r[0],children:[],parent:null,subtreeWidth:0},e=t;for(let s=1;se.info.level)e.children.push(o),o.parent=e;else if(i.level===e.info.level){let n=e.parent??t;n.children.push(o),o.parent=n}else{let n=e.parent;for(;n&&n.info.level>=i.level;)n=n.parent;let h=n??t;h.children.push(o),o.parent=h}e=o}return t}function tt(r){if(!r.children.length)return r.subtreeWidth=180,180;if(vt.has(r.info.type)){let t=0;r.children.forEach((e,s)=>{s>0&&(t+=90),t+=tt(e)}),r.subtreeWidth=Math.max(180,t)}else r.subtreeWidth=Math.max(180,...r.children.map(tt));return r.subtreeWidth}function et(r,t,e,s,i){let o=Math.max(r.subtreeWidth,s),n=t+(o-180)/2;if(i[r.info.id]={x:n,y:e,w:180,h:36,parentId:r.parent?r.parent.info.id:null,type:r.info.type,code:r.info.code,description:r.info.description??null,uri:r.info.uri??null,statistics:r.info.statistics??null},!r.children.length)return;let h=e+36+40;if(vt.has(r.info.type)){let a=t+(o-r.subtreeWidth)/2;for(let l of r.children)et(l,a,h,l.subtreeWidth,i),a+=l.subtreeWidth+90}else{let a=h;for(let l of r.children)et(l,t,a,o,i),a=bt(l,i)+40}}function bt(r,t){let e=t[r.info.id],s=e?e.y+e.h:0;for(let i of r.children)s=Math.max(s,bt(i,t));return s}function Et(r){let t=r.code??[];if(!t.length)return{positions:{},width:180+30*2,height:36+30*2};let e=Rt(t);tt(e);let s={};et(e,30,30,e.subtreeWidth,s);let i=0,o=0;for(let n of Object.values(s))i=Math.max(i,n.x+n.w),o=Math.max(o,n.y+n.h);return{positions:s,width:i+30,height:o+30}}var Wt={route:"var(--crd-color-route, #6366f1)",from:"var(--crd-color-from, #0ea5e9)",to:"var(--crd-color-to, #0ea5e9)",log:"var(--crd-color-log, #64748b)",choice:"var(--crd-color-choice, #f59e0b)",when:"var(--crd-color-when, #fbbf24)",otherwise:"var(--crd-color-otherwise, #fbbf24)",doTry:"var(--crd-color-doTry, #f59e0b)",doCatch:"var(--crd-color-doCatch, #fbbf24)",doFinally:"var(--crd-color-doFinally, #fbbf24)",multicast:"var(--crd-color-multicast, #8b5cf6)",circuitBreaker:"var(--crd-color-circuitBreaker, #ef4444)"};function It(r){return Wt[r]??"var(--crd-color-default, #6366f1)"}function Lt(r,t=28){if(!r)return"";let e=r.replace(/^\.+/,"");return e.length>t?e.slice(0,t-1)+"\u2026":e}function Bt(r){if(!r)return null;let t=r.exchangesTotal??0,e=r.exchangesFailed??0;return`\u2713${t} \u2717${e}`}var st=class extends m{static properties={src:{type:String},refresh:{type:Number},filter:{type:String},_data:{state:!0},_error:{state:!0}};static styles=B` + :host { + display: block; + font-family: var(--crd-font, system-ui, sans-serif); + font-size: var(--crd-font-size, 12px); + background: var(--crd-bg, transparent); + color: var(--crd-fg, #1e293b); + } + @media (prefers-color-scheme: dark) { + :host { color: var(--crd-fg, #e2e8f0); } + } + .error { color: #ef4444; padding: 8px; } + .loading { opacity: .6; padding: 8px; } + .route-label { + font-weight: 600; + font-size: 0.9em; + padding: 4px 0 2px 0; + opacity: .8; + } + svg { display: block; overflow: visible; } + `;#t=null;#s=Math.random().toString(36).slice(2);constructor(){super(),this.src="",this.refresh=0,this.filter="",this._data=null,this._error=null}connectedCallback(){super.connectedCallback(),this.#e(),this.refresh>0&&(this.#t=setInterval(()=>this.#e(),this.refresh))}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this.#t),this.#t=null}async#e(){if(this.src)try{let t=new URL(this.src,location.href);this.filter&&t.searchParams.set("filter",this.filter),t.searchParams.set("metric","true");let e=await fetch(t);if(!e.ok)throw new Error(`HTTP ${e.status} ${e.statusText}`);this._data=await e.json(),this._error=null}catch(t){this._error=t.message}}render(){if(this._error)return D`

⚠ ${this._error}

`;if(!this._data)return D`

Loading diagram…

`;let t=`arrow-${this.#s}`;return D` + + + + + + + + ${(this._data.routes??[]).map(e=>this.#i(e,t))} + `}#i(t,e){let{positions:s,width:i,height:o}=Et(t),n=Object.keys(s);return D` +
${t.routeId}
+ + ${n.map(h=>this.#r(h,s,e))} + ${n.map(h=>this.#o(h,s[h]))} + + `}#r(t,e,s){let i=e[t];if(!i.parentId)return d;let o=e[i.parentId];if(!o)return d;let n=o.x+180/2,h=o.y+36,a=i.x+180/2,l=i.y,p=(h+l)/2;return R``}#o(t,e){let s=Lt(e.description??e.code),i=Bt(e.statistics),o=It(e.type),n=e.x+180/2,h=e.y+36/2+4;return R` + + + + ${s} + + ${i?R` + + ${i} + `:d} + `}};customElements.define("camel-route-diagram",st); /*! Bundled license information: @lit/reactive-element/css-tag.js: diff --git a/components/camel-diagram/src/test/resources/smoke-test.html b/components/camel-diagram/src/test/resources/smoke-test.html new file mode 100644 index 0000000000000..13291ba599bd6 --- /dev/null +++ b/components/camel-diagram/src/test/resources/smoke-test.html @@ -0,0 +1,64 @@ + + + + + + camel-route-diagram smoke test + + + + + +

camel-route-diagram smoke test

+ +
+

Light mode (auto)

+ +
+ +
+

Dark mode (forced via CSS override)

+ + +
+ + From cd7b4a9a397f29cec7a2cf7e33817362560d7ec3 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:14:50 -0400 Subject: [PATCH 04/18] CAMEL-23636: fix duplicate license header in smoke-test.html Co-Authored-By: Claude Sonnet 4.6 --- .../src/test/resources/smoke-test.html | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/components/camel-diagram/src/test/resources/smoke-test.html b/components/camel-diagram/src/test/resources/smoke-test.html index 13291ba599bd6..4aaee62f75a1a 100644 --- a/components/camel-diagram/src/test/resources/smoke-test.html +++ b/components/camel-diagram/src/test/resources/smoke-test.html @@ -1,20 +1,22 @@ - + From a159549d202f7ebd7113c48428d7f897c404ec5f Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:15:46 -0400 Subject: [PATCH 05/18] CAMEL-23636: document camel-route-diagram web component Co-Authored-By: Claude Sonnet 4.6 --- .../camel-diagram/src/main/docs/diagram.adoc | 65 +++++++++++++++++++ .../pages/camel-4x-upgrade-guide-4_21.adoc | 15 +++++ 2 files changed, 80 insertions(+) diff --git a/components/camel-diagram/src/main/docs/diagram.adoc b/components/camel-diagram/src/main/docs/diagram.adoc index f624170dfa069..df9fd3c31e266 100644 --- a/components/camel-diagram/src/main/docs/diagram.adoc +++ b/components/camel-diagram/src/main/docs/diagram.adoc @@ -276,3 +276,68 @@ String diagram = renderer.renderDiagramAnsi(layoutRoutes, totalHeight, highlight RouteDiagramRenderer pngRenderer = new RouteDiagramRenderer(nodeWidth, fontSize); BufferedImage image = pngRenderer.renderDiagram(layoutRoutes, totalHeight, colors, highlightedNodes, style); ---- + +== Embeddable Web Component + +`camel-diagram` ships a lightweight `` web component that renders +interactive route diagrams as SVG directly in the browser. +Any application with `camel-diagram` on the classpath automatically serves the component +as a static resource — no extra server configuration needed. + +=== Usage + +Include the bundled script served from `META-INF/resources/camel/diagram/camel-route-diagram.js` +(automatically exposed by Servlet 3 containers and Quarkus/Spring Boot static-resource mechanisms): + +[source,html] +---- + + + + +---- + +The `src` attribute must point to an endpoint returning the `route-structure` dev console JSON +(for example the Quarkus Dev UI endpoint `/q/dev/route-structure`). +The component automatically appends `?metric=true` so that per-processor exchange statistics +are included in the diagram. + +=== Attributes + +[width="100%",cols="2,5,2",options="header"] +|=== +| Attribute | Description | Default +| `src` | URL to fetch the route-structure JSON from (required) | — +| `refresh` | Polling interval in milliseconds; `0` disables polling | `0` +| `filter` | Route ID filter, forwarded as `?filter=` query parameter | (all routes) +|=== + +=== Theming + +The component is theme-agnostic. +It respects `prefers-color-scheme` automatically for dark/light mode, +and exposes CSS custom properties so the host application can override every visual aspect: + +[source,css] +---- +camel-route-diagram { + --crd-bg: #ffffff; /* canvas background */ + --crd-fg: #1e293b; /* text colour */ + --crd-edge: #94a3b8; /* edge/arrow colour */ + --crd-stat: #64748b; /* metric overlay text */ + --crd-font: system-ui; /* font family */ + --crd-font-size: 12px; /* base font size */ + --crd-color-from: #0ea5e9; /* "from" node */ + --crd-color-to: #0ea5e9; /* "to" node */ + --crd-color-log: #64748b; /* "log" node */ + --crd-color-choice: #f59e0b; /* "choice" node */ + --crd-color-when: #fbbf24; /* "when" branch */ + --crd-color-otherwise: #fbbf24; /* "otherwise" branch */ + --crd-color-multicast: #8b5cf6; /* "multicast" node */ + --crd-color-circuitBreaker: #ef4444; /* "circuitBreaker" node */ + --crd-color-default: #6366f1; /* all other EIP nodes */ +} +---- diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc index cc9ef69d2372d..f7fab2a6e9149 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc @@ -2496,3 +2496,18 @@ and do not rely on a custom verifier can opt in to `BOTH`. The `CLIENT` policy is a deliberate choice: it preserves backward compatibility and allows `NoopHostnameVerifier` to work as documented. A future release may add an option to opt into the `BOTH` policy for defense-in-depth. + +=== camel-diagram: Embeddable web component + +A new `` web component is now bundled inside `camel-diagram.jar` +at `META-INF/resources/camel/diagram/camel-route-diagram.js`. +Any Servlet 3 container (including Quarkus and Spring Boot) serving the JAR automatically +exposes the script as a static resource. + +The component consumes the existing `route-structure` dev console JSON endpoint and renders +routes as interactive SVG diagrams with optional per-processor metric overlays and configurable +periodic refresh. +It is theme-agnostic, respects `prefers-color-scheme` for automatic dark/light mode, and +exposes CSS custom properties for full visual control. + +See xref:components::diagram-component.adoc[Diagram] for usage instructions and theming options. From aea23ac93be0cadccb43be43ccac62ff20cac41d Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:23:35 -0400 Subject: [PATCH 06/18] CAMEL-23636: fix dark-mode background and Safari file:// loading in smoke-test Dark mode media query now sets background (#0f172a) alongside text color so light text is never rendered on a transparent/white host. The smoke-test switches to an inline module import to avoid the file:// CORS restriction that blocks external + + @@ -52,14 +68,21 @@

camel-route-diagram smoke test

-

Light mode (auto)

+

Auto theme (follows OS color scheme — dark on dark OS, light on light OS)

-

Dark mode (forced via CSS override)

+

Light theme (forced)

+ + +
+ +
+

Dark theme (forced)

+ style="--crd-bg:#0f172a; --crd-fg:#e2e8f0; --crd-edge:#475569;">
From b7bbe33e6154e078ba49c0fb039d9572e9cb4ab1 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:28:50 -0400 Subject: [PATCH 07/18] =?UTF-8?q?CAMEL-23636:=20fix=20smoke-test=20server?= =?UTF-8?q?=20instructions=20=E2=80=94=20serve=20from=20src/=20not=20src/t?= =?UTF-8?q?est/resources/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relative import ../../main/resources/... goes two levels up from the HTML file's location. When Python serves from src/test/resources/ the path escapes the server root and 404s. Serving from src/ makes the path resolve correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../camel-diagram/src/test/resources/smoke-test.html | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/camel-diagram/src/test/resources/smoke-test.html b/components/camel-diagram/src/test/resources/smoke-test.html index cc664918e1426..e581ef23e9221 100644 --- a/components/camel-diagram/src/test/resources/smoke-test.html +++ b/components/camel-diagram/src/test/resources/smoke-test.html @@ -20,10 +20,15 @@ From f2a0f48ce44f9de0643d9b0b02b385d23ffd705b Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:34:44 -0400 Subject: [PATCH 08/18] CAMEL-23636: fix edges passing through intermediate nodes in linear chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a chain [route → from → log → choice], all three were children of route in the tree, so three edges originated from the route box — the edge to log passed through from, the edge to choice passed through from and log. Added visualParentId() and lastChainId() to port RouteDiagramLayoutEngine's findLastLayoutNode() logic: for non-first children of a linear parent the edge now originates from the last node of the previous sibling's chain. Co-Authored-By: Claude Sonnet 4.6 --- .../src/main/frontend/src/layout.js | 35 ++++++++++++++++++- .../src/main/frontend/test/layout.test.js | 18 ++++++++++ .../camel/diagram/camel-route-diagram.js | 20 +++++------ 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/components/camel-diagram/src/main/frontend/src/layout.js b/components/camel-diagram/src/main/frontend/src/layout.js index d79b77ce63a40..912d0f36c4be5 100644 --- a/components/camel-diagram/src/main/frontend/src/layout.js +++ b/components/camel-diagram/src/main/frontend/src/layout.js @@ -88,6 +88,39 @@ export function computeSubtreeWidth(node) { return node.subtreeWidth; } +/** + * Returns the id of the node that an edge should visually originate from when + * drawing a connection TO `node`. For non-first children of a linear (non-branching) + * parent this is the last node in the previous sibling's chain — not the shared + * tree parent — so edges don't pass through intermediate nodes. + * Ports RouteDiagramLayoutEngine.findLastLayoutNode(). + */ +function visualParentId(node) { + if (!node.parent) return null; + const parent = node.parent; + if (BRANCHING_EIPS.has(parent.info.type)) { + // All branches of a branching EIP connect from the branching node. + return parent.info.id; + } + const idx = parent.children.indexOf(node); + if (idx === 0) { + return parent.info.id; + } + // Connect from the last node in the previous sibling's subtree chain. + return lastChainId(parent.children[idx - 1]); +} + +/** + * Traverses the rightmost (last) chain of a subtree and returns its leaf id. + * Stops at branching EIPs (they have no single continuation point). + */ +function lastChainId(node) { + if (BRANCHING_EIPS.has(node.info.type) || !node.children.length) { + return node.info.id; + } + return lastChainId(node.children[node.children.length - 1]); +} + /** * Walks the tree and populates positions[id] with {x, y, w, h, parentId, type, code, ...}. * x, y are the top-left corner of the node box in SVG logical pixels. @@ -107,7 +140,7 @@ export function assignPositions(node, x, y, parentWidth, positions) { y, w: NODE_W, h: NODE_H, - parentId: node.parent ? node.parent.info.id : null, + parentId: visualParentId(node), type: node.info.type, code: node.info.code, description: node.info.description ?? null, diff --git a/components/camel-diagram/src/main/frontend/test/layout.test.js b/components/camel-diagram/src/main/frontend/test/layout.test.js index 0294ecc379ba7..37e3fd3998cf6 100644 --- a/components/camel-diagram/src/main/frontend/test/layout.test.js +++ b/components/camel-diagram/src/main/frontend/test/layout.test.js @@ -78,6 +78,24 @@ describe('computeSubtreeWidth', () => { }); describe('assignPositions', () => { + it('linear chain: each node connects to its visual predecessor, not the tree root', () => { + const nodes = [ + node('route', 'r1', 0), + node('from', 'f1', 1), + node('log', 'l1', 1), + node('to', 't1', 1), + ]; + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + const positions = {}; + assignPositions(tree, 0, 0, tree.subtreeWidth, positions); + + expect(positions['r1'].parentId).toBeNull(); + expect(positions['f1'].parentId).toBe('r1'); + expect(positions['l1'].parentId).toBe('f1'); // NOT 'r1' + expect(positions['t1'].parentId).toBe('l1'); // NOT 'r1' + }); + it('single-chain route assigns increasing y values', () => { const nodes = [ node('route', 'r1', 0), diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index d8465177f2847..326d0ab589d46 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -1,6 +1,6 @@ -var M=globalThis,U=M.ShadowRoot&&(M.ShadyCSS===void 0||M.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,L=Symbol(),it=new WeakMap,S=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==L)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(U&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=it.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&it.set(e,t))}return t}toString(){return this.cssText}},rt=r=>new S(typeof r=="string"?r:r+"",void 0,L),B=(r,...t)=>{let e=r.length===1?r[0]:t.reduce((s,i,o)=>s+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[o+1],r[0]);return new S(e,r,L)},ot=(r,t)=>{if(U)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),i=M.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,r.appendChild(s)}},z=U?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return rt(e)})(r):r;var{is:St,defineProperty:xt,getOwnPropertyDescriptor:wt,getOwnPropertyNames:Ct,getOwnPropertySymbols:Pt,getPrototypeOf:Nt}=Object,T=globalThis,nt=T.trustedTypes,Ot=nt?nt.emptyScript:"",Dt=T.reactiveElementPolyfillSupport,x=(r,t)=>r,j={toAttribute(r,t){switch(t){case Boolean:r=r?Ot:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},ht=(r,t)=>!St(r,t),at={attribute:!0,type:String,converter:j,reflect:!1,useDefault:!1,hasChanged:ht};Symbol.metadata??=Symbol("metadata"),T.litPropertyMetadata??=new WeakMap;var f=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=at){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&xt(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){let{get:i,set:o}=wt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:i,set(n){let h=i?.call(this);o?.call(this,n),this.requestUpdate(t,h,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??at}static _$Ei(){if(this.hasOwnProperty(x("elementProperties")))return;let t=Nt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(x("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(x("properties"))){let e=this.properties,s=[...Ct(e),...Pt(e)];for(let i of s)this.createProperty(i,e[i])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let i of s)e.unshift(z(i))}else t!==void 0&&e.push(z(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ot(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:j).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){let o=s.getPropertyOptions(i),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:j;this._$Em=i;let h=n.fromAttribute(e,o.type);this[i]=h??this._$Ej?.get(i)??h,this._$Em=null}}requestUpdate(t,e,s,i=!1,o){if(t!==void 0){let n=this.constructor;if(i===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??ht)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[i,o]of s){let{wrapped:n}=o,h=this[i];n!==!0||this._$AL.has(i)||h===void 0||this.C(i,void 0,o,h)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};f.elementStyles=[],f.shadowRootOptions={mode:"open"},f[x("elementProperties")]=new Map,f[x("finalized")]=new Map,Dt?.({ReactiveElement:f}),(T.reactiveElementVersions??=[]).push("2.1.2");var X=globalThis,lt=r=>r,k=X.trustedTypes,ct=k?k.createPolicy("lit-html",{createHTML:r=>r}):void 0,_t="$lit$",_=`lit$${Math.random().toFixed(9).slice(2)}$`,mt="?"+_,Ht=`<${mt}>`,A=document,C=()=>A.createComment(""),P=r=>r===null||typeof r!="object"&&typeof r!="function",J=Array.isArray,Mt=r=>J(r)||typeof r?.[Symbol.iterator]=="function",G=`[ -\f\r]`,w=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,dt=/-->/g,pt=/>/g,g=RegExp(`>|${G}(?:([^\\s"'>=/]+)(${G}*=${G}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),ut=/'/g,ft=/"/g,gt=/^(?:script|style|textarea|title)$/i,Z=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),D=Z(1),R=Z(2),qt=Z(3),v=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),$t=new WeakMap,y=A.createTreeWalker(A,129);function yt(r,t){if(!J(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return ct!==void 0?ct.createHTML(t):t}var Ut=(r,t)=>{let e=r.length-1,s=[],i,o=t===2?"":t===3?"":"",n=w;for(let h=0;h"?(n=i??w,c=-1):p[1]===void 0?c=-2:(c=n.lastIndex-p[2].length,l=p[1],n=p[3]===void 0?g:p[3]==='"'?ft:ut):n===ft||n===ut?n=g:n===dt||n===pt?n=w:(n=g,i=void 0);let $=n===g&&r[h+1].startsWith("/>")?" ":"";o+=n===w?a+Ht:c>=0?(s.push(l),a.slice(0,c)+_t+a.slice(c)+_+$):a+_+(c===-2?h:$)}return[yt(r,o+(r[e]||"")+(t===2?"":t===3?"":"")),s]},N=class r{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let o=0,n=0,h=t.length-1,a=this.parts,[l,p]=Ut(t,e);if(this.el=r.createElement(l,s),y.currentNode=this.el.content,e===2||e===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(i=y.nextNode())!==null&&a.length0){i.textContent=k?k.emptyScript:"";for(let $=0;$2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){let o=this.strings,n=!1;if(o===void 0)t=b(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{let h=t,a,l;for(t=o[0],a=0;a{let s=e?.renderBefore??t,i=s._$litPart$;if(i===void 0){let o=e?.renderBefore??null;s._$litPart$=i=new O(t.insertBefore(C(),o),o,void 0,e??{})}return i._$AI(r),i};var Q=globalThis,m=class extends f{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=At(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return v}};m._$litElement$=!0,m.finalized=!0,Q.litElementHydrateSupport?.({LitElement:m});var kt=Q.litElementPolyfillSupport;kt?.({LitElement:m});(Q.litElementVersions??=[]).push("4.2.2");var vt=new Set(["choice","multicast","doTry","loadBalance","recipientList","circuitBreaker"]);function Rt(r){if(!r.length)return null;let t={info:r[0],children:[],parent:null,subtreeWidth:0},e=t;for(let s=1;se.info.level)e.children.push(o),o.parent=e;else if(i.level===e.info.level){let n=e.parent??t;n.children.push(o),o.parent=n}else{let n=e.parent;for(;n&&n.info.level>=i.level;)n=n.parent;let h=n??t;h.children.push(o),o.parent=h}e=o}return t}function tt(r){if(!r.children.length)return r.subtreeWidth=180,180;if(vt.has(r.info.type)){let t=0;r.children.forEach((e,s)=>{s>0&&(t+=90),t+=tt(e)}),r.subtreeWidth=Math.max(180,t)}else r.subtreeWidth=Math.max(180,...r.children.map(tt));return r.subtreeWidth}function et(r,t,e,s,i){let o=Math.max(r.subtreeWidth,s),n=t+(o-180)/2;if(i[r.info.id]={x:n,y:e,w:180,h:36,parentId:r.parent?r.parent.info.id:null,type:r.info.type,code:r.info.code,description:r.info.description??null,uri:r.info.uri??null,statistics:r.info.statistics??null},!r.children.length)return;let h=e+36+40;if(vt.has(r.info.type)){let a=t+(o-r.subtreeWidth)/2;for(let l of r.children)et(l,a,h,l.subtreeWidth,i),a+=l.subtreeWidth+90}else{let a=h;for(let l of r.children)et(l,t,a,o,i),a=bt(l,i)+40}}function bt(r,t){let e=t[r.info.id],s=e?e.y+e.h:0;for(let i of r.children)s=Math.max(s,bt(i,t));return s}function Et(r){let t=r.code??[];if(!t.length)return{positions:{},width:180+30*2,height:36+30*2};let e=Rt(t);tt(e);let s={};et(e,30,30,e.subtreeWidth,s);let i=0,o=0;for(let n of Object.values(s))i=Math.max(i,n.x+n.w),o=Math.max(o,n.y+n.h);return{positions:s,width:i+30,height:o+30}}var Wt={route:"var(--crd-color-route, #6366f1)",from:"var(--crd-color-from, #0ea5e9)",to:"var(--crd-color-to, #0ea5e9)",log:"var(--crd-color-log, #64748b)",choice:"var(--crd-color-choice, #f59e0b)",when:"var(--crd-color-when, #fbbf24)",otherwise:"var(--crd-color-otherwise, #fbbf24)",doTry:"var(--crd-color-doTry, #f59e0b)",doCatch:"var(--crd-color-doCatch, #fbbf24)",doFinally:"var(--crd-color-doFinally, #fbbf24)",multicast:"var(--crd-color-multicast, #8b5cf6)",circuitBreaker:"var(--crd-color-circuitBreaker, #ef4444)"};function It(r){return Wt[r]??"var(--crd-color-default, #6366f1)"}function Lt(r,t=28){if(!r)return"";let e=r.replace(/^\.+/,"");return e.length>t?e.slice(0,t-1)+"\u2026":e}function Bt(r){if(!r)return null;let t=r.exchangesTotal??0,e=r.exchangesFailed??0;return`\u2713${t} \u2717${e}`}var st=class extends m{static properties={src:{type:String},refresh:{type:Number},filter:{type:String},_data:{state:!0},_error:{state:!0}};static styles=B` +var M=globalThis,U=M.ShadowRoot&&(M.ShadyCSS===void 0||M.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,B=Symbol(),rt=new WeakMap,S=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==B)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(U&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=rt.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&rt.set(e,t))}return t}toString(){return this.cssText}},ot=i=>new S(typeof i=="string"?i:i+"",void 0,B),z=(i,...t)=>{let e=i.length===1?i[0]:t.reduce((s,r,o)=>s+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+i[o+1],i[0]);return new S(e,i,B)},nt=(i,t)=>{if(U)i.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),r=M.litNonce;r!==void 0&&s.setAttribute("nonce",r),s.textContent=e.cssText,i.appendChild(s)}},j=U?i=>i:i=>i instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return ot(e)})(i):i;var{is:xt,defineProperty:wt,getOwnPropertyDescriptor:Ct,getOwnPropertyNames:Pt,getOwnPropertySymbols:Nt,getPrototypeOf:Ot}=Object,T=globalThis,at=T.trustedTypes,Dt=at?at.emptyScript:"",Ht=T.reactiveElementPolyfillSupport,x=(i,t)=>i,G={toAttribute(i,t){switch(t){case Boolean:i=i?Dt:null;break;case Object:case Array:i=i==null?i:JSON.stringify(i)}return i},fromAttribute(i,t){let e=i;switch(t){case Boolean:e=i!==null;break;case Number:e=i===null?null:Number(i);break;case Object:case Array:try{e=JSON.parse(i)}catch{e=null}}return e}},lt=(i,t)=>!xt(i,t),ht={attribute:!0,type:String,converter:G,reflect:!1,useDefault:!1,hasChanged:lt};Symbol.metadata??=Symbol("metadata"),T.litPropertyMetadata??=new WeakMap;var f=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=ht){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),r=this.getPropertyDescriptor(t,s,e);r!==void 0&&wt(this.prototype,t,r)}}static getPropertyDescriptor(t,e,s){let{get:r,set:o}=Ct(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:r,set(n){let h=r?.call(this);o?.call(this,n),this.requestUpdate(t,h,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??ht}static _$Ei(){if(this.hasOwnProperty(x("elementProperties")))return;let t=Ot(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(x("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(x("properties"))){let e=this.properties,s=[...Pt(e),...Nt(e)];for(let r of s)this.createProperty(r,e[r])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,r]of e)this.elementProperties.set(s,r)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let r=this._$Eu(e,s);r!==void 0&&this._$Eh.set(r,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let r of s)e.unshift(j(r))}else t!==void 0&&e.push(j(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return nt(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),r=this.constructor._$Eu(t,s);if(r!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:G).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(r):this.setAttribute(r,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,r=s._$Eh.get(t);if(r!==void 0&&this._$Em!==r){let o=s.getPropertyOptions(r),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:G;this._$Em=r;let h=n.fromAttribute(e,o.type);this[r]=h??this._$Ej?.get(r)??h,this._$Em=null}}requestUpdate(t,e,s,r=!1,o){if(t!==void 0){let n=this.constructor;if(r===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??lt)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:r,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),r===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[r,o]of this._$Ep)this[r]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[r,o]of s){let{wrapped:n}=o,h=this[r];n!==!0||this._$AL.has(r)||h===void 0||this.C(r,void 0,o,h)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};f.elementStyles=[],f.shadowRootOptions={mode:"open"},f[x("elementProperties")]=new Map,f[x("finalized")]=new Map,Ht?.({ReactiveElement:f}),(T.reactiveElementVersions??=[]).push("2.1.2");var J=globalThis,ct=i=>i,k=J.trustedTypes,dt=k?k.createPolicy("lit-html",{createHTML:i=>i}):void 0,mt="$lit$",_=`lit$${Math.random().toFixed(9).slice(2)}$`,gt="?"+_,Mt=`<${gt}>`,A=document,C=()=>A.createComment(""),P=i=>i===null||typeof i!="object"&&typeof i!="function",Z=Array.isArray,Ut=i=>Z(i)||typeof i?.[Symbol.iterator]=="function",V=`[ +\f\r]`,w=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,pt=/-->/g,ut=/>/g,g=RegExp(`>|${V}(?:([^\\s"'>=/]+)(${V}*=${V}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),ft=/'/g,$t=/"/g,yt=/^(?:script|style|textarea|title)$/i,Q=i=>(t,...e)=>({_$litType$:i,strings:t,values:e}),D=Q(1),R=Q(2),Ft=Q(3),v=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),_t=new WeakMap,y=A.createTreeWalker(A,129);function At(i,t){if(!Z(i)||!i.hasOwnProperty("raw"))throw Error("invalid template strings array");return dt!==void 0?dt.createHTML(t):t}var Tt=(i,t)=>{let e=i.length-1,s=[],r,o=t===2?"":t===3?"":"",n=w;for(let h=0;h"?(n=r??w,c=-1):p[1]===void 0?c=-2:(c=n.lastIndex-p[2].length,l=p[1],n=p[3]===void 0?g:p[3]==='"'?$t:ft):n===$t||n===ft?n=g:n===pt||n===ut?n=w:(n=g,r=void 0);let $=n===g&&i[h+1].startsWith("/>")?" ":"";o+=n===w?a+Mt:c>=0?(s.push(l),a.slice(0,c)+mt+a.slice(c)+_+$):a+_+(c===-2?h:$)}return[At(i,o+(i[e]||"")+(t===2?"":t===3?"":"")),s]},N=class i{constructor({strings:t,_$litType$:e},s){let r;this.parts=[];let o=0,n=0,h=t.length-1,a=this.parts,[l,p]=Tt(t,e);if(this.el=i.createElement(l,s),y.currentNode=this.el.content,e===2||e===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(r=y.nextNode())!==null&&a.length0){r.textContent=k?k.emptyScript:"";for(let $=0;$2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,r){let o=this.strings,n=!1;if(o===void 0)t=b(this,t,e,0),n=!P(t)||t!==this._$AH&&t!==v,n&&(this._$AH=t);else{let h=t,a,l;for(t=o[0],a=0;a{let s=e?.renderBefore??t,r=s._$litPart$;if(r===void 0){let o=e?.renderBefore??null;s._$litPart$=r=new O(t.insertBefore(C(),o),o,void 0,e??{})}return r._$AI(i),r};var tt=globalThis,m=class extends f{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=vt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return v}};m._$litElement$=!0,m.finalized=!0,tt.litElementHydrateSupport?.({LitElement:m});var Rt=tt.litElementPolyfillSupport;Rt?.({LitElement:m});(tt.litElementVersions??=[]).push("4.2.2");var I=new Set(["choice","multicast","doTry","loadBalance","recipientList","circuitBreaker"]);function It(i){if(!i.length)return null;let t={info:i[0],children:[],parent:null,subtreeWidth:0},e=t;for(let s=1;se.info.level)e.children.push(o),o.parent=e;else if(r.level===e.info.level){let n=e.parent??t;n.children.push(o),o.parent=n}else{let n=e.parent;for(;n&&n.info.level>=r.level;)n=n.parent;let h=n??t;h.children.push(o),o.parent=h}e=o}return t}function et(i){if(!i.children.length)return i.subtreeWidth=180,180;if(I.has(i.info.type)){let t=0;i.children.forEach((e,s)=>{s>0&&(t+=90),t+=et(e)}),i.subtreeWidth=Math.max(180,t)}else i.subtreeWidth=Math.max(180,...i.children.map(et));return i.subtreeWidth}function Wt(i){if(!i.parent)return null;let t=i.parent;if(I.has(t.info.type))return t.info.id;let e=t.children.indexOf(i);return e===0?t.info.id:bt(t.children[e-1])}function bt(i){return I.has(i.info.type)||!i.children.length?i.info.id:bt(i.children[i.children.length-1])}function st(i,t,e,s,r){let o=Math.max(i.subtreeWidth,s),n=t+(o-180)/2;if(r[i.info.id]={x:n,y:e,w:180,h:36,parentId:Wt(i),type:i.info.type,code:i.info.code,description:i.info.description??null,uri:i.info.uri??null,statistics:i.info.statistics??null},!i.children.length)return;let h=e+36+40;if(I.has(i.info.type)){let a=t+(o-i.subtreeWidth)/2;for(let l of i.children)st(l,a,h,l.subtreeWidth,r),a+=l.subtreeWidth+90}else{let a=h;for(let l of i.children)st(l,t,a,o,r),a=Et(l,r)+40}}function Et(i,t){let e=t[i.info.id],s=e?e.y+e.h:0;for(let r of i.children)s=Math.max(s,Et(r,t));return s}function St(i){let t=i.code??[];if(!t.length)return{positions:{},width:180+30*2,height:36+30*2};let e=It(t);et(e);let s={};st(e,30,30,e.subtreeWidth,s);let r=0,o=0;for(let n of Object.values(s))r=Math.max(r,n.x+n.w),o=Math.max(o,n.y+n.h);return{positions:s,width:r+30,height:o+30}}var Lt={route:"var(--crd-color-route, #6366f1)",from:"var(--crd-color-from, #0ea5e9)",to:"var(--crd-color-to, #0ea5e9)",log:"var(--crd-color-log, #64748b)",choice:"var(--crd-color-choice, #f59e0b)",when:"var(--crd-color-when, #fbbf24)",otherwise:"var(--crd-color-otherwise, #fbbf24)",doTry:"var(--crd-color-doTry, #f59e0b)",doCatch:"var(--crd-color-doCatch, #fbbf24)",doFinally:"var(--crd-color-doFinally, #fbbf24)",multicast:"var(--crd-color-multicast, #8b5cf6)",circuitBreaker:"var(--crd-color-circuitBreaker, #ef4444)"};function Bt(i){return Lt[i]??"var(--crd-color-default, #6366f1)"}function zt(i,t=28){if(!i)return"";let e=i.replace(/^\.+/,"");return e.length>t?e.slice(0,t-1)+"\u2026":e}function jt(i){if(!i)return null;let t=i.exchangesTotal??0,e=i.exchangesFailed??0;return`\u2713${t} \u2717${e}`}var it=class extends m{static properties={src:{type:String},refresh:{type:Number},filter:{type:String},_data:{state:!0},_error:{state:!0}};static styles=z` :host { display: block; font-family: var(--crd-font, system-ui, sans-serif); @@ -33,19 +33,19 @@ var M=globalThis,U=M.ShadowRoot&&(M.ShadyCSS===void 0||M.ShadyCSS.nativeShadow)& ${(this._data.routes??[]).map(e=>this.#i(e,t))} - `}#i(t,e){let{positions:s,width:i,height:o}=Et(t),n=Object.keys(s);return D` + `}#i(t,e){let{positions:s,width:r,height:o}=St(t),n=Object.keys(s);return D`
${t.routeId}
- ${n.map(h=>this.#r(h,s,e))} ${n.map(h=>this.#o(h,s[h]))} - `}#r(t,e,s){let i=e[t];if(!i.parentId)return d;let o=e[i.parentId];if(!o)return d;let n=o.x+180/2,h=o.y+36,a=i.x+180/2,l=i.y,p=(h+l)/2;return R``}#o(t,e){let s=Lt(e.description??e.code),i=Bt(e.statistics),o=It(e.type),n=e.x+180/2,h=e.y+36/2+4;return R` + marker-end="url(#${s})"/>`}#o(t,e){let s=zt(e.description??e.code),r=jt(e.statistics),o=Bt(e.type),n=e.x+180/2,h=e.y+36/2+4;return R` ${s} - ${i?R` + ${r?R` - ${i} + ${r} `:d} - `}};customElements.define("camel-route-diagram",st); + `}};customElements.define("camel-route-diagram",it); /*! Bundled license information: @lit/reactive-element/css-tag.js: From 094203cb846591531e4ed999ab5bb19fb5aef0af Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:42:47 -0400 Subject: [PATCH 09/18] CAMEL-23636: move smoke-test.html to src/main/frontend/ where it belongs The page tests frontend source and should live next to it. From there the bundle import is a clean one-level-up path (../resources/...) and the server command is simply: cd src/main/frontend && python3 -m http.server 8080 Co-Authored-By: Claude Sonnet 4.6 --- components/camel-diagram/pom.xml | 2 ++ .../frontend}/smoke-test.html | 20 ++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) rename components/camel-diagram/src/{test/resources => main/frontend}/smoke-test.html (77%) diff --git a/components/camel-diagram/pom.xml b/components/camel-diagram/pom.xml index cd3221780c965..867a749814c90 100644 --- a/components/camel-diagram/pom.xml +++ b/components/camel-diagram/pom.xml @@ -119,6 +119,8 @@ src/main/frontend/package.json src/main/frontend/package-lock.json src/main/frontend/node_modules/** + + src/main/frontend/smoke-test.html src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js diff --git a/components/camel-diagram/src/test/resources/smoke-test.html b/components/camel-diagram/src/main/frontend/smoke-test.html similarity index 77% rename from components/camel-diagram/src/test/resources/smoke-test.html rename to components/camel-diagram/src/main/frontend/smoke-test.html index e581ef23e9221..c0ef795dd2895 100644 --- a/components/camel-diagram/src/test/resources/smoke-test.html +++ b/components/camel-diagram/src/main/frontend/smoke-test.html @@ -20,27 +20,19 @@ camel-route-diagram smoke test - - - - - -

camel-route-diagram smoke test

- -
-

Auto theme (follows OS color scheme)

- -
- -
-

Light theme (forced)

- - -
- -
-

Dark theme (forced)

- - -
- - diff --git a/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js b/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js deleted file mode 100644 index 33ec7f7dcade0..0000000000000 --- a/components/camel-diagram/src/main/frontend/src/camel-route-diagram.js +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LitElement, html, svg, css, nothing } from 'lit'; -import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; -import { layoutRoute, NODE_W, NODE_H } from './layout.js'; - -const TYPE_COLORS = { - route: 'var(--crd-color-route, #6366f1)', - from: 'var(--crd-color-from, #0ea5e9)', - to: 'var(--crd-color-to, #0ea5e9)', - log: 'var(--crd-color-log, #64748b)', - choice: 'var(--crd-color-choice, #f59e0b)', - when: 'var(--crd-color-when, #fbbf24)', - otherwise: 'var(--crd-color-otherwise, #fbbf24)', - doTry: 'var(--crd-color-doTry, #f59e0b)', - doCatch: 'var(--crd-color-doCatch, #fbbf24)', - doFinally: 'var(--crd-color-doFinally, #fbbf24)', - multicast: 'var(--crd-color-multicast, #8b5cf6)', - circuitBreaker: 'var(--crd-color-circuitBreaker, #ef4444)', -}; - -// SVG icon paths from Lucide (https://lucide.dev) — ISC License -// Copyright (c) Lucide Contributors 2022; portions © Cole Bemis 2013-2022 (Feather, MIT) -const ICONS = { - workflow: '', - 'log-in': '', - 'log-out': '', - 'file-text': '', - 'git-branch': '', - 'corner-down-right':'', - split: '', - shield: '', - 'alert-triangle': '', - flag: '', - zap: '', - box: '', -}; - -const TYPE_ICON = { - route: 'workflow', from: 'log-in', to: 'log-out', log: 'file-text', - choice: 'git-branch', when: 'corner-down-right', otherwise: 'corner-down-right', - doTry: 'shield', doCatch: 'alert-triangle', doFinally: 'flag', - multicast: 'split', circuitBreaker: 'zap', -}; -const iconFor = (type) => ICONS[TYPE_ICON[type]] ?? ICONS.box; - -function nodeColor(type) { - return TYPE_COLORS[type] ?? 'var(--crd-color-default, #6366f1)'; -} - -function truncate(text, maxLen = 28) { - if (!text) return ''; - const clean = text.replace(/^\.+/, ''); - return clean.length > maxLen ? clean.slice(0, maxLen - 1) + '…' : clean; -} - -function formatStat(stats) { - if (!stats) return null; - const total = stats.exchangesTotal ?? 0; - const failed = stats.exchangesFailed ?? 0; - return `✓${total} ✗${failed}`; -} - -/** - * A web component that renders Apache Camel route diagrams as interactive SVG. - * - * Attributes: - * src - URL of the route-structure dev console endpoint (required) - * refresh - polling interval in ms; 0 = disabled (default: 0) - * filter - route ID filter, forwarded as ?filter= query param (default: all routes) - * - * CSS custom properties (all optional): - * --crd-bg, --crd-fg, --crd-edge, --crd-font, --crd-font-size, --crd-stat - * --crd-color-{route,from,to,log,choice,when,otherwise,doTry,doCatch,doFinally,...,default} - * - * @since 4.21 - */ -class CamelRouteDiagram extends LitElement { - static properties = { - src: { type: String }, - refresh: { type: Number }, - filter: { type: String }, - _data: { state: true }, - _error: { state: true }, - }; - - static styles = css` - :host { - display: block; - font-family: var(--crd-font, system-ui, sans-serif); - font-size: var(--crd-font-size, 12px); - background: var(--crd-bg, transparent); - color: var(--crd-fg, #1e293b); - } - @media (prefers-color-scheme: dark) { - :host { - background: var(--crd-bg, #0f172a); - color: var(--crd-fg, #e2e8f0); - } - } - .error { color: #ef4444; padding: 8px; } - .loading { opacity: .6; padding: 8px; } - .route-label { - font-weight: 600; - font-size: 0.9em; - padding: 4px 0 2px 0; - opacity: .8; - } - svg { display: block; overflow: visible; } - `; - - #timer = null; - #uid = Math.random().toString(36).slice(2); - #controller = null; - - constructor() { - super(); - this.src = ''; - this.refresh = 0; - this.filter = ''; - this._data = null; - this._error = null; - } - - connectedCallback() { - super.connectedCallback(); - // On reconnect (hasUpdated is true) the reactive properties haven't changed, - // so updated() won't fire automatically — force it with requestUpdate(). - // On first connect, super.connectedCallback() already schedules the update; - // this requestUpdate() coalesces harmlessly. - this.requestUpdate(); - } - - disconnectedCallback() { - super.disconnectedCallback(); - clearInterval(this.#timer); - this.#timer = null; - this.#controller?.abort(); - } - - updated(changedProperties) { - // React to src/filter/refresh changes and to reconnect (changedProperties is - // empty when requestUpdate() was called with no property change on reconnect). - const srcOrFilterChanged = changedProperties.has('src') || changedProperties.has('filter'); - const refreshChanged = changedProperties.has('refresh'); - const isReconnect = changedProperties.size === 0; - - if (refreshChanged || isReconnect) { - clearInterval(this.#timer); - this.#timer = null; - if (this.refresh > 0) { - this.#timer = setInterval(() => this.#doFetch(), this.refresh); - } - } - if (srcOrFilterChanged || isReconnect) { - this.#doFetch(); - } - } - - async #doFetch() { - const src = this.src?.trim(); - if (!src) return; - // Cancel any in-flight request so the last-sent response always wins. - this.#controller?.abort(); - this.#controller = new AbortController(); - try { - const url = new URL(src, location.href); - if (this.filter) url.searchParams.set('filter', this.filter); - url.searchParams.set('metric', 'true'); - const res = await fetch(url, { signal: this.#controller.signal }); - if (!res.ok) { - this._error = `HTTP ${res.status} ${res.statusText}`; - this._data = null; - return; - } - const data = await res.json(); - if (!Array.isArray(data?.routes)) { - this._error = 'Unexpected response: missing routes array'; - this._data = null; - return; - } - this._data = data; - this._error = null; - } catch (e) { - if (e.name !== 'AbortError') { - this._error = e.message; - } - } - } - - render() { - if (this._error) return html`

⚠ ${this._error}

`; - if (!this._data) return html`

Loading diagram…

`; - - return html`${this._data.routes.map(r => this.#renderRoute(r))}`; - } - - #renderRoute(route) { - const { positions, width, height } = layoutRoute(route); - const ids = Object.keys(positions); - const markerId = `arrow-${this.#uid}`; - // The is defined inside the same it is used in so that - // url(#id) paint-server references resolve correctly in all browsers, - // including Firefox which does not resolve them across sibling elements. - return html` -
${route.routeId}
- - - - - - - ${ids.map(id => this.#renderEdge(id, positions, markerId))} - ${ids.map(id => this.#renderNode(id, positions[id]))} - - `; - } - - #renderEdge(id, positions, markerId) { - const pos = positions[id]; - if (!pos.parentId) return nothing; - const parent = positions[pos.parentId]; - if (!parent) return nothing; - - const x1 = parent.x + NODE_W / 2; - const y1 = parent.y + NODE_H; - const x2 = pos.x + NODE_W / 2; - const y2 = pos.y; - const my = (y1 + y2) / 2; - - return svg``; - } - - #renderNode(id, pos) { - const label = truncate(pos.description ?? pos.code); - const stat = formatStat(pos.statistics); - const fill = nodeColor(pos.type); - const cx = pos.x + NODE_W / 2; - const tx = cx + 9; // shift label clear of the icon - const textY = pos.y + NODE_H / 2 + 4; - - return svg` - - - - ${unsafeSVG(iconFor(pos.type))} - - - ${label} - - ${stat ? svg` - - ${stat} - ` : nothing} - `; - } -} - -customElements.define('camel-route-diagram', CamelRouteDiagram); diff --git a/components/camel-diagram/src/main/frontend/src/layout.js b/components/camel-diagram/src/main/frontend/src/layout.js deleted file mode 100644 index 8856a0b3c6489..0000000000000 --- a/components/camel-diagram/src/main/frontend/src/layout.js +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const NODE_W = 180; -export const NODE_H = 36; -export const H_GAP = NODE_W / 2; -export const V_GAP = 40; -export const PADDING = 30; - -const BRANCHING_EIPS = new Set([ - 'choice', 'multicast', 'doTry', 'loadBalance', 'recipientList', 'circuitBreaker', -]); - -/** - * Builds a tree of {info, children, parent, subtreeWidth} nodes from a flat, level-ordered array. - * Faithfully ports RouteDiagramLayoutEngine.buildTree(). - * - * @param {Array<{type:string, id:string, level:number, code:string}>} nodes - * @returns {{info, children, parent, subtreeWidth}|null} - */ -export function buildTree(nodes) { - if (!nodes.length) return null; - const root = { info: nodes[0], children: [], parent: null, subtreeWidth: 0 }; - let current = root; - - for (let i = 1; i < nodes.length; i++) { - const ni = nodes[i]; - // Skip nodes without an id — they would collide on positions[undefined]. - if (!ni.id) continue; - const tn = { info: ni, children: [], parent: null, subtreeWidth: 0 }; - - if (ni.level > current.info.level) { - current.children.push(tn); - tn.parent = current; - } else if (ni.level === current.info.level) { - const parent = current.parent ?? root; - parent.children.push(tn); - tn.parent = parent; - } else { - let ancestor = current.parent; - while (ancestor && ancestor.info.level >= ni.level) { - ancestor = ancestor.parent; - } - const target = ancestor ?? root; - target.children.push(tn); - tn.parent = target; - } - current = tn; - } - return root; -} - -/** - * Computes subtreeWidth on every node (bottom-up). - * Branching EIPs: sum of child widths + (n-1)*H_GAP. - * Others: max of child widths. - * - * @param {{info, children, subtreeWidth}} node - * @returns {number} - */ -export function computeSubtreeWidth(node) { - if (!node.children.length) { - node.subtreeWidth = NODE_W; - return NODE_W; - } - if (BRANCHING_EIPS.has(node.info.type)) { - let total = 0; - node.children.forEach((c, i) => { - if (i > 0) total += H_GAP; - total += computeSubtreeWidth(c); - }); - node.subtreeWidth = Math.max(NODE_W, total); - } else { - // Use reduce instead of spread to avoid RangeError on very large child arrays. - node.subtreeWidth = node.children.reduce( - (max, c) => Math.max(max, computeSubtreeWidth(c)), - NODE_W, - ); - } - return node.subtreeWidth; -} - -/** - * Returns the id of the node that an edge should visually originate from when - * drawing a connection TO `node`. For non-first children of a linear (non-branching) - * parent this is the last node in the previous sibling's chain — not the shared - * tree parent — so edges don't pass through intermediate nodes. - * Ports RouteDiagramLayoutEngine.findLastLayoutNode(). - */ -function visualParentId(node) { - if (!node.parent) return null; - const parent = node.parent; - if (BRANCHING_EIPS.has(parent.info.type)) { - // All branches of a branching EIP connect from the branching node. - return parent.info.id; - } - const idx = parent.children.indexOf(node); - if (idx === 0) { - return parent.info.id; - } - // Connect from the last node in the previous sibling's subtree chain. - return lastChainId(parent.children[idx - 1]); -} - -/** - * Traverses the rightmost (last) chain of a subtree and returns its leaf id. - * Stops at branching EIPs (they have no single continuation point). - */ -function lastChainId(node) { - if (BRANCHING_EIPS.has(node.info.type) || !node.children.length) { - return node.info.id; - } - return lastChainId(node.children[node.children.length - 1]); -} - -/** - * Walks the tree and populates positions[id] with {x, y, w, h, parentId, type, code, ...}. - * x, y are the top-left corner of the node box in SVG logical pixels. - * - * Returns the bottom-most Y coordinate of the entire subtree rooted at `node`, - * which the caller uses to position the next sibling without re-traversing the tree. - * - * @param {{info, children, parent, subtreeWidth}} node - * @param {number} x left edge of the available horizontal band - * @param {number} y top of this node - * @param {number} parentWidth width of the parent's horizontal band - * @param {Object} positions output map: id → position record - * @returns {number} bottom Y of this subtree (y + h of the deepest node placed) - */ -export function assignPositions(node, x, y, parentWidth, positions) { - if (!node.info.id) return y + NODE_H; - - const available = Math.max(node.subtreeWidth, parentWidth); - const nodeX = x + (available - NODE_W) / 2; - - positions[node.info.id] = { - x: nodeX, - y, - w: NODE_W, - h: NODE_H, - parentId: visualParentId(node), - type: node.info.type, - code: node.info.code, - description: node.info.description ?? null, - uri: node.info.uri ?? null, - statistics: node.info.statistics ?? null, - }; - - if (!node.children.length) return y + NODE_H; - - const childY = y + NODE_H + V_GAP; - - if (BRANCHING_EIPS.has(node.info.type)) { - let childX = x + (available - node.subtreeWidth) / 2; - let maxBottom = childY; - for (const child of node.children) { - const bottom = assignPositions(child, childX, childY, child.subtreeWidth, positions); - if (bottom > maxBottom) maxBottom = bottom; - childX += child.subtreeWidth + H_GAP; - } - return maxBottom; - } else { - let curY = childY; - for (const child of node.children) { - // assignPositions returns the bottom of this child's full subtree — O(n) total. - curY = assignPositions(child, x, curY, available, positions) + V_GAP; - } - return curY - V_GAP; - } -} - -/** - * Main entry: convert one route object from the route-structure JSON to positioned nodes. - * - * @param {{routeId:string, code:Array}} route - * @returns {{positions:Object, width:number, height:number}} - */ -export function layoutRoute(route) { - const nodes = route.code ?? []; - if (!nodes.length) { - return { positions: {}, width: NODE_W + PADDING * 2, height: NODE_H + PADDING * 2 }; - } - - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - - const positions = {}; - assignPositions(tree, PADDING, PADDING, tree.subtreeWidth, positions); - - let maxX = 0; - let maxYVal = 0; - for (const p of Object.values(positions)) { - maxX = Math.max(maxX, p.x + p.w); - maxYVal = Math.max(maxYVal, p.y + p.h); - } - - return { positions, width: maxX + PADDING, height: maxYVal + PADDING }; -} diff --git a/components/camel-diagram/src/main/frontend/test/layout.test.js b/components/camel-diagram/src/main/frontend/test/layout.test.js deleted file mode 100644 index dcbacbd7feb9e..0000000000000 --- a/components/camel-diagram/src/main/frontend/test/layout.test.js +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { describe, it, expect } from 'vitest'; -import { buildTree, computeSubtreeWidth, assignPositions, NODE_W, NODE_H, V_GAP, H_GAP } from '../src/layout.js'; - -const node = (type, id, level, code = type) => ({ type, id, level, code }); - -describe('buildTree', () => { - it('single root node returns tree with no children', () => { - const tree = buildTree([node('route', 'r1', 0)]); - expect(tree.info.id).toBe('r1'); - expect(tree.children).toHaveLength(0); - }); - - it('flat siblings share the same parent', () => { - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('log', 'l1', 1), - node('to', 't1', 1), - ]; - const tree = buildTree(nodes); - expect(tree.children).toHaveLength(3); - expect(tree.children.map(c => c.info.id)).toEqual(['f1', 'l1', 't1']); - }); - - it('nested choice/when structure builds correct tree', () => { - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('choice', 'ch', 1), - node('when', 'wh', 2), - node('to', 't1', 3), - node('otherwise', 'ow', 2), - node('to', 't2', 3), - ]; - const tree = buildTree(nodes); - const choice = tree.children.find(c => c.info.type === 'choice'); - expect(choice).toBeDefined(); - expect(choice.children).toHaveLength(2); - expect(choice.children[0].info.type).toBe('when'); - expect(choice.children[0].children[0].info.type).toBe('to'); - expect(choice.children[1].info.type).toBe('otherwise'); - }); - - it('nodes without an id are silently skipped', () => { - const nodes = [ - node('route', 'r1', 0), - { type: 'log', level: 1 }, // no id - node('to', 't1', 1), - ]; - const tree = buildTree(nodes); - expect(tree.children).toHaveLength(1); - expect(tree.children[0].info.id).toBe('t1'); - }); -}); - -describe('computeSubtreeWidth', () => { - it('leaf node width equals NODE_W', () => { - const tree = buildTree([node('log', 'l1', 0)]); - computeSubtreeWidth(tree); - expect(tree.subtreeWidth).toBe(NODE_W); - }); - - it('branching EIP width is sum of branch widths plus gaps', () => { - const nodes = [ - node('choice', 'ch', 0), - node('when', 'w1', 1), - node('otherwise', 'ow', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - expect(tree.subtreeWidth).toBe(NODE_W * 2 + H_GAP); - }); - - it('non-branching node width is max of child subtree widths', () => { - // A route with two linear siblings; non-branching, so width = max child width = NODE_W. - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('to', 't1', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - expect(tree.subtreeWidth).toBe(NODE_W); - }); -}); - -describe('assignPositions', () => { - it('linear chain: each node connects to its visual predecessor, not the tree root', () => { - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('log', 'l1', 1), - node('to', 't1', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - expect(positions['r1'].parentId).toBeNull(); - expect(positions['f1'].parentId).toBe('r1'); - expect(positions['l1'].parentId).toBe('f1'); // NOT 'r1' - expect(positions['t1'].parentId).toBe('l1'); // NOT 'r1' - }); - - it('single-chain route assigns strictly increasing y values', () => { - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('log', 'l1', 1), - node('to', 't1', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - const ys = ['r1', 'f1', 'l1', 't1'].map(id => positions[id].y); - expect(ys[1]).toBeGreaterThan(ys[0]); - expect(ys[2]).toBeGreaterThan(ys[1]); - expect(ys[3]).toBeGreaterThan(ys[2]); - }); - - it('branching EIP children are laid out side-by-side (different x, same y)', () => { - const nodes = [ - node('choice', 'ch', 0), - node('when', 'w1', 1), - node('otherwise', 'ow', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - // when must be to the LEFT of otherwise - expect(positions['w1'].x).toBeLessThan(positions['ow'].x); - expect(positions['w1'].y).toBe(positions['ow'].y); - }); - - it('next sibling is placed below the deepest descendant of the previous sibling', () => { - // route -> [choice -> [when -> log_a, otherwise -> log_b], log_after] - // log_after must be below BOTH log_a and log_b. - const nodes = [ - node('route', 'r1', 0), - node('choice', 'ch', 1), - node('when', 'wh', 2), - node('log', 'la', 3), - node('otherwise', 'ow', 2), - node('log', 'lb', 3), - node('log', 'lafter', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - const lafterY = positions['lafter'].y; - expect(lafterY).toBeGreaterThan(positions['la'].y + NODE_H); - expect(lafterY).toBeGreaterThan(positions['lb'].y + NODE_H); - }); - - it('linear chain after a branching EIP connects from the branching EIP, not its descendants', () => { - // route -> [choice -> [when, otherwise], log_after] - // log_after.parentId should be 'ch' (the branching EIP), not 'when' or 'otherwise'. - const nodes = [ - node('route', 'r1', 0), - node('choice', 'ch', 1), - node('when', 'wh', 2), - node('otherwise', 'ow', 2), - node('log', 'lafter', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - expect(positions['lafter'].parentId).toBe('ch'); - }); - - it('assignPositions returns the bottom Y of the entire subtree', () => { - const nodes = [ - node('route', 'r1', 0), - node('from', 'f1', 1), - node('log', 'l1', 1), - ]; - const tree = buildTree(nodes); - computeSubtreeWidth(tree); - const positions = {}; - const bottom = assignPositions(tree, 0, 0, tree.subtreeWidth, positions); - - // bottom must equal the y + h of the deepest node (l1) - const deepestY = positions['l1'].y + NODE_H; - expect(bottom).toBe(deepestY); - }); -}); diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/THIRD-PARTY-NOTICES.txt b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/THIRD-PARTY-NOTICES.txt index 94a6ba9a9ca76..1a892479e6964 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/THIRD-PARTY-NOTICES.txt +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/THIRD-PARTY-NOTICES.txt @@ -1,110 +1,14 @@ -camel-route-diagram bundles the following third-party libraries. -This file is generated by build.mjs from the esbuild-produced .LEGAL.txt sidecar. +camel-route-diagram bundles the following third-party content. -------------------------------------------------------------------------------- -Copyright notices (from camel-route-diagram.js.LEGAL.txt): --------------------------------------------------------------------------------- - -Bundled license information: - -@lit/reactive-element/css-tag.js: - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -@lit/reactive-element/reactive-element.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/lit-html.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-element/lit-element.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/is-server.js: - /** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directive.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directives/unsafe-html.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directives/unsafe-svg.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - Lucide (icon SVG paths inlined in camel-route-diagram.js): - /** - * @license - * ISC License - * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). - * All other copyright (c) for Lucide are held by Lucide Contributors 2022. - * SPDX-License-Identifier: ISC - * https://lucide.dev - */ - --------------------------------------------------------------------------------- -BSD 3-Clause License (applies to all @lit/* and lit* packages listed above): -------------------------------------------------------------------------------- -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - --------------------------------------------------------------------------------- -ISC License (applies to Lucide icon paths listed above): --------------------------------------------------------------------------------- + ISC License + Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). + All other copyright (c) for Lucide are held by Lucide Contributors 2022. + SPDX-License-Identifier: ISC + https://lucide.dev Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index 38051d5ddf2bb..b6dd8395c7c6f 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -1,66 +1,451 @@ -var k=globalThis,R=k.ShadowRoot&&(k.ShadyCSS===void 0||k.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,G=Symbol(),ht=new WeakMap,S=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==G)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(R&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=ht.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&ht.set(e,t))}return t}toString(){return this.cssText}},lt=r=>new S(typeof r=="string"?r:r+"",void 0,G),F=(r,...t)=>{let e=r.length===1?r[0]:t.reduce((s,i,n)=>s+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[n+1],r[0]);return new S(e,r,G)},ct=(r,t)=>{if(R)r.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),i=k.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=e.cssText,r.appendChild(s)}},q=R?r=>r:r=>r instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return lt(e)})(r):r;var{is:Ot,defineProperty:Tt,getOwnPropertyDescriptor:Ht,getOwnPropertyNames:Dt,getOwnPropertySymbols:Ut,getPrototypeOf:kt}=Object,I=globalThis,dt=I.trustedTypes,Rt=dt?dt.emptyScript:"",It=I.reactiveElementPolyfillSupport,w=(r,t)=>r,Y={toAttribute(r,t){switch(t){case Boolean:r=r?Rt:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,t){let e=r;switch(t){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},ut=(r,t)=>!Ot(r,t),pt={attribute:!0,type:String,converter:Y,reflect:!1,useDefault:!1,hasChanged:ut};Symbol.metadata??=Symbol("metadata"),I.litPropertyMetadata??=new WeakMap;var f=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=pt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),i=this.getPropertyDescriptor(t,s,e);i!==void 0&&Tt(this.prototype,t,i)}}static getPropertyDescriptor(t,e,s){let{get:i,set:n}=Ht(this.prototype,t)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let h=i?.call(this);n?.call(this,o),this.requestUpdate(t,h,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??pt}static _$Ei(){if(this.hasOwnProperty(w("elementProperties")))return;let t=kt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(w("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(w("properties"))){let e=this.properties,s=[...Dt(e),...Ut(e)];for(let i of s)this.createProperty(i,e[i])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,i]of e)this.elementProperties.set(s,i)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let i=this._$Eu(e,s);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let i of s)e.unshift(q(i))}else t!==void 0&&e.push(q(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ct(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){let n=(s.converter?.toAttribute!==void 0?s.converter:Y).toAttribute(e,s.type);this._$Em=t,n==null?this.removeAttribute(i):this.setAttribute(i,n),this._$Em=null}}_$AK(t,e){let s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){let n=s.getPropertyOptions(i),o=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:Y;this._$Em=i;let h=o.fromAttribute(e,n.type);this[i]=h??this._$Ej?.get(i)??h,this._$Em=null}}requestUpdate(t,e,s,i=!1,n){if(t!==void 0){let o=this.constructor;if(i===!1&&(n=this[t]),s??=o.getPropertyOptions(t),!((s.hasChanged??ut)(n,e)||s.useDefault&&s.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:i,wrapped:n},o){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),n!==!0||o!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,n]of this._$Ep)this[i]=n;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[i,n]of s){let{wrapped:o}=n,h=this[i];o!==!0||this._$AL.has(i)||h===void 0||this.C(i,void 0,n,h)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};f.elementStyles=[],f.shadowRootOptions={mode:"open"},f[w("elementProperties")]=new Map,f[w("finalized")]=new Map,It?.({ReactiveElement:f}),(I.reactiveElementVersions??=[]).push("2.1.2");var et=globalThis,ft=r=>r,L=et.trustedTypes,$t=L?L.createPolicy("lit-html",{createHTML:r=>r}):void 0,vt="$lit$",m=`lit$${Math.random().toFixed(9).slice(2)}$`,bt="?"+m,Lt=`<${bt}>`,v=document,P=()=>v.createComment(""),N=r=>r===null||typeof r!="object"&&typeof r!="function",st=Array.isArray,Wt=r=>st(r)||typeof r?.[Symbol.iterator]=="function",K=`[ -\f\r]`,C=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_t=/-->/g,mt=/>/g,y=RegExp(`>|${K}(?:([^\\s"'>=/]+)(${K}*=${K}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),gt=/'/g,yt=/"/g,Et=/^(?:script|style|textarea|title)$/i,rt=r=>(t,...e)=>({_$litType$:r,strings:t,values:e}),T=rt(1),W=rt(2),se=rt(3),$=Symbol.for("lit-noChange"),d=Symbol.for("lit-nothing"),At=new WeakMap,A=v.createTreeWalker(v,129);function xt(r,t){if(!st(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return $t!==void 0?$t.createHTML(t):t}var zt=(r,t)=>{let e=r.length-1,s=[],i,n=t===2?"":t===3?"":"",o=C;for(let h=0;h"?(o=i??C,c=-1):p[1]===void 0?c=-2:(c=o.lastIndex-p[2].length,l=p[1],o=p[3]===void 0?y:p[3]==='"'?yt:gt):o===yt||o===gt?o=y:o===_t||o===mt?o=C:(o=y,i=void 0);let _=o===y&&r[h+1].startsWith("/>")?" ":"";n+=o===C?a+Lt:c>=0?(s.push(l),a.slice(0,c)+vt+a.slice(c)+m+_):a+m+(c===-2?h:_)}return[xt(r,n+(r[e]||"")+(t===2?"":t===3?"":"")),s]},M=class r{constructor({strings:t,_$litType$:e},s){let i;this.parts=[];let n=0,o=0,h=t.length-1,a=this.parts,[l,p]=zt(t,e);if(this.el=r.createElement(l,s),A.currentNode=this.el.content,e===2||e===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(i=A.nextNode())!==null&&a.length0){i.textContent=L?L.emptyScript:"";for(let _=0;_2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=d}_$AI(t,e=this,s,i){let n=this.strings,o=!1;if(n===void 0)t=E(this,t,e,0),o=!N(t)||t!==this._$AH&&t!==$,o&&(this._$AH=t);else{let h=t,a,l;for(t=n[0],a=0;a{let s=e?.renderBefore??t,i=s._$litPart$;if(i===void 0){let n=e?.renderBefore??null;s._$litPart$=i=new O(t.insertBefore(P(),n),n,void 0,e??{})}return i._$AI(r),i};var it=globalThis,g=class extends f{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=St(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return $}};g._$litElement$=!0,g.finalized=!0,it.litElementHydrateSupport?.({LitElement:g});var Vt=it.litElementPolyfillSupport;Vt?.({LitElement:g});(it.litElementVersions??=[]).push("4.2.2");var wt={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},B=r=>(...t)=>({_$litDirective$:r,values:t}),z=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,s){this._$Ct=t,this._$AM=e,this._$Ci=s}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};var b=class extends z{constructor(t){if(super(t),this.it=d,t.type!==wt.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(t){if(t===d||t==null)return this._t=void 0,this.it=t;if(t===$)return t;if(typeof t!="string")throw Error(this.constructor.directiveName+"() called with a non-string value");if(t===this.it)return this._t;this.it=t;let e=[t];return e.raw=e,this._t={_$litType$:this.constructor.resultType,strings:e,values:[]}}};b.directiveName="unsafeHTML",b.resultType=1;var ge=B(b);var H=class extends b{};H.directiveName="unsafeSVG",H.resultType=2;var Ct=B(H);var V=new Set(["choice","multicast","doTry","loadBalance","recipientList","circuitBreaker"]);function jt(r){if(!r.length)return null;let t={info:r[0],children:[],parent:null,subtreeWidth:0},e=t;for(let s=1;se.info.level)e.children.push(n),n.parent=e;else if(i.level===e.info.level){let o=e.parent??t;o.children.push(n),n.parent=o}else{let o=e.parent;for(;o&&o.info.level>=i.level;)o=o.parent;let h=o??t;h.children.push(n),n.parent=h}e=n}return t}function nt(r){if(!r.children.length)return r.subtreeWidth=180,180;if(V.has(r.info.type)){let t=0;r.children.forEach((e,s)=>{s>0&&(t+=90),t+=nt(e)}),r.subtreeWidth=Math.max(180,t)}else r.subtreeWidth=r.children.reduce((t,e)=>Math.max(t,nt(e)),180);return r.subtreeWidth}function Gt(r){if(!r.parent)return null;let t=r.parent;if(V.has(t.info.type))return t.info.id;let e=t.children.indexOf(r);return e===0?t.info.id:Pt(t.children[e-1])}function Pt(r){return V.has(r.info.type)||!r.children.length?r.info.id:Pt(r.children[r.children.length-1])}function ot(r,t,e,s,i){if(!r.info.id)return e+36;let n=Math.max(r.subtreeWidth,s),o=t+(n-180)/2;if(i[r.info.id]={x:o,y:e,w:180,h:36,parentId:Gt(r),type:r.info.type,code:r.info.code,description:r.info.description??null,uri:r.info.uri??null,statistics:r.info.statistics??null},!r.children.length)return e+36;let h=e+36+40;if(V.has(r.info.type)){let a=t+(n-r.subtreeWidth)/2,l=h;for(let p of r.children){let c=ot(p,a,h,p.subtreeWidth,i);c>l&&(l=c),a+=p.subtreeWidth+90}return l}else{let a=h;for(let l of r.children)a=ot(l,t,a,n,i)+40;return a-40}}function Nt(r){let t=r.code??[];if(!t.length)return{positions:{},width:180+30*2,height:36+30*2};let e=jt(t);nt(e);let s={};ot(e,30,30,e.subtreeWidth,s);let i=0,n=0;for(let o of Object.values(s))i=Math.max(i,o.x+o.w),n=Math.max(n,o.y+o.h);return{positions:s,width:i+30,height:n+30}}var Ft={route:"var(--crd-color-route, #6366f1)",from:"var(--crd-color-from, #0ea5e9)",to:"var(--crd-color-to, #0ea5e9)",log:"var(--crd-color-log, #64748b)",choice:"var(--crd-color-choice, #f59e0b)",when:"var(--crd-color-when, #fbbf24)",otherwise:"var(--crd-color-otherwise, #fbbf24)",doTry:"var(--crd-color-doTry, #f59e0b)",doCatch:"var(--crd-color-doCatch, #fbbf24)",doFinally:"var(--crd-color-doFinally, #fbbf24)",multicast:"var(--crd-color-multicast, #8b5cf6)",circuitBreaker:"var(--crd-color-circuitBreaker, #ef4444)"},Mt={workflow:'',"log-in":'',"log-out":'',"file-text":'',"git-branch":'',"corner-down-right":'',split:'',shield:'',"alert-triangle":'',flag:'',zap:'',box:''},qt={route:"workflow",from:"log-in",to:"log-out",log:"file-text",choice:"git-branch",when:"corner-down-right",otherwise:"corner-down-right",doTry:"shield",doCatch:"alert-triangle",doFinally:"flag",multicast:"split",circuitBreaker:"zap"},Yt=r=>Mt[qt[r]]??Mt.box;function Kt(r){return Ft[r]??"var(--crd-color-default, #6366f1)"}function Xt(r,t=28){if(!r)return"";let e=r.replace(/^\.+/,"");return e.length>t?e.slice(0,t-1)+"\u2026":e}function Zt(r){if(!r)return null;let t=r.exchangesTotal??0,e=r.exchangesFailed??0;return`\u2713${t} \u2717${e}`}var at=class extends g{static properties={src:{type:String},refresh:{type:Number},filter:{type:String},_data:{state:!0},_error:{state:!0}};static styles=F` +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ─── Layout engine (ported from RouteDiagramLayoutEngine.java) ─────────────── + +const NODE_W = 180; +const NODE_H = 36; +const H_GAP = NODE_W / 2; +const V_GAP = 40; +const PADDING = 30; + +const BRANCHING_EIPS = new Set([ + 'choice', 'multicast', 'doTry', 'loadBalance', 'recipientList', 'circuitBreaker', +]); + +function buildTree(nodes) { + if (!nodes.length) return null; + const root = { info: nodes[0], children: [], parent: null, subtreeWidth: 0 }; + let current = root; + + for (let i = 1; i < nodes.length; i++) { + const ni = nodes[i]; + if (!ni.id) continue; + const tn = { info: ni, children: [], parent: null, subtreeWidth: 0 }; + + if (ni.level > current.info.level) { + current.children.push(tn); + tn.parent = current; + } else if (ni.level === current.info.level) { + const parent = current.parent ?? root; + parent.children.push(tn); + tn.parent = parent; + } else { + let ancestor = current.parent; + while (ancestor && ancestor.info.level >= ni.level) { + ancestor = ancestor.parent; + } + const target = ancestor ?? root; + target.children.push(tn); + tn.parent = target; + } + current = tn; + } + return root; +} + +function computeSubtreeWidth(node) { + if (!node.children.length) { + node.subtreeWidth = NODE_W; + return NODE_W; + } + if (BRANCHING_EIPS.has(node.info.type)) { + let total = 0; + node.children.forEach((c, i) => { + if (i > 0) total += H_GAP; + total += computeSubtreeWidth(c); + }); + node.subtreeWidth = Math.max(NODE_W, total); + } else { + node.subtreeWidth = node.children.reduce( + (max, c) => Math.max(max, computeSubtreeWidth(c)), + NODE_W, + ); + } + return node.subtreeWidth; +} + +function visualParentId(node) { + if (!node.parent) return null; + const parent = node.parent; + if (BRANCHING_EIPS.has(parent.info.type)) { + return parent.info.id; + } + const idx = parent.children.indexOf(node); + if (idx === 0) { + return parent.info.id; + } + return lastChainId(parent.children[idx - 1]); +} + +function lastChainId(node) { + if (BRANCHING_EIPS.has(node.info.type) || !node.children.length) { + return node.info.id; + } + return lastChainId(node.children[node.children.length - 1]); +} + +function assignPositions(node, x, y, parentWidth, positions) { + if (!node.info.id) return y + NODE_H; + + const available = Math.max(node.subtreeWidth, parentWidth); + const nodeX = x + (available - NODE_W) / 2; + + positions[node.info.id] = { + x: nodeX, + y, + w: NODE_W, + h: NODE_H, + parentId: visualParentId(node), + type: node.info.type, + code: node.info.code, + description: node.info.description ?? null, + uri: node.info.uri ?? null, + statistics: node.info.statistics ?? null, + }; + + if (!node.children.length) return y + NODE_H; + + const childY = y + NODE_H + V_GAP; + + if (BRANCHING_EIPS.has(node.info.type)) { + let childX = x + (available - node.subtreeWidth) / 2; + let maxBottom = childY; + for (const child of node.children) { + const bottom = assignPositions(child, childX, childY, child.subtreeWidth, positions); + if (bottom > maxBottom) maxBottom = bottom; + childX += child.subtreeWidth + H_GAP; + } + return maxBottom; + } else { + let curY = childY; + for (const child of node.children) { + curY = assignPositions(child, x, curY, available, positions) + V_GAP; + } + return curY - V_GAP; + } +} + +function layoutRoute(route) { + const nodes = route.code ?? []; + if (!nodes.length) { + return { positions: {}, width: NODE_W + PADDING * 2, height: NODE_H + PADDING * 2 }; + } + + const tree = buildTree(nodes); + computeSubtreeWidth(tree); + + const positions = {}; + assignPositions(tree, PADDING, PADDING, tree.subtreeWidth, positions); + + let maxX = 0; + let maxYVal = 0; + for (const p of Object.values(positions)) { + maxX = Math.max(maxX, p.x + p.w); + maxYVal = Math.max(maxYVal, p.y + p.h); + } + + return { positions, width: maxX + PADDING, height: maxYVal + PADDING }; +} + +// ─── Web component ──────────────────────────────────────────────────────────── + +const TYPE_COLORS = { + route: 'var(--crd-color-route, #6366f1)', + from: 'var(--crd-color-from, #0ea5e9)', + to: 'var(--crd-color-to, #0ea5e9)', + log: 'var(--crd-color-log, #64748b)', + choice: 'var(--crd-color-choice, #f59e0b)', + when: 'var(--crd-color-when, #fbbf24)', + otherwise: 'var(--crd-color-otherwise, #fbbf24)', + doTry: 'var(--crd-color-doTry, #f59e0b)', + doCatch: 'var(--crd-color-doCatch, #fbbf24)', + doFinally: 'var(--crd-color-doFinally, #fbbf24)', + multicast: 'var(--crd-color-multicast, #8b5cf6)', + circuitBreaker: 'var(--crd-color-circuitBreaker, #ef4444)', +}; + +// SVG icon paths from Lucide (https://lucide.dev) — ISC License +// Copyright (c) Lucide Contributors 2022; portions © Cole Bemis 2013-2022 (Feather, MIT) +const ICONS = { + workflow: '', + 'log-in': '', + 'log-out': '', + 'file-text': '', + 'git-branch': '', + 'corner-down-right': '', + split: '', + shield: '', + 'alert-triangle': '', + flag: '', + zap: '', + box: '', +}; + +const TYPE_ICON = { + route: 'workflow', from: 'log-in', to: 'log-out', log: 'file-text', + choice: 'git-branch', when: 'corner-down-right', otherwise: 'corner-down-right', + doTry: 'shield', doCatch: 'alert-triangle', doFinally: 'flag', + multicast: 'split', circuitBreaker: 'zap', +}; + +function iconFor(type) { + return ICONS[TYPE_ICON[type]] ?? ICONS.box; +} + +function nodeColor(type) { + return TYPE_COLORS[type] ?? 'var(--crd-color-default, #6366f1)'; +} + +function truncate(text, maxLen = 28) { + if (!text) return ''; + const clean = text.replace(/^\.+/, ''); + return clean.length > maxLen ? clean.slice(0, maxLen - 1) + '…' : clean; +} + +function formatStat(stats) { + if (!stats) return null; + const total = stats.exchangesTotal ?? 0; + const failed = stats.exchangesFailed ?? 0; + return `✓${total} ✗${failed}`; +} + +function esc(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +const COMPONENT_STYLE = ` + :host { + display: block; + font-family: var(--crd-font, system-ui, sans-serif); + font-size: var(--crd-font-size, 12px); + background: var(--crd-bg, transparent); + color: var(--crd-fg, #1e293b); + } + @media (prefers-color-scheme: dark) { :host { - display: block; - font-family: var(--crd-font, system-ui, sans-serif); - font-size: var(--crd-font-size, 12px); - background: var(--crd-bg, transparent); - color: var(--crd-fg, #1e293b); - } - @media (prefers-color-scheme: dark) { - :host { - background: var(--crd-bg, #0f172a); - color: var(--crd-fg, #e2e8f0); - } - } - .error { color: #ef4444; padding: 8px; } - .loading { opacity: .6; padding: 8px; } - .route-label { - font-weight: 600; - font-size: 0.9em; - padding: 4px 0 2px 0; - opacity: .8; - } - svg { display: block; overflow: visible; } - `;#t=null;#r=Math.random().toString(36).slice(2);#e=null;constructor(){super(),this.src="",this.refresh=0,this.filter="",this._data=null,this._error=null}connectedCallback(){super.connectedCallback(),this.requestUpdate()}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this.#t),this.#t=null,this.#e?.abort()}updated(t){let e=t.has("src")||t.has("filter"),s=t.has("refresh"),i=t.size===0;(s||i)&&(clearInterval(this.#t),this.#t=null,this.refresh>0&&(this.#t=setInterval(()=>this.#s(),this.refresh))),(e||i)&&this.#s()}async#s(){let t=this.src?.trim();if(t){this.#e?.abort(),this.#e=new AbortController;try{let e=new URL(t,location.href);this.filter&&e.searchParams.set("filter",this.filter),e.searchParams.set("metric","true");let s=await fetch(e,{signal:this.#e.signal});if(!s.ok){this._error=`HTTP ${s.status} ${s.statusText}`,this._data=null;return}let i=await s.json();if(!Array.isArray(i?.routes)){this._error="Unexpected response: missing routes array",this._data=null;return}this._data=i,this._error=null}catch(e){e.name!=="AbortError"&&(this._error=e.message)}}}render(){return this._error?T`

⚠ ${this._error}

`:this._data?T`${this._data.routes.map(t=>this.#i(t))}`:T`

Loading diagram…

`}#i(t){let{positions:e,width:s,height:i}=Nt(t),n=Object.keys(e),o=`arrow-${this.#r}`;return T` -
${t.routeId}
- + background: var(--crd-bg, #0f172a); + color: var(--crd-fg, #e2e8f0); + } + } + .error { color: #ef4444; padding: 8px; } + .loading { opacity: .6; padding: 8px; } + .route-label { + font-weight: 600; + font-size: 0.9em; + padding: 4px 0 2px 0; + opacity: .8; + } + svg { display: block; overflow: visible; } +`; + +/** + * A web component that renders Apache Camel route diagrams as interactive SVG. + * + * Attributes: + * src - URL of the route-structure dev console endpoint (required) + * refresh - polling interval in ms; 0 = disabled (default: 0) + * filter - route ID filter, forwarded as ?filter= query param (default: all routes) + * + * CSS custom properties (all optional): + * --crd-bg, --crd-fg, --crd-edge, --crd-font, --crd-font-size, --crd-stat + * --crd-color-{route,from,to,log,choice,when,otherwise,doTry,doCatch,doFinally,...,default} + * + * @since 4.21 + */ +class CamelRouteDiagram extends HTMLElement { + static observedAttributes = ['src', 'refresh', 'filter']; + + #src = ''; + #refresh = 0; + #filter = ''; + #timer = null; + #uid = Math.random().toString(36).slice(2); + #controller = null; + #data = null; + #error = null; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.#scheduleRefresh(); + this.#render(); + this.#doFetch(); + } + + disconnectedCallback() { + clearInterval(this.#timer); + this.#timer = null; + this.#controller?.abort(); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (oldValue === newValue) return; + switch (name) { + case 'src': + this.#src = newValue ?? ''; + if (this.isConnected) this.#doFetch(); + break; + case 'filter': + this.#filter = newValue ?? ''; + if (this.isConnected) this.#doFetch(); + break; + case 'refresh': + this.#refresh = Number(newValue) || 0; + if (this.isConnected) this.#scheduleRefresh(); + break; + } + } + + #scheduleRefresh() { + clearInterval(this.#timer); + this.#timer = null; + if (this.#refresh > 0) { + this.#timer = setInterval(() => this.#doFetch(), this.#refresh); + } + } + + async #doFetch() { + const src = this.#src?.trim(); + if (!src) return; + // Cancel any in-flight request so the last-sent response always wins. + this.#controller?.abort(); + this.#controller = new AbortController(); + try { + const url = new URL(src, location.href); + if (this.#filter) url.searchParams.set('filter', this.#filter); + url.searchParams.set('metric', 'true'); + const res = await fetch(url, { signal: this.#controller.signal }); + if (!res.ok) { + this.#error = `HTTP ${res.status} ${res.statusText}`; + this.#data = null; + this.#render(); + return; + } + const data = await res.json(); + if (!Array.isArray(data?.routes)) { + this.#error = 'Unexpected response: missing routes array'; + this.#data = null; + this.#render(); + return; + } + this.#data = data; + this.#error = null; + this.#render(); + } catch (e) { + if (e.name !== 'AbortError') { + this.#error = e.message; + this.#render(); + } + } + } + + #render() { + this.shadowRoot.innerHTML = this.#buildHTML(); + } + + #buildHTML() { + const style = ``; + if (this.#error) return `${style}

⚠ ${esc(this.#error)}

`; + if (!this.#data) return `${style}

Loading diagram…

`; + return style + this.#data.routes.map(r => this.#routeHTML(r)).join(''); + } + + #routeHTML(route) { + const { positions, width, height } = layoutRoute(route); + const ids = Object.keys(positions); + // The is defined inside the same it is used in so that + // url(#id) paint-server references resolve correctly in all browsers, + // including Firefox which does not resolve them across sibling elements. + const markerId = `arrow-${this.#uid}`; + return ` +
${esc(route.routeId)}
+ - - ${n.map(h=>this.#n(h,e,o))} - ${n.map(h=>this.#o(h,e[h]))} - - `}#n(t,e,s){let i=e[t];if(!i.parentId)return d;let n=e[i.parentId];if(!n)return d;let o=n.x+180/2,h=n.y+36,a=i.x+180/2,l=i.y,p=(h+l)/2;return W` this.#edgeHTML(id, positions, markerId)).join('')} + ${ids.map(id => this.#nodeHTML(positions[id])).join('')} + `; + } + + #edgeHTML(id, positions, markerId) { + const pos = positions[id]; + if (!pos.parentId) return ''; + const parent = positions[pos.parentId]; + if (!parent) return ''; + + const x1 = parent.x + NODE_W / 2; + const y1 = parent.y + NODE_H; + const x2 = pos.x + NODE_W / 2; + const y2 = pos.y; + const my = (y1 + y2) / 2; + + return ``}#o(t,e){let s=Xt(e.description??e.code),i=Zt(e.statistics),n=Kt(e.type),h=e.x+180/2+9,a=e.y+36/2+4;return W` - - `; + } + + #nodeHTML(pos) { + const label = truncate(pos.description ?? pos.code); + const stat = formatStat(pos.statistics); + const fill = nodeColor(pos.type); + const cx = pos.x + NODE_W / 2; + const tx = cx + 9; // shift label clear of the icon + const textY = pos.y + NODE_H / 2 + 4; + + return ` + + - + - ${Ct(Yt(e.type))} + ${iconFor(pos.type)} - - ${s} + ${esc(label)} - ${i?W` - - ${i} - `:d} - `}};customElements.define("camel-route-diagram",at); + ${stat ? ` + + ${esc(stat)} + ` : ''} + `; + } +} + +customElements.define('camel-route-diagram', CamelRouteDiagram); diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js.LEGAL.txt b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js.LEGAL.txt deleted file mode 100644 index 77c6a9556dc0c..0000000000000 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js.LEGAL.txt +++ /dev/null @@ -1,67 +0,0 @@ -Bundled license information: - -@lit/reactive-element/css-tag.js: - /** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -@lit/reactive-element/reactive-element.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/lit-html.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-element/lit-element.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/is-server.js: - /** - * @license - * Copyright 2022 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directive.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directives/unsafe-html.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -lit-html/directives/unsafe-svg.js: - /** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: BSD-3-Clause - */ - -Lucide (icon SVG paths inlined in camel-route-diagram.js): - /** - * @license - * ISC License - * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). - * All other copyright (c) for Lucide are held by Lucide Contributors 2022. - * SPDX-License-Identifier: ISC - * https://lucide.dev - */ diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramLayoutEngineTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramLayoutEngineTest.java new file mode 100644 index 0000000000000..6888824b5b7a6 --- /dev/null +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramLayoutEngineTest.java @@ -0,0 +1,234 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.diagram; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Ports the Vitest layout.test.js suite to JUnit, covering computeSubtreeWidth and assignPositions behaviour through + * the public layoutRoute() API. + * + * Java constants (default constructor, SCALE=2): nodeWidth = 360 (DEFAULT_BOX_WIDTH * SCALE) hGap = 180 (nodeWidth / 2) + * V_GAP = 80 (40 * SCALE) PADDING = 60 (30 * SCALE) + */ +class RouteDiagramLayoutEngineTest { + + private static final RouteDiagramLayoutEngine ENGINE = new RouteDiagramLayoutEngine(); + private static final int NODE_W = ENGINE.getNodeWidth(); // 360 + private static final int H_GAP = NODE_W / 2; // 180 + private static final int PADDING = RouteDiagramLayoutEngine.PADDING; // 60 + + // ─── helpers ───────────────────────────────────────────────────────────── + + private static RouteDiagramLayoutEngine.NodeInfo node(String type, String id, int level) { + RouteDiagramLayoutEngine.NodeInfo n = new RouteDiagramLayoutEngine.NodeInfo(); + n.type = type; + n.id = id; + n.level = level; + n.code = type; + return n; + } + + private static RouteDiagramLayoutEngine.RouteInfo route(RouteDiagramLayoutEngine.NodeInfo... nodes) { + RouteDiagramLayoutEngine.RouteInfo r = new RouteDiagramLayoutEngine.RouteInfo(); + r.routeId = "test"; + r.nodes.addAll(List.of(nodes)); + return r; + } + + private static RouteDiagramLayoutEngine.LayoutNode findNode( + RouteDiagramLayoutEngine.LayoutRoute lr, String id) { + return lr.nodes.stream() + .filter(n -> id.equals(n.id)) + .findFirst() + .orElseThrow(() -> new AssertionError("No layout node with id: " + id)); + } + + // ─── computeSubtreeWidth (verified through node positions) ─────────────── + + @Test + void leafNodeSubtreeWidthEqualsNodeWidth() { + // A single leaf node fills exactly one node-width slot. + // nodeX = PADDING + (subtreeWidth - NODE_W) / 2; if subtreeWidth == NODE_W, nodeX == PADDING. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("log", "l1", 0)), 0); + + RouteDiagramLayoutEngine.LayoutNode l1 = findNode(lr, "l1"); + assertThat(l1.x).as("leaf node must be placed at PADDING (subtreeWidth == nodeWidth)").isEqualTo(PADDING); + } + + @Test + void branchingEipSubtreeWidthIsSumOfBranchWidthsPlusGaps() { + // choice -> [when, otherwise], both leaves. + // subtreeWidth(choice) = NODE_W + H_GAP + NODE_W = NODE_W*2 + H_GAP + // when.x = PADDING (leftmost child) + // ow.x = PADDING + NODE_W + H_GAP + // gap between children = NODE_W + H_GAP + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("choice", "ch", 0), + node("when", "w1", 1), + node("otherwise", "ow", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode w1 = findNode(lr, "w1"); + RouteDiagramLayoutEngine.LayoutNode ow = findNode(lr, "ow"); + assertThat(ow.x - w1.x) + .as("gap between two leaf branches of a branching EIP must equal NODE_W + H_GAP") + .isEqualTo(NODE_W + H_GAP); + } + + @Test + void nonBranchingNodeSubtreeWidthIsMaxOfChildWidths() { + // route -> [from, to] (linear siblings, both leaves, equal widths). + // subtreeWidth(route) = max(NODE_W, NODE_W) = NODE_W + // Both children should be placed at x == PADDING. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("from", "f1", 1), + node("to", "t1", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode f1 = findNode(lr, "f1"); + RouteDiagramLayoutEngine.LayoutNode t1 = findNode(lr, "t1"); + assertThat(f1.x).as("first linear child x must equal PADDING").isEqualTo(PADDING); + assertThat(t1.x).as("second linear child x must equal PADDING").isEqualTo(PADDING); + } + + // ─── assignPositions ───────────────────────────────────────────────────── + + @Test + void linearChainEachNodeConnectsToItsVisualPredecessor() { + // route -> from -> log -> to (flat level-1 siblings processed linearly). + // f1.parentNode == r1, l1.parentNode == f1, t1.parentNode == l1. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("from", "f1", 1), + node("log", "l1", 1), + node("to", "t1", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode r1 = findNode(lr, "r1"); + RouteDiagramLayoutEngine.LayoutNode f1 = findNode(lr, "f1"); + RouteDiagramLayoutEngine.LayoutNode l1 = findNode(lr, "l1"); + RouteDiagramLayoutEngine.LayoutNode t1 = findNode(lr, "t1"); + + assertThat(r1.parentNode).as("root must have no parent").isNull(); + assertThat(f1.parentNode).as("f1 must connect from r1").isSameAs(r1); + assertThat(l1.parentNode).as("l1 must connect from f1, not r1").isSameAs(f1); + assertThat(t1.parentNode).as("t1 must connect from l1, not r1").isSameAs(l1); + } + + @Test + void singleChainRouteAssignsStrictlyIncreasingYValues() { + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("from", "f1", 1), + node("log", "l1", 1), + node("to", "t1", 1)), + 0); + + int yr1 = findNode(lr, "r1").y; + int yf1 = findNode(lr, "f1").y; + int yl1 = findNode(lr, "l1").y; + int yt1 = findNode(lr, "t1").y; + + assertThat(yf1).as("f1.y must be below r1").isGreaterThan(yr1); + assertThat(yl1).as("l1.y must be below f1").isGreaterThan(yf1); + assertThat(yt1).as("t1.y must be below l1").isGreaterThan(yl1); + } + + @Test + void branchingEipChildrenAreLaidOutSideBySide() { + // choice -> [when, otherwise]: children must share the same y, different x. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("choice", "ch", 0), + node("when", "w1", 1), + node("otherwise", "ow", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode w1 = findNode(lr, "w1"); + RouteDiagramLayoutEngine.LayoutNode ow = findNode(lr, "ow"); + + assertThat(w1.x).as("when must be to the left of otherwise").isLessThan(ow.x); + assertThat(w1.y).as("both branches must start at the same y").isEqualTo(ow.y); + } + + @Test + void nextSiblingIsPlacedBelowDeepestDescendantOfPreviousSibling() { + // route -> choice -> [when -> log_a, otherwise -> log_b], log_after + // log_after must be below BOTH log_a and log_b. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("choice", "ch", 1), + node("when", "wh", 2), + node("log", "la", 3), + node("otherwise", "ow", 2), + node("log", "lb", 3), + node("log", "lafter", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode la = findNode(lr, "la"); + RouteDiagramLayoutEngine.LayoutNode lb = findNode(lr, "lb"); + RouteDiagramLayoutEngine.LayoutNode lafter = findNode(lr, "lafter"); + + assertThat(lafter.y) + .as("lafter must be below la") + .isGreaterThan(la.y + la.height); + assertThat(lafter.y) + .as("lafter must be below lb") + .isGreaterThan(lb.y + lb.height); + } + + @Test + void linearChainAfterBranchingEipConnectsFromBranchingEip() { + // route -> choice -> [when, otherwise], log_after + // log_after.parentNode must be the choice node, not when or otherwise. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("choice", "ch", 1), + node("when", "wh", 2), + node("otherwise", "ow", 2), + node("log", "lafter", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode ch = findNode(lr, "ch"); + RouteDiagramLayoutEngine.LayoutNode lafter = findNode(lr, "lafter"); + + assertThat(lafter.parentNode) + .as("node after a branching EIP must connect from the branching EIP itself") + .isSameAs(ch); + } + + @Test + void layoutRouteMaxYEqualsDeepestNodeBottom() { + // route -> from -> log: maxY must equal log.y + log.height. + RouteDiagramLayoutEngine.LayoutRoute lr = ENGINE.layoutRoute( + route(node("route", "r1", 0), + node("from", "f1", 1), + node("log", "l1", 1)), + 0); + + RouteDiagramLayoutEngine.LayoutNode l1 = findNode(lr, "l1"); + assertThat(lr.maxY) + .as("maxY must equal the bottom of the deepest node") + .isEqualTo(l1.y + l1.height); + } +} diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java index 73aa3953da5d9..374a65dbc7551 100644 --- a/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java @@ -55,4 +55,17 @@ void bundledJsContainsCustomElementRegistration() throws IOException { .contains("camel-route-diagram"); } } + + @Test + void thirdPartyNoticesMentionsLucide() throws IOException { + try (InputStream is = getClass().getClassLoader() + .getResourceAsStream("META-INF/resources/camel/diagram/THIRD-PARTY-NOTICES.txt")) { + assertThat(is).isNotNull(); + String content = new String(is.readAllBytes(), StandardCharsets.UTF_8); + assertThat(content) + .as("THIRD-PARTY-NOTICES.txt must attribute Lucide with ISC license") + .contains("Lucide") + .contains("ISC"); + } + } } From 7498acc81afef4e80420006fc507f76c090398cd Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:30:44 -0400 Subject: [PATCH 15/18] CAMEL-23636: fix 8 code-review findings in camel-diagram - RouteDiagramHelperTest: remove public final modifier, drop wrong Javadoc copied from production class, fix import order (java.* before org.*), remove unused RouteInfo import, replace weak size-only assertions with containsExactly/endsWith checks, and split into four focused test methods - Add two new tests pinning the wrapText tail-truncation behavior that changed during consolidation into RouteDiagramHelper: one for the "remaining fits on last line without ellipsis" path, one for truncation - camel-route-diagram.js: append route index to SVG marker ID so multiple routes in one shadow root no longer share duplicate IDs - camel-route-diagram.js: remove this.#data = null from HTTP and parse error paths so all error branches consistently preserve the last good data, matching the network-error catch block behavior Co-Authored-By: Claude Sonnet 4.6 --- .../diagram/RouteDiagramAsciiRenderer.java | 47 +--------------- .../camel/diagram/RouteDiagramHelper.java | 52 ++++++++++++++++-- .../camel/diagram/RouteDiagramRenderer.java | 4 +- .../camel/diagram/TopologyAsciiRenderer.java | 47 ++-------------- .../camel/diagram/camel-route-diagram.js | 14 ++--- .../camel/diagram/RouteDiagramHelperTest.java | 55 +++++++++++++++++++ .../camel/diagram/RouteDiagramTest.java | 6 +- .../camel/diagram/TopologyDiagramTest.java | 10 ---- 8 files changed, 119 insertions(+), 116 deletions(-) create mode 100644 components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramHelperTest.java diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java index d32e850300c00..b3a30dafb103b 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java @@ -36,7 +36,6 @@ */ public class RouteDiagramAsciiRenderer { - private static final int MAX_WRAP_LINES = 3; private static final int Y_SCALE = 20; private static final int MIN_BOX_WIDTH = 16; private static final int X_DIVISOR = 15; @@ -484,51 +483,7 @@ private int boxHeight(LayoutNode node) { private List rewrapText(LayoutNode node, int maxWidth) { String label = String.join("", node.wrappedLines); - return wrapText(label, maxWidth); - } - - static List wrapText(String text, int maxWidth) { - if (maxWidth <= 0 || text.length() <= maxWidth) { - return List.of(text); - } - - List lines = new ArrayList<>(); - String remaining = text; - - while (!remaining.isEmpty() && lines.size() < MAX_WRAP_LINES) { - if (remaining.length() <= maxWidth) { - lines.add(remaining); - remaining = ""; - break; - } - - int breakAt = -1; - for (int i = 0; i < maxWidth && i < remaining.length(); i++) { - char c = remaining.charAt(i); - if (c == ' ' || c == ':' || c == '/' || c == '.' || c == ',' || c == '&' || c == '?') { - breakAt = i + 1; - } - } - if (breakAt <= 0) { - breakAt = maxWidth; - } - - lines.add(remaining.substring(0, breakAt).stripTrailing()); - remaining = remaining.substring(breakAt).stripLeading(); - } - - if (!remaining.isEmpty()) { - int lastIdx = lines.size() - 1; - String lastLine = lines.get(lastIdx); - if (lastLine.length() + remaining.length() <= maxWidth) { - lines.set(lastIdx, lastLine + remaining); - } else { - String combined = lastLine + remaining; - lines.set(lastIdx, combined.substring(0, Math.max(1, maxWidth - 3)) + "..."); - } - } - - return lines; + return RouteDiagramHelper.wrapText(label, maxWidth); } private int toCol(int pixelX) { diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java index 1d298aa1f7929..c1635d5b69842 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java @@ -36,6 +36,8 @@ */ public final class RouteDiagramHelper { + static final int MAX_WRAP_LINES = 3; + private RouteDiagramHelper() { } @@ -52,12 +54,10 @@ public static List parseRoutes(JsonObject jo) { return routes; } - for (int i = 0; i < arr.size(); i++) { - Object item = arr.get(i); - if (!(item instanceof JsonObject)) { + for (Object item : arr) { + if (!(item instanceof JsonObject o)) { continue; } - JsonObject o = (JsonObject) item; RouteInfo route = new RouteInfo(); route.routeId = o.getString("routeId"); String source = o.getString("source"); @@ -120,6 +120,50 @@ public static List parseRoutes(JsonObject jo) { return routes; } + static List wrapText(String text, int maxWidth) { + if (maxWidth <= 0 || text.length() <= maxWidth) { + return List.of(text); + } + + List lines = new ArrayList<>(); + String remaining = text; + + while (!remaining.isEmpty() && lines.size() < MAX_WRAP_LINES) { + if (remaining.length() <= maxWidth) { + lines.add(remaining); + remaining = ""; + break; + } + + int breakAt = -1; + for (int i = 0; i < maxWidth && i < remaining.length(); i++) { + char c = remaining.charAt(i); + if (c == ' ' || c == ':' || c == '/' || c == '.' || c == ',' || c == '&' || c == '?') { + breakAt = i + 1; + } + } + if (breakAt <= 0) { + breakAt = maxWidth; + } + + lines.add(remaining.substring(0, breakAt).stripTrailing()); + remaining = remaining.substring(breakAt).stripLeading(); + } + + if (!remaining.isEmpty()) { + int lastIdx = lines.size() - 1; + String lastLine = lines.get(lastIdx); + if (lastLine.length() + remaining.length() <= maxWidth) { + lines.set(lastIdx, lastLine + remaining); + } else { + String combined = lastLine + remaining; + lines.set(lastIdx, combined.substring(0, Math.max(1, maxWidth - 3)) + "..."); + } + } + + return lines; + } + public enum HighlightStyle { SUCCESS, FAIL diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java index 9da413e0ef0c0..4fad14257620f 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java @@ -100,7 +100,7 @@ public RouteDiagramRenderer(int nodeWidth, int fontSizeScaled, boolean metrics) public RouteDiagramRenderer(int nodeWidth, int fontSizeScaled, int nodeTextPadding, boolean metrics) { this.nodeWidth = nodeWidth; this.fontSizeNode = fontSizeScaled; - this.fontSizeLabel = fontSizeScaled + 1 * SCALE; + this.fontSizeLabel = fontSizeScaled + SCALE; this.nodeTextPadding = nodeTextPadding; this.metrics = metrics; } @@ -167,7 +167,7 @@ private static Color parseColor(String value) { } Integer idx = Colors.rgbColor(value); if (idx != null) { - return new Color(Colors.rgbColor(idx.intValue())); + return new Color(Colors.rgbColor(idx)); } return null; } diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java index 9c1b6b9b34004..0803f642cc382 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java @@ -25,6 +25,8 @@ import org.apache.camel.diagram.TopologyLayoutEngine.TopologyLayoutNode; import org.apache.camel.diagram.TopologyLayoutEngine.TopologyLayoutResult; +import static org.apache.camel.diagram.RouteDiagramHelper.wrapText; + /** * Renders topology diagrams as ASCII art or Unicode box-drawing text. */ @@ -33,7 +35,7 @@ public class TopologyAsciiRenderer { private static final int Y_SCALE = 20; private static final int MIN_BOX_WIDTH = 16; private static final int X_DIVISOR = 15; - private static final int MAX_WRAP_LINES = 3; + private static final int MAX_WRAP_LINES = RouteDiagramHelper.MAX_WRAP_LINES; private static final char UNI_H = '─'; private static final char UNI_V = '│'; @@ -151,8 +153,7 @@ private void drawNode(char[][] grid, TopologyLayoutNode node) { line1 = node.routeId; } - List lines = new ArrayList<>(); - lines.addAll(wrapText(line1, boxWidth - 4)); + List lines = new ArrayList<>(wrapText(line1, boxWidth - 4)); if (!isExternalNode(node) && !showDescription) { String line2 = "(" + node.from + ")"; List fromLines = wrapText(line2, boxWidth - 4); @@ -331,46 +332,6 @@ private int boxHeight(TopologyLayoutNode node) { return 2 + Math.min(lines, MAX_WRAP_LINES + 1); } - static List wrapText(String text, int maxWidth) { - if (maxWidth <= 0 || text.length() <= maxWidth) { - return new ArrayList<>(List.of(text)); - } - - List lines = new ArrayList<>(); - String remaining = text; - - while (!remaining.isEmpty() && lines.size() < MAX_WRAP_LINES) { - if (remaining.length() <= maxWidth) { - lines.add(remaining); - remaining = ""; - break; - } - - int breakAt = -1; - for (int i = 0; i < maxWidth && i < remaining.length(); i++) { - char c = remaining.charAt(i); - if (c == ' ' || c == ':' || c == '/' || c == '.' || c == ',' || c == '&' || c == '?') { - breakAt = i + 1; - } - } - if (breakAt <= 0) { - breakAt = maxWidth; - } - - lines.add(remaining.substring(0, breakAt).stripTrailing()); - remaining = remaining.substring(breakAt).stripLeading(); - } - - if (!remaining.isEmpty()) { - int lastIdx = lines.size() - 1; - String lastLine = lines.get(lastIdx); - String combined = lastLine + remaining; - lines.set(lastIdx, combined.substring(0, Math.max(1, maxWidth - 3)) + "..."); - } - - return lines; - } - private String applyAnsiColors(String plain) { if (counterPositions.isEmpty()) { return plain; diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index b6dd8395c7c6f..643320a484e75 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -339,14 +339,12 @@ class CamelRouteDiagram extends HTMLElement { const res = await fetch(url, { signal: this.#controller.signal }); if (!res.ok) { this.#error = `HTTP ${res.status} ${res.statusText}`; - this.#data = null; this.#render(); return; } const data = await res.json(); if (!Array.isArray(data?.routes)) { this.#error = 'Unexpected response: missing routes array'; - this.#data = null; this.#render(); return; } @@ -369,16 +367,16 @@ class CamelRouteDiagram extends HTMLElement { const style = ``; if (this.#error) return `${style}

⚠ ${esc(this.#error)}

`; if (!this.#data) return `${style}

Loading diagram…

`; - return style + this.#data.routes.map(r => this.#routeHTML(r)).join(''); + return style + this.#data.routes.map((r, i) => this.#routeHTML(r, i)).join(''); } - #routeHTML(route) { + #routeHTML(route, routeIdx) { const { positions, width, height } = layoutRoute(route); const ids = Object.keys(positions); - // The is defined inside the same it is used in so that - // url(#id) paint-server references resolve correctly in all browsers, - // including Firefox which does not resolve them across sibling elements. - const markerId = `arrow-${this.#uid}`; + // Each route gets its own markerId so that elements sharing a shadow root + // do not carry duplicate IDs. The route index makes each ID unique within the + // component while keeping url(#id) resolution local to each element. + const markerId = `arrow-${this.#uid}-${routeIdx}`; return `
${esc(route.routeId)}
lines = RouteDiagramHelper.wrapText("kafka:my-topic?brokers=localhost:9092", 15); + assertThat(lines).hasSizeGreaterThan(1); + assertThat(String.join("", lines)).contains("kafka").contains("localhost"); + } + + @Test + void wrapTextRemainingThatFitsOnLastLineIsAppendedWithoutEllipsis() { + // "aaa bbb ccc dd" with maxWidth=5: + // round 1 → "aaa", round 2 → "bbb", round 3 → "ccc", remaining = "dd" + // lastLine("ccc").len=3 + remaining("dd").len=2 = 5 <= maxWidth → append, no "..." + List lines = RouteDiagramHelper.wrapText("aaa bbb ccc dd", 5); + assertThat(lines).containsExactly("aaa", "bbb", "cccdd"); + } + + @Test + void wrapTextRemainingThatDoesNotFitOnLastLineIsTruncatedWithEllipsis() { + // Same structure but remaining = "ddddd" (len 5): 3+5=8 > 5 → truncate with "..." + List lines = RouteDiagramHelper.wrapText("aaa bbb ccc ddddd", 5); + assertThat(lines).hasSize(3); + assertThat(lines.get(2)).endsWith("..."); + } +} diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java index 4024de903f33d..078d67e9b669c 100644 --- a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java @@ -1041,14 +1041,14 @@ void testUnicodeDiagramWithScopeBox() { @Test void testAsciiWrapTextShort() { - List lines = RouteDiagramAsciiRenderer.wrapText("timer:tick", 20); + List lines = RouteDiagramHelper.wrapText("timer:tick", 20); assertEquals(1, lines.size()); assertEquals("timer:tick", lines.get(0)); } @Test void testAsciiWrapTextWrap() { - List lines = RouteDiagramAsciiRenderer.wrapText("kafka:my-topic?brokers=localhost:9092", 20); + List lines = RouteDiagramHelper.wrapText("kafka:my-topic?brokers=localhost:9092", 20); assertTrue(lines.size() > 1, "Long text should wrap"); String rejoined = String.join("", lines); assertTrue(rejoined.contains("kafka:")); @@ -1058,7 +1058,7 @@ void testAsciiWrapTextWrap() { @Test void testAsciiWrapTextTruncate() { String veryLong = "a]".repeat(60); - List lines = RouteDiagramAsciiRenderer.wrapText(veryLong, 20); + List lines = RouteDiagramHelper.wrapText(veryLong, 20); assertTrue(lines.size() <= 3, "Should not exceed 3 lines"); assertTrue(lines.get(lines.size() - 1).endsWith("..."), "Truncated text should end with ..."); } diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java index d94497ef87eae..c9643c1d0cb3c 100644 --- a/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java @@ -274,16 +274,6 @@ void testJsonParsing() { assertEquals("internal", edges.get(0).connectionType); } - @Test - void testWrapText() { - List lines = TopologyAsciiRenderer.wrapText("short", 20); - assertEquals(1, lines.size()); - assertEquals("short", lines.get(0)); - - List wrapped = TopologyAsciiRenderer.wrapText("this is a longer text that needs wrapping", 15); - assertTrue(wrapped.size() > 1); - } - @Test void testOrderProcessingTopology() { List nodes = List.of( From a136e479814fcb1b183378c7257c144cd21975b6 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:31:58 -0400 Subject: [PATCH 16/18] CAMEL-23636: fix broken xref in upgrade guide The xref:components:others:diagram.adoc target does not exist in the published components catalog yet (diagram.adoc was added to main after the last release), so the PR doc-validation Antora build cannot resolve it. Replace the xref with a plain-text reference to avoid the build failure. Co-Authored-By: Claude Sonnet 4.6 --- .../modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc index db3dd92a2aeb5..f8e6addbef663 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc @@ -2510,4 +2510,4 @@ periodic refresh. It is theme-agnostic, respects `prefers-color-scheme` for automatic dark/light mode, and exposes CSS custom properties for full visual control. -See xref:components:others:diagram.adoc[Diagram] for usage instructions and theming options. +See the `camel-diagram` component documentation for usage instructions and theming options. From 299a7194329fd37220b9e585a2f7b83a849a9d30 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:27:54 -0400 Subject: [PATCH 17/18] CAMEL-23636: replace SVG marker arrowheads with inline polygons and use orthogonal edge paths Co-Authored-By: Claude Opus 4.6 --- .../camel/diagram/camel-route-diagram.js | 42 ++++----- .../camel/diagram/WebComponentBundleTest.java | 14 +++ .../src/test/resources/smoke-test.html | 86 +++++++++++++++++++ 3 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 components/camel-diagram/src/test/resources/smoke-test.html diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index 643320a484e75..694549bd81eb5 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -22,6 +22,7 @@ const NODE_H = 36; const H_GAP = NODE_W / 2; const V_GAP = 40; const PADDING = 30; +const ARROW_SIZE = 6; const BRANCHING_EIPS = new Set([ 'choice', 'multicast', 'doTry', 'loadBalance', 'recipientList', 'circuitBreaker', @@ -288,18 +289,21 @@ class CamelRouteDiagram extends HTMLElement { this.attachShadow({ mode: 'open' }); } + //noinspection JSUnusedGlobalSymbols connectedCallback() { this.#scheduleRefresh(); this.#render(); this.#doFetch(); } + //noinspection JSUnusedGlobalSymbols disconnectedCallback() { clearInterval(this.#timer); this.#timer = null; this.#controller?.abort(); } + //noinspection JSUnusedGlobalSymbols attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; switch (name) { @@ -373,26 +377,16 @@ class CamelRouteDiagram extends HTMLElement { #routeHTML(route, routeIdx) { const { positions, width, height } = layoutRoute(route); const ids = Object.keys(positions); - // Each route gets its own markerId so that elements sharing a shadow root - // do not carry duplicate IDs. The route index makes each ID unique within the - // component while keeping url(#id) resolution local to each element. - const markerId = `arrow-${this.#uid}-${routeIdx}`; return `
${esc(route.routeId)}
- - - - - - ${ids.map(id => this.#edgeHTML(id, positions, markerId)).join('')} + ${ids.map(id => this.#edgeHTML(id, positions)).join('')} ${ids.map(id => this.#nodeHTML(positions[id])).join('')} `; } - #edgeHTML(id, positions, markerId) { + #edgeHTML(id, positions) { const pos = positions[id]; if (!pos.parentId) return ''; const parent = positions[pos.parentId]; @@ -402,14 +396,22 @@ class CamelRouteDiagram extends HTMLElement { const y1 = parent.y + NODE_H; const x2 = pos.x + NODE_W / 2; const y2 = pos.y; - const my = (y1 + y2) / 2; - - return ``; + const endY = y2 - ARROW_SIZE / 2; + const edge = x1 === x2 + ? `M${x1},${y1} L${x2},${endY}` + : `M${x1},${y1} L${x1},${(y1 + y2) / 2} L${x2},${(y1 + y2) / 2} L${x2},${endY}`; + + return ` + + `; } #nodeHTML(pos) { diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java index 374a65dbc7551..9128b65a96ab0 100644 --- a/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/WebComponentBundleTest.java @@ -56,6 +56,20 @@ void bundledJsContainsCustomElementRegistration() throws IOException { } } + @Test + void bundledJsUsesArrowMarkerGeometryThatAnchorsAtTheTip() throws IOException { + try (InputStream is = getClass().getClassLoader() + .getResourceAsStream("META-INF/resources/camel/diagram/camel-route-diagram.js")) { + assertThat(is).isNotNull(); + String content = new String(is.readAllBytes(), StandardCharsets.UTF_8); + assertThat(content) + .as("branch connectors must render an explicit arrowhead at the path endpoint") + .contains("const ARROW_SIZE = 6") + .contains(" + + + + + + camel-route-diagram smoke test + + + + + +

camel-route-diagram smoke test

+ +
+

Auto theme (follows OS color scheme)

+ +
+ +
+

Light theme (forced)

+ + +
+ +
+

Dark theme (forced)

+ + +
+ + From 25b035bb78442d67f7e36cd8798ed57cf53f28a8 Mon Sep 17 00:00:00 2001 From: Adriano Machado <60320+ammachado@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:09:57 -0400 Subject: [PATCH 18/18] CAMEL-23636: fix node label rendering and multi-route layout in camel-route-diagram - Switch node text to left-aligned (text-anchor=start) after the icon, with per-node SVG clipPath to prevent overflow past the node's right edge; eliminates both the visual off-center appearance and icon/label overlap for long truncated labels - Add opaque --crd-node-bg background rect per node so semi-transparent fills no longer bleed edges that pass behind a node (e.g. the straight vertical edge through the middle branch of a 3-way multicast) - Render multiple routes side by side via flex-row layout on .wrap and a .route-col wrapper per route; single-route diagrams are unaffected - Smoke-test multi-route card uses overflow-x:scroll for an always-visible horizontal scrollbar Co-Authored-By: Claude Sonnet 4.6 --- .../camel/diagram/camel-route-diagram.js | 85 ++- .../src/test/resources/smoke-test.html | 703 +++++++++++++++++- 2 files changed, 725 insertions(+), 63 deletions(-) diff --git a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js index 694549bd81eb5..f4654b0ce9c5e 100644 --- a/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js +++ b/components/camel-diagram/src/main/resources/META-INF/resources/camel/diagram/camel-route-diagram.js @@ -236,17 +236,37 @@ function esc(s) { const COMPONENT_STYLE = ` :host { display: block; + /* + * fit-content makes the host expand to the SVG's intrinsic width so the + * parent scroll container sees real overflow and shows a scrollbar. + * min-width: 100% prevents collapsing when the diagram is narrower than + * the container. + */ + width: fit-content; + min-width: 100%; font-family: var(--crd-font, system-ui, sans-serif); font-size: var(--crd-font-size, 12px); - background: var(--crd-bg, transparent); color: var(--crd-fg, #1e293b); } @media (prefers-color-scheme: dark) { - :host { + :host { color: var(--crd-fg, #e2e8f0); } + } + /* Background on .wrap (not :host) so it tracks the SVG width on scroll. */ + .wrap { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 24px; + background: var(--crd-bg, transparent); + --crd-node-bg: var(--crd-bg, #ffffff); + } + @media (prefers-color-scheme: dark) { + .wrap { background: var(--crd-bg, #0f172a); - color: var(--crd-fg, #e2e8f0); + --crd-node-bg: var(--crd-bg, #0f172a); } } + .route-col { flex-shrink: 0; } .error { color: #ef4444; padding: 8px; } .loading { opacity: .6; padding: 8px; } .route-label { @@ -267,7 +287,7 @@ const COMPONENT_STYLE = ` * filter - route ID filter, forwarded as ?filter= query param (default: all routes) * * CSS custom properties (all optional): - * --crd-bg, --crd-fg, --crd-edge, --crd-font, --crd-font-size, --crd-stat + * --crd-bg, --crd-node-bg, --crd-fg, --crd-edge, --crd-font, --crd-font-size, --crd-stat * --crd-color-{route,from,to,log,choice,when,otherwise,doTry,doCatch,doFinally,...,default} * * @since 4.21 @@ -369,21 +389,29 @@ class CamelRouteDiagram extends HTMLElement { #buildHTML() { const style = ``; - if (this.#error) return `${style}

⚠ ${esc(this.#error)}

`; - if (!this.#data) return `${style}

Loading diagram…

`; - return style + this.#data.routes.map((r, i) => this.#routeHTML(r, i)).join(''); + if (this.#error) return `${style}

⚠ ${esc(this.#error)}

`; + if (!this.#data) return `${style}

Loading diagram…

`; + return style + `
${this.#data.routes.map((r, i) => this.#routeHTML(r, i)).join('')}
`; } #routeHTML(route, routeIdx) { const { positions, width, height } = layoutRoute(route); const ids = Object.keys(positions); - return ` + const pfx = `t${this.#uid}r${routeIdx}`; + const defs = ids.map(id => { + const p = positions[id]; + return `` + + ``; + }).join(''); + return `
${esc(route.routeId)}
+ ${defs} ${ids.map(id => this.#edgeHTML(id, positions)).join('')} - ${ids.map(id => this.#nodeHTML(positions[id])).join('')} - `; + ${ids.map(id => this.#nodeHTML(positions[id], `${pfx}${id}`)).join('')} + +
`; } #edgeHTML(id, positions) { @@ -414,36 +442,37 @@ class CamelRouteDiagram extends HTMLElement { fill="var(--crd-edge, #94a3b8)"/>`; } - #nodeHTML(pos) { - const label = truncate(pos.description ?? pos.code); - const stat = formatStat(pos.statistics); - const fill = nodeColor(pos.type); - const cx = pos.x + NODE_W / 2; - const tx = cx + 9; // shift label clear of the icon - const textY = pos.y + NODE_H / 2 + 4; + #nodeHTML(pos, clipId) { + const label = truncate(pos.description ?? pos.code); + const stat = formatStat(pos.statistics); + const fill = nodeColor(pos.type); + const textX = pos.x + 30; + const textY = pos.y + NODE_H / 2 + 4; return ` + - - ${iconFor(pos.type)} - - + ${esc(label)} ${stat ? ` - + ${esc(stat)} ` : ''} + + ${iconFor(pos.type)} + `; } } diff --git a/components/camel-diagram/src/test/resources/smoke-test.html b/components/camel-diagram/src/test/resources/smoke-test.html index 58579364dc4e6..8466d6ab05384 100644 --- a/components/camel-diagram/src/test/resources/smoke-test.html +++ b/components/camel-diagram/src/test/resources/smoke-test.html @@ -21,66 +21,699 @@ Local smoke test for the camel-route-diagram web component. Browsers block ES module imports from file:// URLs (CORS/null-origin). - Serve from this directory: + Serve from the src/ directory: cd components/camel-diagram/src/ python3 -m http.server 8080 open http://localhost:8080/test/resources/smoke-test.html + + All data is mocked — no running Camel instance needed. --> - camel-route-diagram smoke test + + camel-route-diagram · Smoke Test -

camel-route-diagram smoke test

-
-

Auto theme (follows OS color scheme)

- + + + + +
+
+

<camel-route-diagram>

+

+ A local verification page for the route-diagram web component. Each section exercises a different + EIP pattern, rendering feature, or edge case. All data is mocked — no running Camel instance required. +

+ + cd components/camel-diagram/src/
+ python3 -m http.server 8080 && open http://localhost:8080/test/resources/smoke-test.html +
+
+
+ + +
+ + +
+
+

Theme Variants

+ +
+
+ +
+
+

Auto (OS preference)

+

+ Follows prefers-color-scheme. No --crd-* variables + are set — the component picks light or dark based on your OS setting. +

+
+
+ +
+
+ +
+
+

Light (forced)

+

+ Override via inline style="--crd-bg:#fff; --crd-fg:#1e293b; --crd-edge:#94a3b8" + to pin the light palette regardless of OS setting. +

+
+
+ + +
+
+ +
+
+

Dark (forced)

+

+ Override via style="--crd-bg:#0f172a; --crd-fg:#e2e8f0; --crd-edge:#475569" + to pin the dark palette regardless of OS setting. +

+
+
+ + +
+
+ +
-
-

Light theme (forced)

- - + +
+
+

Content-Based Router

+ +
+
+
+

Order Router — choice with two when + otherwise

+

+ Routes incoming HTTP orders to premium, standard, or default fulfillment channels based on a + ${header.tier} value. Verifies that three side-by-side branches render correctly + and that the post-choice log node reconnects from the choice merge point. +

+
+
+ +
+
-
-

Dark theme (forced)

- - + +
+
+

Error Handling

+ +
+
+
+

Safe Processor — doTry wrapping a choice, + with doCatch and doFinally

+

+ A choice inside a doTry: the doTry acts as a branching EIP so its three + clauses (try block, doCatch, doFinally) are laid side-by-side. + The post-try log node reconnects from the doTry merge point. + Mirrors the testChoiceInsideDoTryNoSpuriousMergeConnection layout-engine test. +

+
+
+ +
+
+ + +
+
+

Scatter-Gather & Resilience

+ + +
+
+ +
+
+

Order Fan-out — multicast to three branches

+

+ Sends each order simultaneously to billing, shipping, and notification routes. + Verifies that three to nodes are placed side-by-side and that the + post-multicast log reconnects from the multicast merge point. +

+
+
+ +
+
+ +
+
+

Resilient HTTP Call — circuitBreaker with fallback

+

+ Protects a downstream payment-service call with Resilience4j. The onFallback + branch is laid side-by-side with the main call. The final log reconnects + from the circuitBreaker merge point. +

+
+
+ +
+
+ +
+
+ + +
+
+

Exchange Statistics

+ +
+
+
+

High-Throughput Event Consumer — per-node exchangesTotal / exchangesFailed

+

+ When a node has a statistics object the component renders + ✓ successes / ✗ failures beneath the label. + Here the otherwise branch has accumulated 32 failures out of 14 189 + exchanges — spot the error rate at a glance. +

+
+
+ +
+
+
+ + +
+
+

Long URI — Text Wrapping

+ +
+
+
+

Kafka Consumer — multi-option URI wrapped inside the node box

+

+ A from node with a long Kafka URI + (brokers=…&groupId=…&autoOffsetReset=…) verifies that the label + wraps gracefully inside the fixed-width box without overflowing or truncating silently. + Mirrors testTextWrappingLongLabel. +

+
+
+ +
+
+
+ + +
+
+

Multi-Route — Order-Processing Pipeline

+ +
+
+
+

Three-route pipeline rendered from a single { routes: […] } response

+

+ The generator fires a timer and hands off to processor + (which routes valid/invalid orders via choice), which in turn delegates valid + orders to validator for publication to Kafka. + Nodes in the generator route use the description field for human-readable labels. +

+
+
+ +
+
+
+ +
+ + + +