mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge remote-tracking branch 'upstream/develop' into gh-5747-rendering-util-types
This commit is contained in:
commit
a45588ce7c
@ -25,6 +25,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
|||||||
'sankey',
|
'sankey',
|
||||||
'block',
|
'block',
|
||||||
'packet',
|
'packet',
|
||||||
|
'architecture',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
6
.changeset/famous-bananas-join.md
Normal file
6
.changeset/famous-bananas-join.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix for self loops in cluster
|
||||||
|
Supporting legacy defaultRenderer directive
|
5
.changeset/khaki-items-leave.md
Normal file
5
.changeset/khaki-items-leave.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(er): allow multi-line relationship labels
|
9
.changeset/nice-flowers-yawn.md
Normal file
9
.changeset/nice-flowers-yawn.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
'@mermaid-js/docs': patch
|
||||||
|
'@mermaid-js/parser': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
New Diagram: Architecture
|
||||||
|
|
||||||
|
Adds architecture diagrams which allows users to show relations between services.
|
6
.changeset/pants-things-go.md
Normal file
6
.changeset/pants-things-go.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'@mermaid-js/layout-elk': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Updates to the default elk configuration
|
||||||
|
feat: exposing cycleBreakingStrategy to the configuration so that it can be modified suing the configuration.
|
5
.changeset/silly-apples-glow.md
Normal file
5
.changeset/silly-apples-glow.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: Move icons to architecture, remove full icon sets to reduce bundle size
|
@ -55,6 +55,7 @@ GENERICTYPE
|
|||||||
getBoundarys
|
getBoundarys
|
||||||
grammr
|
grammr
|
||||||
graphtype
|
graphtype
|
||||||
|
halign
|
||||||
iife
|
iife
|
||||||
interp
|
interp
|
||||||
introdcued
|
introdcued
|
||||||
@ -66,6 +67,7 @@ Kaufmann
|
|||||||
keyify
|
keyify
|
||||||
LABELPOS
|
LABELPOS
|
||||||
LABELTYPE
|
LABELTYPE
|
||||||
|
layoutstop
|
||||||
lcov
|
lcov
|
||||||
LEFTOF
|
LEFTOF
|
||||||
Lexa
|
Lexa
|
||||||
@ -138,6 +140,7 @@ tsdoc
|
|||||||
typeof
|
typeof
|
||||||
typestr
|
typestr
|
||||||
unshift
|
unshift
|
||||||
|
urlsafe
|
||||||
verifymethod
|
verifymethod
|
||||||
VERIFYMTHD
|
VERIFYMTHD
|
||||||
WARN_DOCSDIR_DOESNT_MATCH
|
WARN_DOCSDIR_DOESNT_MATCH
|
||||||
|
@ -24,6 +24,7 @@ Doctave
|
|||||||
DokuWiki
|
DokuWiki
|
||||||
dompurify
|
dompurify
|
||||||
elkjs
|
elkjs
|
||||||
|
fcose
|
||||||
fontawesome
|
fontawesome
|
||||||
Foswiki
|
Foswiki
|
||||||
Gitea
|
Gitea
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
Adamiecki
|
Adamiecki
|
||||||
arrowend
|
arrowend
|
||||||
|
Bendpoints
|
||||||
bmatrix
|
bmatrix
|
||||||
braintree
|
braintree
|
||||||
catmull
|
catmull
|
||||||
|
@ -4,3 +4,4 @@ handDrawn
|
|||||||
KOEPF
|
KOEPF
|
||||||
neato
|
neato
|
||||||
newbranch
|
newbranch
|
||||||
|
validify
|
||||||
|
10
.github/workflows/autofix.yml
vendored
10
.github/workflows/autofix.yml
vendored
@ -7,17 +7,19 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
autofix:
|
autofix:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
# uses version from "packageManager" field in package.json
|
# uses version from "packageManager" field in package.json
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -40,4 +42,4 @@ jobs:
|
|||||||
working-directory: ./packages/mermaid
|
working-directory: ./packages/mermaid
|
||||||
run: pnpm run docs:build
|
run: pnpm run docs:build
|
||||||
|
|
||||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c # main
|
||||||
|
8
.github/workflows/build-docs.yml
vendored
8
.github/workflows/build-docs.yml
vendored
@ -8,6 +8,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@ -16,12 +18,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
49
.github/workflows/build.yml
vendored
49
.github/workflows/build.yml
vendored
@ -1,49 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push: {}
|
|
||||||
merge_group:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-mermaid:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
# uses version from "packageManager" field in package.json
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
cache: pnpm
|
|
||||||
node-version-file: '.node-version'
|
|
||||||
|
|
||||||
- name: Install Packages
|
|
||||||
run: |
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
env:
|
|
||||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
|
||||||
|
|
||||||
- name: Run Build
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Upload Mermaid Build as Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: mermaid-build
|
|
||||||
path: packages/mermaid/dist
|
|
||||||
|
|
||||||
- name: Upload Mermaid Mindmap Build as Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: mermaid-mindmap-build
|
|
||||||
path: packages/mermaid-mindmap/dist
|
|
2
.github/workflows/check-readme-in-sync.yml
vendored
2
.github/workflows/check-readme-in-sync.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- name: Check for difference in README.md and docs/README.md
|
- name: Check for difference in README.md and docs/README.md
|
||||||
run: |
|
run: |
|
||||||
|
26
.github/workflows/checks.yml
vendored
26
.github/workflows/checks.yml
vendored
@ -1,26 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
merge_group:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
|
|
||||||
name: Static analysis on Test files
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: check tests
|
|
||||||
if: github.repository_owner == 'mermaid-js'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: testomatio/check-tests@stable
|
|
||||||
with:
|
|
||||||
framework: cypress
|
|
||||||
tests: './cypress/e2e/**/**.spec.js'
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
has-tests-label: true
|
|
11
.github/workflows/codeql.yml
vendored
11
.github/workflows/codeql.yml
vendored
@ -11,6 +11,9 @@ on:
|
|||||||
- synchronize
|
- synchronize
|
||||||
- ready_for_review
|
- ready_for_review
|
||||||
|
|
||||||
|
permissions: # added using https://github.com/step-security/secure-repo
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: Analyze
|
name: Analyze
|
||||||
@ -29,11 +32,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||||
with:
|
with:
|
||||||
config-file: ./.github/codeql/codeql-config.yml
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
@ -45,7 +48,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@ -59,4 +62,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -15,6 +15,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||||
|
10
.github/workflows/e2e-applitools.yml
vendored
10
.github/workflows/e2e-applitools.yml
vendored
@ -11,6 +11,8 @@ on:
|
|||||||
default: master
|
default: master
|
||||||
description: 'Parent branch to use for PRs'
|
description: 'Parent branch to use for PRs'
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@ -30,13 +32,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
|
echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
# uses version from "packageManager" field in package.json
|
# uses version from "packageManager" field in package.json
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ jobs:
|
|||||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||||
|
|
||||||
- name: Cypress run
|
- name: Cypress run
|
||||||
uses: cypress-io/github-action@v4
|
uses: cypress-io/github-action@d79d2d530a66e641eb4a5f227e13bc985c60b964 # v4.2.2
|
||||||
id: cypress
|
id: cypress
|
||||||
with:
|
with:
|
||||||
start: pnpm run dev
|
start: pnpm run dev
|
||||||
|
34
.github/workflows/e2e.yml
vendored
34
.github/workflows/e2e.yml
vendored
@ -2,11 +2,15 @@ name: E2E
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches:
|
||||||
- 'gh-readonly-queue/**'
|
- develop
|
||||||
|
- master
|
||||||
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@ -32,15 +36,15 @@ jobs:
|
|||||||
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
|
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
|
||||||
options: --user 1001
|
options: --user 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
- name: Cache snapshots
|
- name: Cache snapshots
|
||||||
id: cache-snapshot
|
id: cache-snapshot
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
save-always: true
|
save-always: true
|
||||||
path: ./cypress/snapshots
|
path: ./cypress/snapshots
|
||||||
@ -49,13 +53,13 @@ jobs:
|
|||||||
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
|
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
|
||||||
- name: Switch to base branch
|
- name: Switch to base branch
|
||||||
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.targetHash }}
|
ref: ${{ env.targetHash }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||||
with:
|
with:
|
||||||
# just perform install
|
# just perform install
|
||||||
runTests: false
|
runTests: false
|
||||||
@ -78,26 +82,26 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
containers: [1, 2, 3, 4]
|
containers: [1, 2, 3, 4]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
# uses version from "packageManager" field in package.json
|
# uses version from "packageManager" field in package.json
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
|
||||||
# These cached snapshots are downloaded, providing the reference snapshots.
|
# These cached snapshots are downloaded, providing the reference snapshots.
|
||||||
- name: Cache snapshots
|
- name: Cache snapshots
|
||||||
id: cache-snapshot
|
id: cache-snapshot
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: ./cypress/snapshots
|
path: ./cypress/snapshots
|
||||||
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
|
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||||
with:
|
with:
|
||||||
runTests: false
|
runTests: false
|
||||||
|
|
||||||
@ -113,7 +117,7 @@ jobs:
|
|||||||
# Install NPM dependencies, cache them correctly
|
# Install NPM dependencies, cache them correctly
|
||||||
# and run all Cypress tests
|
# and run all Cypress tests
|
||||||
- name: Cypress run
|
- name: Cypress run
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||||
id: cypress
|
id: cypress
|
||||||
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
|
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
|
||||||
# Otherwise (e.g. if running from fork), we run on a single container only
|
# Otherwise (e.g. if running from fork), we run on a single container only
|
||||||
@ -137,7 +141,7 @@ jobs:
|
|||||||
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
|
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
|
||||||
|
|
||||||
- name: Upload Coverage to Codecov
|
- name: Upload Coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||||
# Run step only pushes to develop and pull_requests
|
# Run step only pushes to develop and pull_requests
|
||||||
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
|
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
|
||||||
with:
|
with:
|
||||||
|
8
.github/workflows/issue-triage.yml
vendored
8
.github/workflows/issue-triage.yml
vendored
@ -4,11 +4,17 @@ on:
|
|||||||
issues:
|
issues:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
|
permissions: # added using https://github.com/step-security/secure-repo
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
|
permissions:
|
||||||
|
issues: write # for andymckay/labeler to label issues
|
||||||
|
pull-requests: write # for andymckay/labeler to label PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: andymckay/labeler@1.0.4
|
- uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4
|
||||||
with:
|
with:
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
add-labels: 'Status: Triage'
|
add-labels: 'Status: Triage'
|
||||||
|
9
.github/workflows/link-checker.yml
vendored
9
.github/workflows/link-checker.yml
vendored
@ -19,6 +19,9 @@ on:
|
|||||||
# * is a special character in YAML so you have to quote this string
|
# * is a special character in YAML so you have to quote this string
|
||||||
- cron: '30 8 * * *'
|
- cron: '30 8 * * *'
|
||||||
|
|
||||||
|
permissions: # added using https://github.com/step-security/secure-repo
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
link-checker:
|
link-checker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -26,17 +29,17 @@ jobs:
|
|||||||
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting
|
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- name: Restore lychee cache
|
- name: Restore lychee cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||||
with:
|
with:
|
||||||
path: .lycheecache
|
path: .lycheecache
|
||||||
key: cache-lychee-${{ github.sha }}
|
key: cache-lychee-${{ github.sha }}
|
||||||
restore-keys: cache-lychee-
|
restore-keys: cache-lychee-
|
||||||
|
|
||||||
- name: Link Checker
|
- name: Link Checker
|
||||||
uses: lycheeverse/lychee-action@v1.9.3
|
uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a # v1.9.3
|
||||||
with:
|
with:
|
||||||
args: >-
|
args: >-
|
||||||
--config .github/lychee.toml
|
--config .github/lychee.toml
|
||||||
|
27
.github/workflows/lint.yml
vendored
27
.github/workflows/lint.yml
vendored
@ -4,26 +4,32 @@ on:
|
|||||||
push:
|
push:
|
||||||
merge_group:
|
merge_group:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
docker-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
|
- uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
|
||||||
|
with:
|
||||||
|
verbose: true
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
# uses version from "packageManager" field in package.json
|
# uses version from "packageManager" field in package.json
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -82,3 +88,10 @@ jobs:
|
|||||||
working-directory: ./packages/mermaid
|
working-directory: ./packages/mermaid
|
||||||
continue-on-error: ${{ github.event_name == 'push' }}
|
continue-on-error: ${{ github.event_name == 'push' }}
|
||||||
run: pnpm run docs:verify
|
run: pnpm run docs:verify
|
||||||
|
|
||||||
|
- uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable
|
||||||
|
with:
|
||||||
|
framework: cypress
|
||||||
|
tests: './cypress/e2e/**/**.spec.js'
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
has-tests-label: true
|
||||||
|
2
.github/workflows/pr-labeler.yml
vendored
2
.github/workflows/pr-labeler.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
pull-requests: write # write permission is required to label PRs
|
pull-requests: write # write permission is required to label PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Label PR
|
- name: Label PR
|
||||||
uses: release-drafter/release-drafter@v6
|
uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v6.0.0
|
||||||
with:
|
with:
|
||||||
config-name: pr-labeler.yml
|
config-name: pr-labeler.yml
|
||||||
disable-autolabeler: false
|
disable-autolabeler: false
|
||||||
|
12
.github/workflows/publish-docs.yml
vendored
12
.github/workflows/publish-docs.yml
vendored
@ -23,12 +23,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -37,13 +37,13 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v4
|
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
|
||||||
|
|
||||||
- name: Run Build
|
- name: Run Build
|
||||||
run: pnpm --filter mermaid run docs:build:vitepress
|
run: pnpm --filter mermaid run docs:build:vitepress
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
|
||||||
with:
|
with:
|
||||||
path: packages/mermaid/src/vitepress/.vitepress/dist
|
path: packages/mermaid/src/vitepress/.vitepress/dist
|
||||||
|
|
||||||
@ -56,4 +56,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||||
|
@ -9,14 +9,14 @@ jobs:
|
|||||||
publish-preview:
|
publish-preview:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -28,7 +28,7 @@ jobs:
|
|||||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||||
|
|
||||||
- name: Install Json
|
- name: Install Json
|
||||||
run: npm i json --global
|
run: npm i json@11.0.0 --global
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
working-directory: ./packages/mermaid
|
working-directory: ./packages/mermaid
|
||||||
|
47
.github/workflows/release-publish.yml
vendored
47
.github/workflows/release-publish.yml
vendored
@ -1,47 +0,0 @@
|
|||||||
name: Publish release
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: fregante/setup-git-user@v2
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
# uses version from "packageManager" field in package.json
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
cache: pnpm
|
|
||||||
node-version-file: '.node-version'
|
|
||||||
|
|
||||||
- name: Install Packages
|
|
||||||
run: |
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
npm i json --global
|
|
||||||
env:
|
|
||||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
|
||||||
|
|
||||||
- name: Prepare release
|
|
||||||
run: |
|
|
||||||
VERSION=${GITHUB_REF:10}
|
|
||||||
echo "Preparing release $VERSION"
|
|
||||||
git checkout -t origin/release/$VERSION
|
|
||||||
npm version --no-git-tag-version --allow-same-version $VERSION
|
|
||||||
git add package.json
|
|
||||||
git commit -nm "Bump version $VERSION"
|
|
||||||
git checkout -t origin/master
|
|
||||||
git merge -m "Release $VERSION" --no-ff release/$VERSION
|
|
||||||
git push --no-verify
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
run: |
|
|
||||||
npm set //registry.npmjs.org/:_authToken $NPM_TOKEN
|
|
||||||
npm publish
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
15
.github/workflows/release.yml
vendored
15
.github/workflows/release.yml
vendored
@ -7,18 +7,24 @@ on:
|
|||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
|
permissions: # added using https://github.com/step-security/secure-repo
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
permissions:
|
||||||
|
contents: write # for changesets/action to push to the repo
|
||||||
|
pull-requests: write # for changesets/action to create PRs
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -28,10 +34,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
id: changesets
|
id: changesets
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7
|
||||||
with:
|
with:
|
||||||
version: pnpm changeset:version
|
version: pnpm changeset:version
|
||||||
publish: pnpm changeset:publish
|
publish: pnpm changeset:publish
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
37
.github/workflows/scorecard.yml
vendored
Normal file
37
.github/workflows/scorecard.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
branch_protection_rule:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
schedule:
|
||||||
|
- cron: 29 15 * * 0
|
||||||
|
permissions: read-all
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
security-events: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Run analysis
|
||||||
|
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
publish_results: true
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
- name: Upload to code-scanning
|
||||||
|
uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -9,13 +9,13 @@ jobs:
|
|||||||
unit-test:
|
unit-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
# uses version from "packageManager" field in package.json
|
# uses version from "packageManager" field in package.json
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||||
with:
|
with:
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
@ -39,7 +39,7 @@ jobs:
|
|||||||
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
|
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
|
||||||
|
|
||||||
- name: Upload Coverage to Codecov
|
- name: Upload Coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||||
# Run step only pushes to develop and pull_requests
|
# Run step only pushes to develop and pull_requests
|
||||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/unlock-reopened-issues.yml
vendored
2
.github/workflows/unlock-reopened-issues.yml
vendored
@ -8,6 +8,6 @@ jobs:
|
|||||||
triage:
|
triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: Dunning-Kruger/unlock-issues@v1
|
- uses: Dunning-Kruger/unlock-issues@b06b7f7e5c3f2eaa1c6d5d89f40930e4d6d9699e # v1
|
||||||
with:
|
with:
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
8
.github/workflows/update-browserlist.yml
vendored
8
.github/workflows/update-browserlist.yml
vendored
@ -8,18 +8,18 @@ jobs:
|
|||||||
update-browser-list:
|
update-browser-list:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||||
- run: npx update-browserslist-db@latest
|
- run: npx update-browserslist-db@latest
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: EndBug/add-and-commit@v9
|
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
||||||
with:
|
with:
|
||||||
author_name: ${{ github.actor }}
|
author_name: ${{ github.actor }}
|
||||||
author_email: ${{ github.actor }}@users.noreply.github.com
|
author_email: ${{ github.actor }}@users.noreply.github.com
|
||||||
message: 'chore: update browsers list'
|
message: 'chore: update browsers list'
|
||||||
push: false
|
push: false
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||||
with:
|
with:
|
||||||
branch: update-browserslist
|
branch: update-browserslist
|
||||||
title: Update Browserslist
|
title: Update Browserslist
|
||||||
|
2
.hadolint.yaml
Normal file
2
.hadolint.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ignored:
|
||||||
|
- DL3002 # TODO: Last USER should not be root
|
@ -1,4 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit
|
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit
|
||||||
|
12
Dockerfile
12
Dockerfile
@ -1,2 +1,10 @@
|
|||||||
FROM node:20.12.2-alpine3.19 AS base
|
FROM node:20.12.2-alpine3.19@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2
|
||||||
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
|
|
||||||
|
USER 0:0
|
||||||
|
|
||||||
|
RUN corepack enable \
|
||||||
|
&& corepack enable pnpm
|
||||||
|
|
||||||
|
ENV NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
|
|
||||||
|
EXPOSE 9000 3333
|
||||||
|
@ -36,6 +36,7 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
|
|||||||
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
|
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
|
||||||
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
|
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
|
||||||
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
|
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
|
||||||
|
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mermaid-js/mermaid/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mermaid-js/mermaid)
|
||||||
|
|
||||||
<img src="./img/header.png" alt="" />
|
<img src="./img/header.png" alt="" />
|
||||||
|
|
||||||
|
174
cypress/integration/rendering/architecture.spec.ts
Normal file
174
cypress/integration/rendering/architecture.spec.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||||
|
|
||||||
|
describe.skip('architecture diagram', () => {
|
||||||
|
it('should render a simple architecture diagram with groups', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
|
||||||
|
service db(database)[Database] in api
|
||||||
|
service disk1(disk)[Storage] in api
|
||||||
|
service disk2(disk)[Storage] in api
|
||||||
|
service server(server)[Server] in api
|
||||||
|
service gateway(internet)[Gateway]
|
||||||
|
|
||||||
|
db L--R server
|
||||||
|
disk1 T--B server
|
||||||
|
disk2 T--B db
|
||||||
|
server T--B gateway
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with groups within groups', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
group api[API]
|
||||||
|
group public[Public API] in api
|
||||||
|
group private[Private API] in api
|
||||||
|
|
||||||
|
service serv1(server)[Server] in public
|
||||||
|
|
||||||
|
service serv2(server)[Server] in private
|
||||||
|
service db(database)[Database] in private
|
||||||
|
|
||||||
|
service gateway(internet)[Gateway] in api
|
||||||
|
|
||||||
|
serv1 B--T serv2
|
||||||
|
serv2 L--R db
|
||||||
|
serv1 L--R gateway
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with the fallback icon', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
service unknown(iconnamedoesntexist)[Unknown Icon]
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with split directioning', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
service db(database)[Database]
|
||||||
|
service s3(disk)[Storage]
|
||||||
|
service serv1(server)[Server 1]
|
||||||
|
service serv2(server)[Server 2]
|
||||||
|
service disk(disk)[Disk]
|
||||||
|
|
||||||
|
db L--R s3
|
||||||
|
serv1 L--T s3
|
||||||
|
serv2 L--B s3
|
||||||
|
serv1 T--B disk
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with directional arrows', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC (L--R) servL
|
||||||
|
servC (R--L) servR
|
||||||
|
servC (T--B) servT
|
||||||
|
servC (B--T) servB
|
||||||
|
|
||||||
|
servL (T--L) servT
|
||||||
|
servL (B--L) servB
|
||||||
|
servR (T--R) servT
|
||||||
|
servR (B--R) servB
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with group edges', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
group left_group(cloud)[Left]
|
||||||
|
group right_group(cloud)[Right]
|
||||||
|
group top_group(cloud)[Top]
|
||||||
|
group bottom_group(cloud)[Bottom]
|
||||||
|
group center_group(cloud)[Center]
|
||||||
|
|
||||||
|
service left_disk(disk)[Disk] in left_group
|
||||||
|
service right_disk(disk)[Disk] in right_group
|
||||||
|
service top_disk(disk)[Disk] in top_group
|
||||||
|
service bottom_disk(disk)[Disk] in bottom_group
|
||||||
|
service center_disk(disk)[Disk] in center_group
|
||||||
|
|
||||||
|
left_disk{group} (R--L) center_disk{group}
|
||||||
|
right_disk{group} (L--R) center_disk{group}
|
||||||
|
top_disk{group} (B--T) center_disk{group}
|
||||||
|
bottom_disk{group} (T--B) center_disk{group}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with edge labels', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC L-[Label]-R servL
|
||||||
|
servC R-[Label]-L servR
|
||||||
|
servC T-[Label]-B servT
|
||||||
|
servC B-[Label]-T servB
|
||||||
|
|
||||||
|
servL T-[Label]-L servT
|
||||||
|
servL B-[Label]-L servB
|
||||||
|
servR T-[Label]-R servT
|
||||||
|
servR B-[Label]-R servB
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with simple junction edges', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
service left_disk(disk)[Disk]
|
||||||
|
service top_disk(disk)[Disk]
|
||||||
|
service bottom_disk(disk)[Disk]
|
||||||
|
service top_gateway(internet)[Gateway]
|
||||||
|
service bottom_gateway(internet)[Gateway]
|
||||||
|
junction juncC
|
||||||
|
junction juncR
|
||||||
|
|
||||||
|
left_disk R--L juncC
|
||||||
|
top_disk B--T juncC
|
||||||
|
bottom_disk T--B juncC
|
||||||
|
juncC R--L juncR
|
||||||
|
top_gateway B--T juncR
|
||||||
|
bottom_gateway T--B juncR
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should render an architecture diagram with complex junction edges', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`architecture-beta
|
||||||
|
group left
|
||||||
|
group right
|
||||||
|
service left_disk(disk)[Disk] in left
|
||||||
|
service top_disk(disk)[Disk] in left
|
||||||
|
service bottom_disk(disk)[Disk] in left
|
||||||
|
service top_gateway(internet)[Gateway] in right
|
||||||
|
service bottom_gateway(internet)[Gateway] in right
|
||||||
|
junction juncC in left
|
||||||
|
junction juncR in right
|
||||||
|
|
||||||
|
left_disk R--L juncC
|
||||||
|
top_disk B--T juncC
|
||||||
|
bottom_disk T--B juncC
|
||||||
|
|
||||||
|
|
||||||
|
top_gateway (B--T juncR
|
||||||
|
bottom_gateway (T--B juncR
|
||||||
|
|
||||||
|
juncC{group} R--L) juncR{group}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -321,4 +321,37 @@ ORDER ||--|{ LINE-ITEM : contains
|
|||||||
{ logLevel: 1 }
|
{ logLevel: 1 }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render relationship labels with line breaks', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
b["Customer Account Secondary"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
c["Customer Account Tertiary"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
d["Customer Account Nth"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
p ||--o| a : "has<br />one"
|
||||||
|
p ||--o| b : "has<br />one<br />two"
|
||||||
|
p ||--o| c : "has<br />one<br/>two<br />three"
|
||||||
|
p ||--o| d : "has<br />one<br />two<br/>three<br />...<br/>Nth"
|
||||||
|
`,
|
||||||
|
{ logLevel: 1 }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -73,7 +73,9 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 72px;
|
font-size: 72px;
|
||||||
}
|
}
|
||||||
|
pre {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
/* tspan {
|
/* tspan {
|
||||||
font-size: 6px !important;
|
font-size: 6px !important;
|
||||||
} */
|
} */
|
||||||
@ -88,17 +90,202 @@
|
|||||||
config:
|
config:
|
||||||
look: handDrawn
|
look: handDrawn
|
||||||
layout: elk
|
layout: elk
|
||||||
|
elk:
|
||||||
|
<!-- nodePlacementStrategy: INTERACTIVE -->
|
||||||
|
<!-- mergeEdges: true -->
|
||||||
|
---
|
||||||
|
stateDiagram-v2
|
||||||
|
direction LR
|
||||||
|
accTitle: An idealized Open Source supply-chain graph
|
||||||
|
|
||||||
|
%%
|
||||||
|
state "🟦 Importer" as author_importer
|
||||||
|
state "🟥 Supplier, Owner" as author_owner
|
||||||
|
state "🟨🟥 Maintainer, Author\n🟨 Custodian" as author
|
||||||
|
state "🟩 Distributor" as repository_distributor
|
||||||
|
state "🟦 Importer" as language_importer
|
||||||
|
state "🟦🟨 Packager" as language_packager
|
||||||
|
state "🟦🟨 OSS Steward" as language_steward
|
||||||
|
state "🟨 Curator" as language_curator
|
||||||
|
state "🟩 Distributor" as language_distributor
|
||||||
|
state "🟦 Contributor" as contributor
|
||||||
|
state "🟦 Importer" as package_importer
|
||||||
|
state "🟨 Patcher" as package_patcher
|
||||||
|
state "🟨🟦 Builder\n🟨🟦 Packager\n🟨🟦 Containerizer" as package_packager
|
||||||
|
state "🟨 Curator" as package_curator
|
||||||
|
state "🟩 Distributor" as package_distributor
|
||||||
|
state "🟦 Importer" as integrator_importer
|
||||||
|
state "🟥 Supplier, Manufacturer, Owner" as integrator_owner
|
||||||
|
state "🟦🟨🟥 Integrator, Developer" as integrator_developer
|
||||||
|
state "🟩🟨 SBOM Redactor\n🟩 Publisher" as integrator_publisher
|
||||||
|
state "🟦🟨 Builder" as integrator_builder
|
||||||
|
state "🟨 Deployer" as deployer
|
||||||
|
state "🟦 Vuln. Checker" as integrator_checker
|
||||||
|
state "🟩🟨 SBOM Redactor" as redactor
|
||||||
|
state "🟦 Consumer\n🟦 User" as consumer
|
||||||
|
state "🟦 Auditor" as auditor_internal
|
||||||
|
state "🟦 Auditor" as auditor_external
|
||||||
|
|
||||||
|
%%
|
||||||
|
classDef createsSBOM stroke:red,stroke-width:3px;
|
||||||
|
classDef updatesSBOM stroke:yellow,stroke-width:3px;
|
||||||
|
classDef assemblesSBOM stroke:yellow,stroke-width:3px;
|
||||||
|
classDef distributesSBOM stroke:green,stroke-width:3px;
|
||||||
|
classDef verifiesSBOM stroke:#07f,stroke-width:3px;
|
||||||
|
|
||||||
|
%%
|
||||||
|
class author_importer verifiesSBOM
|
||||||
|
class author_owner createsSBOM
|
||||||
|
class manufacturer_owner createsSBOM
|
||||||
|
class author assemblesSBOM
|
||||||
|
class package_importer verifiesSBOM
|
||||||
|
class package_patcher updatesSBOM
|
||||||
|
class package_packager assemblesSBOM
|
||||||
|
class package_curator distributesSBOM
|
||||||
|
class package_distributor distributesSBOM
|
||||||
|
class language_importer verifiesSBOM
|
||||||
|
class language_packager assemblesSBOM
|
||||||
|
class language_steward updatesSBOM
|
||||||
|
class language_curator distributesSBOM
|
||||||
|
class language_distributor distributesSBOM
|
||||||
|
class repository_distributor distributesSBOM
|
||||||
|
class integrator_importer verifiesSBOM
|
||||||
|
class integrator_owner createsSBOM
|
||||||
|
class integrator_developer assemblesSBOM
|
||||||
|
class integrator_publisher distributesSBOM
|
||||||
|
class integrator_builder assemblesSBOM
|
||||||
|
class integrator_checker verifiesSBOM
|
||||||
|
class deployer assemblesSBOM
|
||||||
|
class redactor distributesSBOM
|
||||||
|
class auditor_internal verifiesSBOM
|
||||||
|
class auditor_external verifiesSBOM
|
||||||
|
|
||||||
|
state "Maintainer Environment" as environment_maintainer {
|
||||||
|
[*] --> author_importer
|
||||||
|
[*] --> author
|
||||||
|
author_importer --> author
|
||||||
|
author_owner --> author
|
||||||
|
author --> language_packager
|
||||||
|
}
|
||||||
|
|
||||||
|
[*] --> environment_maintainer
|
||||||
|
|
||||||
|
state "Language Ecosystem" as ecosystem_lang {
|
||||||
|
[*] --> language_importer
|
||||||
|
[*] --> language_steward
|
||||||
|
[*] --> language_curator
|
||||||
|
[*] --> language_distributor
|
||||||
|
language_importer --> language_distributor
|
||||||
|
language_importer --> language_curator
|
||||||
|
language_steward --> language_curator
|
||||||
|
language_curator --> language_distributor
|
||||||
|
}
|
||||||
|
|
||||||
|
language_packager --> ecosystem_lang
|
||||||
|
ecosystem_lang --> ecosystem_lang
|
||||||
|
|
||||||
|
state "Public Collaboration Ecosystem" as ecosystem_repo {
|
||||||
|
[*] --> repository_distributor
|
||||||
|
}
|
||||||
|
|
||||||
|
author --> ecosystem_repo
|
||||||
|
ecosystem_repo --> author
|
||||||
|
|
||||||
|
repository_distributor --> contributor
|
||||||
|
contributor --> repository_distributor
|
||||||
|
|
||||||
|
state "Package Ecosystem" as ecosystem_package {
|
||||||
|
[*] --> package_importer
|
||||||
|
[*] --> package_packager
|
||||||
|
[*] --> package_patcher
|
||||||
|
package_importer --> package_patcher
|
||||||
|
package_importer --> package_packager
|
||||||
|
package_patcher --> package_packager
|
||||||
|
package_packager --> package_curator
|
||||||
|
package_packager --> package_distributor
|
||||||
|
package_curator --> package_distributor
|
||||||
|
}
|
||||||
|
|
||||||
|
repository_distributor --> ecosystem_package
|
||||||
|
language_distributor --> ecosystem_package
|
||||||
|
ecosystem_package --> ecosystem_package
|
||||||
|
|
||||||
|
state "Integrator Environment" as environment_integrator {
|
||||||
|
[*] --> integrator_developer
|
||||||
|
[*] --> integrator_importer
|
||||||
|
integrator_importer --> integrator_developer
|
||||||
|
integrator_owner --> integrator_developer
|
||||||
|
integrator_builder --> integrator_publisher
|
||||||
|
integrator_developer --> integrator_checker
|
||||||
|
integrator_checker --> integrator_developer
|
||||||
|
auditor_internal --> integrator_developer
|
||||||
|
integrator_developer --> integrator_builder
|
||||||
|
integrator_developer --> auditor_internal
|
||||||
|
}
|
||||||
|
|
||||||
|
repository_distributor --> environment_integrator
|
||||||
|
language_distributor --> environment_integrator
|
||||||
|
package_distributor --> environment_integrator
|
||||||
|
|
||||||
|
state "Production Environment" as environment_prod {
|
||||||
|
[*] --> deployer
|
||||||
|
deployer --> redactor
|
||||||
|
}
|
||||||
|
|
||||||
|
integrator_publisher --> [*]
|
||||||
|
integrator_developer --> environment_prod
|
||||||
|
integrator_builder --> environment_prod
|
||||||
|
integrator_publisher --> environment_prod
|
||||||
|
|
||||||
|
deployer --> auditor_external
|
||||||
|
deployer --> consumer
|
||||||
|
redactor --> consumer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
---
|
||||||
|
title: hello2
|
||||||
|
config:
|
||||||
|
look: handDrawn
|
||||||
|
layout: dagre
|
||||||
elk:
|
elk:
|
||||||
nodePlacementStrategy: BRANDES_KOEPF
|
nodePlacementStrategy: BRANDES_KOEPF
|
||||||
---
|
---
|
||||||
flowchart LR
|
stateDiagram-v2
|
||||||
A[Start] --Some text--> B(Continue)
|
A --> A
|
||||||
B --> C{Evaluate}
|
state A {
|
||||||
C -- One --> D[Option 1]
|
B --> D
|
||||||
C -- Two --> E[Option 2]
|
state B {
|
||||||
C -- Three --> F[fa:fa-car Option 3]
|
C
|
||||||
|
}
|
||||||
|
state D {
|
||||||
|
E
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
---
|
||||||
|
title: hello2
|
||||||
|
config:
|
||||||
|
look: handDrawn
|
||||||
|
layout: dagre
|
||||||
|
elk:
|
||||||
|
nodePlacementStrategy: BRANDES_KOEPF
|
||||||
|
---
|
||||||
|
flowchart
|
||||||
|
A --> A
|
||||||
|
subgraph A
|
||||||
|
B --> B
|
||||||
|
subgraph B
|
||||||
|
C
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
</pre
|
</pre
|
||||||
>
|
>
|
||||||
@ -195,7 +382,7 @@ flowchart LR
|
|||||||
messageFontFamily: 'courier',
|
messageFontFamily: 'courier',
|
||||||
},
|
},
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
logLevel: 0,
|
logLevel: 3,
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
});
|
});
|
||||||
function callback() {
|
function callback() {
|
||||||
|
252
demos/architecture.html
Normal file
252
demos/architecture.html
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Architecture Mermaid Quick Test Page</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Architecture diagram demo</h1>
|
||||||
|
<h2>Simple diagram with groups</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
|
||||||
|
service db(database)[Database] in api
|
||||||
|
service disk1(disk)[Storage] in api
|
||||||
|
service disk2(disk)[Storage] in api
|
||||||
|
service server(server)[Server] in api
|
||||||
|
service gateway(internet)[Gateway]
|
||||||
|
|
||||||
|
db:L -- R:server
|
||||||
|
disk1:T -- B:server
|
||||||
|
disk2:T -- B:db
|
||||||
|
server:T -- B:gateway
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Groups within groups</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
group api[API]
|
||||||
|
group public[Public API] in api
|
||||||
|
group private[Private API] in api
|
||||||
|
|
||||||
|
|
||||||
|
service serv1(server)[Server] in public
|
||||||
|
|
||||||
|
|
||||||
|
service serv2(server)[Server] in private
|
||||||
|
service db(database)[Database] in private
|
||||||
|
|
||||||
|
service gateway(internet)[Gateway] in api
|
||||||
|
|
||||||
|
serv1:B -- T:serv2
|
||||||
|
|
||||||
|
serv2:L -- R:db
|
||||||
|
|
||||||
|
serv1:L -- R:gateway
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Default icon (?) from unknown icon name</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service unknown(iconnamedoesntexist)[Unknown Icon]
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Split Direction</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service db(database)[Database]
|
||||||
|
service s3(disk)[Storage]
|
||||||
|
service serv1(server)[Server 1]
|
||||||
|
service serv2(server)[Server 2]
|
||||||
|
service disk(disk)[Disk]
|
||||||
|
|
||||||
|
db:L -- R:s3
|
||||||
|
serv1:L -- T:s3
|
||||||
|
serv2:L -- B:s3
|
||||||
|
serv1:T -- B:disk
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Arrow Tests</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC:L <--> R:servL
|
||||||
|
servC:R <--> L:servR
|
||||||
|
servC:T <--> B:servT
|
||||||
|
servC:B <--> T:servB
|
||||||
|
|
||||||
|
servL:T <--> L:servT
|
||||||
|
servL:B <--> L:servB
|
||||||
|
servR:T <--> R:servT
|
||||||
|
servR:B <--> R:servB
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC:L <--> R:servL
|
||||||
|
servC:R <--> L:servR
|
||||||
|
servC:T <--> B:servT
|
||||||
|
servC:B <--> T:servB
|
||||||
|
|
||||||
|
servT:L <--> T:servL
|
||||||
|
servB:L <--> B:servL
|
||||||
|
servT:R <--> T:servR
|
||||||
|
servB:R <--> B:servR
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Group Edges</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
group left_group(cloud)[Left]
|
||||||
|
group right_group(cloud)[Right]
|
||||||
|
group top_group(cloud)[Top]
|
||||||
|
group bottom_group(cloud)[Bottom]
|
||||||
|
group center_group(cloud)[Center]
|
||||||
|
|
||||||
|
service left_disk(disk)[Disk] in left_group
|
||||||
|
service right_disk(disk)[Disk] in right_group
|
||||||
|
service top_disk(disk)[Disk] in top_group
|
||||||
|
service bottom_disk(disk)[Disk] in bottom_group
|
||||||
|
service center_disk(disk)[Disk] in center_group
|
||||||
|
|
||||||
|
left_disk{group}:R <--> L:center_disk{group}
|
||||||
|
right_disk{group}:L <--> R:center_disk{group}
|
||||||
|
top_disk{group}:B <--> T:center_disk{group}
|
||||||
|
bottom_disk{group}:T <--> B:center_disk{group}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Edge Label Test</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC:L -[Label]- R:servL
|
||||||
|
servC:R -[Label]- L:servR
|
||||||
|
servC:T -[Label]- B:servT
|
||||||
|
servC:B -[Label]- T:servB
|
||||||
|
|
||||||
|
servL:T -[Label]- L:servT
|
||||||
|
servL:B -[Label]- L:servB
|
||||||
|
servR:T -[Label]- R:servT
|
||||||
|
servR:B -[Label]- R:servB
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service servC(server)[Server 1]
|
||||||
|
service servL(server)[Server 2]
|
||||||
|
service servR(server)[Server 3]
|
||||||
|
service servT(server)[Server 4]
|
||||||
|
service servB(server)[Server 5]
|
||||||
|
|
||||||
|
servC:L -[Label that is Long]- R:servL
|
||||||
|
servC:R -[Label that is Long]- L:servR
|
||||||
|
servC:T -[Label that is Long]- B:servT
|
||||||
|
servC:B -[Label that is Long]- T:servB
|
||||||
|
|
||||||
|
servL:T -[Label that is Long]- L:servT
|
||||||
|
servL:B -[Label that is Long]- L:servB
|
||||||
|
servR:T -[Label that is Long]- R:servT
|
||||||
|
servR:B -[Label that is Long]- R:servB
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h2>Junction Demo</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service left_disk(disk)[Disk]
|
||||||
|
service top_disk(disk)[Disk]
|
||||||
|
service bottom_disk(disk)[Disk]
|
||||||
|
service top_gateway(internet)[Gateway]
|
||||||
|
service bottom_gateway(internet)[Gateway]
|
||||||
|
junction juncC
|
||||||
|
junction juncR
|
||||||
|
|
||||||
|
left_disk:R -- L:juncC
|
||||||
|
top_disk:B -- T:juncC
|
||||||
|
bottom_disk:T -- B:juncC
|
||||||
|
juncC:R -- L:juncR
|
||||||
|
top_gateway:B -- T:juncR
|
||||||
|
bottom_gateway:T -- B:juncR
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Junction Demo Groups</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
group left
|
||||||
|
group right
|
||||||
|
service left_disk(disk)[Disk] in left
|
||||||
|
service top_disk(disk)[Disk] in left
|
||||||
|
service bottom_disk(disk)[Disk] in left
|
||||||
|
service top_gateway(internet)[Gateway] in right
|
||||||
|
service bottom_gateway(internet)[Gateway] in right
|
||||||
|
junction juncC in left
|
||||||
|
junction juncR in right
|
||||||
|
|
||||||
|
left_disk:R -- L:juncC
|
||||||
|
top_disk:B -- T:juncC
|
||||||
|
bottom_disk:T -- B:juncC
|
||||||
|
|
||||||
|
|
||||||
|
top_gateway:B <-- T:juncR
|
||||||
|
bottom_gateway:T <-- B:juncR
|
||||||
|
|
||||||
|
juncC{group}:R --> L:juncR{group}
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- <h2>AWS Icon Demo</h2>
|
||||||
|
<pre class="mermaid">
|
||||||
|
architecture-beta
|
||||||
|
service s3(aws:s3)[Cloud Store]
|
||||||
|
service ec2(aws:ec2)[Server]
|
||||||
|
service wave(aws:wavelength)[Wave]
|
||||||
|
service droplet(do:droplet)[Droplet]
|
||||||
|
service repo(gh:github)[Repository]
|
||||||
|
</pre> -->
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'base',
|
||||||
|
startOnLoad: true,
|
||||||
|
logLevel: 0,
|
||||||
|
architecture: {
|
||||||
|
iconSize: 80,
|
||||||
|
},
|
||||||
|
// iconLibraries: ['aws:common', 'aws:full', 'github', 'digital-ocean'],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -125,6 +125,35 @@
|
|||||||
</pre>
|
</pre>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
b["Customer Account Secondary"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
c["Customer Account Tertiary"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
d["Customer Account Nth"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
|
||||||
|
p ||--o| a : "has<br />one"
|
||||||
|
p ||--o| b : "has<br />one<br />two"
|
||||||
|
p ||--o| c : "has<br />one<br />two<br />three"
|
||||||
|
p ||--o| d : "has<br />one<br />two<br />three<br />...<br />Nth"
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
<pre class="mermaid">
|
<pre class="mermaid">
|
||||||
erDiagram
|
erDiagram
|
||||||
_customer_order {
|
_customer_order {
|
||||||
|
@ -88,6 +88,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<h2><a href="./block.html">Layered Blocks</a></h2>
|
<h2><a href="./block.html">Layered Blocks</a></h2>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<h2><a href="./architecture.html">Architecture</a></h2>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -7,9 +7,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
working_dir: /mermaid
|
working_dir: /mermaid
|
||||||
mem_limit: '8G'
|
mem_limit: '8G'
|
||||||
entrypoint: ./docker-entrypoint.sh
|
|
||||||
environment:
|
|
||||||
- NODE_OPTIONS=--max_old_space_size=8192
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/mermaid
|
- ./:/mermaid
|
||||||
- root_cache:/root/.cache
|
- root_cache:/root/.cache
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
source /root/.shrc
|
|
||||||
exec "$@"
|
|
@ -16,7 +16,17 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:112](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L112)
|
[packages/mermaid/src/config.type.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L122)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### architecture
|
||||||
|
|
||||||
|
• `Optional` **architecture**: `ArchitectureDiagramConfig`
|
||||||
|
|
||||||
|
#### Defined in
|
||||||
|
|
||||||
|
[packages/mermaid/src/config.type.ts:194](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L194)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -29,7 +39,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
|
[packages/mermaid/src/config.type.ts:141](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L141)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -39,7 +49,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
|
[packages/mermaid/src/config.type.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L200)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -49,7 +59,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
|
[packages/mermaid/src/config.type.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L197)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -59,7 +69,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:177](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L177)
|
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -69,7 +79,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L103)
|
[packages/mermaid/src/config.type.ts:113](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L113)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -83,7 +93,7 @@ You can set this attribute to base the seed on a static string.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L171)
|
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -101,7 +111,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164)
|
[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -111,7 +121,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
|
[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -121,10 +131,11 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Type declaration
|
#### Type declaration
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| :----------------------- | :---------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| :----------------------- | :-------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
|
| `cycleBreakingStrategy?` | `"GREEDY"` \| `"DEPTH_FIRST"` \| `"INTERACTIVE"` \| `"MODEL_ORDER"` \| `"GREEDY_MODEL_ORDER"` | This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops. |
|
||||||
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
|
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
|
||||||
|
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
|
||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
@ -138,7 +149,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:179](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L179)
|
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -148,7 +159,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L172)
|
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -162,7 +173,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:111](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L111)
|
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -172,7 +183,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
|
[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -186,7 +197,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L153)
|
[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -196,7 +207,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
|
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -206,7 +217,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
|
[packages/mermaid/src/config.type.ts:196](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L196)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -228,7 +239,7 @@ Defines the seed to be used when using handDrawn look. This is important for the
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L104)
|
[packages/mermaid/src/config.type.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L114)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -238,7 +249,7 @@ Defines the seed to be used when using handDrawn look. This is important for the
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:175](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L175)
|
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -265,7 +276,7 @@ fall back to legacy rendering for KaTeX.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L146)
|
[packages/mermaid/src/config.type.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L156)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -277,7 +288,7 @@ This option decides the amount of logging to be used by mermaid.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L117)
|
[packages/mermaid/src/config.type.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L127)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -299,7 +310,7 @@ Defines which main look to use for the diagram.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
|
[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -333,7 +344,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
|
[packages/mermaid/src/config.type.ts:195](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L195)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -343,7 +354,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
|
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -353,7 +364,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:180](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L180)
|
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -363,7 +374,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
|
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -373,7 +384,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
|
[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -383,7 +394,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
|
[packages/mermaid/src/config.type.ts:198](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L198)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -397,7 +408,7 @@ This prevents malicious graph directives from overriding a site's default securi
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L138)
|
[packages/mermaid/src/config.type.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L148)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -409,7 +420,7 @@ Level of trust for parsed diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
|
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -419,7 +430,7 @@ Level of trust for parsed diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173)
|
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -431,7 +442,7 @@ Dictates whether mermaid starts on Page load
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L125)
|
[packages/mermaid/src/config.type.ts:135](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L135)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -441,7 +452,7 @@ Dictates whether mermaid starts on Page load
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:178](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L178)
|
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -454,7 +465,7 @@ This is useful when you want to control how to handle syntax errors in your appl
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
|
[packages/mermaid/src/config.type.ts:210](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L210)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -497,7 +508,7 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L176)
|
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -507,7 +518,7 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
|
[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -517,4 +528,4 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
|
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
|
||||||
|
@ -74,6 +74,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
|||||||
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
|
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
|
||||||
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
|
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
|
||||||
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
|
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
|
||||||
|
- [Microsoft Loop](https://loop.cloud.microsoft) ✅
|
||||||
|
|
||||||
### LLM integrations
|
### LLM integrations
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ import matplotlib.pyplot as plt
|
|||||||
|
|
||||||
def mm(graph):
|
def mm(graph):
|
||||||
graphbytes = graph.encode("utf8")
|
graphbytes = graph.encode("utf8")
|
||||||
base64_bytes = base64.b64encode(graphbytes)
|
base64_bytes = base64.urlsafe_b64encode(graphbytes)
|
||||||
base64_string = base64_bytes.decode("ascii")
|
base64_string = base64_bytes.decode("ascii")
|
||||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||||
|
|
||||||
|
194
docs/syntax/architecture.md
Normal file
194
docs/syntax/architecture.md
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/architecture.md](../../packages/mermaid/src/docs/syntax/architecture.md).
|
||||||
|
|
||||||
|
# Architecture Diagrams Documentation (v\<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
|
||||||
|
service db(database)[Database] in api
|
||||||
|
service disk1(disk)[Storage] in api
|
||||||
|
service disk2(disk)[Storage] in api
|
||||||
|
service server(server)[Server] in api
|
||||||
|
|
||||||
|
db:L -- R:server
|
||||||
|
disk1:T -- B:server
|
||||||
|
disk2:T -- B:db
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
|
||||||
|
service db(database)[Database] in api
|
||||||
|
service disk1(disk)[Storage] in api
|
||||||
|
service disk2(disk)[Storage] in api
|
||||||
|
service server(server)[Server] in api
|
||||||
|
|
||||||
|
db:L -- R:server
|
||||||
|
disk1:T -- B:server
|
||||||
|
disk2:T -- B:db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
The building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.
|
||||||
|
|
||||||
|
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
|
||||||
|
|
||||||
|
To begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
The syntax for declaring a group is:
|
||||||
|
|
||||||
|
```
|
||||||
|
group {group id}({icon name})[{title}] (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
Put together:
|
||||||
|
|
||||||
|
```
|
||||||
|
group public_api(cloud)[Public API]
|
||||||
|
```
|
||||||
|
|
||||||
|
creates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.
|
||||||
|
|
||||||
|
Additionally, groups can be placed within a group using the optional `in` keyword
|
||||||
|
|
||||||
|
```
|
||||||
|
group private_api(cloud)[Private API] in public_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
The syntax for declaring a service is:
|
||||||
|
|
||||||
|
```
|
||||||
|
service {service id}({icon name})[{title}] (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
Put together:
|
||||||
|
|
||||||
|
```
|
||||||
|
service database(db)[Database]
|
||||||
|
```
|
||||||
|
|
||||||
|
creates the service identified as `database`, using the icon `db`, with the label `Database`.
|
||||||
|
|
||||||
|
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
|
||||||
|
|
||||||
|
```
|
||||||
|
service database(db)[Database] in private_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edges
|
||||||
|
|
||||||
|
The syntax for declaring an edge is:
|
||||||
|
|
||||||
|
```
|
||||||
|
{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Edge Direction
|
||||||
|
|
||||||
|
The side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
db:R -- L:server
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.
|
||||||
|
|
||||||
|
```
|
||||||
|
db:T -- L:server
|
||||||
|
```
|
||||||
|
|
||||||
|
creates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.
|
||||||
|
|
||||||
|
#### Arrows
|
||||||
|
|
||||||
|
Arrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
subnet:R --> L:gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge with the arrow going into the `gateway` service
|
||||||
|
|
||||||
|
#### Edges out of Groups
|
||||||
|
|
||||||
|
To have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
service server[Server] in groupOne
|
||||||
|
service subnet[Subnet] in groupTwo
|
||||||
|
|
||||||
|
server{group}:B --> T:subnet{group}
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.
|
||||||
|
|
||||||
|
It's important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.
|
||||||
|
|
||||||
|
### Junctions
|
||||||
|
|
||||||
|
Junctions are a special type of node which acts as a potential 4-way split between edges.
|
||||||
|
|
||||||
|
The syntax for declaring a junction is:
|
||||||
|
|
||||||
|
```
|
||||||
|
junction {junction id} (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
architecture-beta
|
||||||
|
service left_disk(disk)[Disk]
|
||||||
|
service top_disk(disk)[Disk]
|
||||||
|
service bottom_disk(disk)[Disk]
|
||||||
|
service top_gateway(internet)[Gateway]
|
||||||
|
service bottom_gateway(internet)[Gateway]
|
||||||
|
junction junctionCenter
|
||||||
|
junction junctionRight
|
||||||
|
|
||||||
|
left_disk:R -- L:junctionCenter
|
||||||
|
top_disk:B -- T:junctionCenter
|
||||||
|
bottom_disk:T -- B:junctionCenter
|
||||||
|
junctionCenter:R -- L:junctionRight
|
||||||
|
top_gateway:B -- T:junctionRight
|
||||||
|
bottom_gateway:T -- B:junctionRight
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
architecture-beta
|
||||||
|
service left_disk(disk)[Disk]
|
||||||
|
service top_disk(disk)[Disk]
|
||||||
|
service bottom_disk(disk)[Disk]
|
||||||
|
service top_gateway(internet)[Gateway]
|
||||||
|
service bottom_gateway(internet)[Gateway]
|
||||||
|
junction junctionCenter
|
||||||
|
junction junctionRight
|
||||||
|
|
||||||
|
left_disk:R -- L:junctionCenter
|
||||||
|
top_disk:B -- T:junctionCenter
|
||||||
|
bottom_disk:T -- B:junctionCenter
|
||||||
|
junctionCenter:R -- L:junctionRight
|
||||||
|
top_gateway:B -- T:junctionRight
|
||||||
|
bottom_gateway:T -- B:junctionRight
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
@ -286,6 +286,7 @@ erDiagram
|
|||||||
|
|
||||||
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
||||||
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
||||||
|
- (v\<MERMAID_RELEASE_VERSION>+) If you want a multi-line label on a relationship, use `<br />` between the two lines (`"first line<br />second line"`)
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"test": "pnpm lint && vitest run",
|
"test": "pnpm lint && vitest run",
|
||||||
"test:watch": "vitest --watch",
|
"test:watch": "vitest --watch",
|
||||||
"test:coverage": "vitest --coverage",
|
"test:coverage": "vitest --coverage",
|
||||||
"prepare": "husky install && pnpm build",
|
"prepare": "husky && pnpm build",
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "lint-staged"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -752,14 +752,34 @@ export const render = async (
|
|||||||
'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,
|
'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,
|
||||||
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,
|
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,
|
||||||
'elk.direction': 'DOWN',
|
'elk.direction': 'DOWN',
|
||||||
'spacing.baseValue': 30,
|
'spacing.baseValue': 35,
|
||||||
// 'spacing.nodeNode': 40,
|
'elk.layered.unnecessaryBendpoints': true,
|
||||||
// 'spacing.nodeNodeBetweenLayers': 45,
|
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk.cycleBreakingStrategy,
|
||||||
// 'spacing.edgeNode': 40,
|
// 'spacing.nodeNode': 20,
|
||||||
// 'spacing.edgeNodeBetweenLayers': 30,
|
// 'spacing.nodeNodeBetweenLayers': 25,
|
||||||
// 'spacing.edgeEdge': 30,
|
// 'spacing.edgeNode': 20,
|
||||||
// 'spacing.edgeEdgeBetweenLayers': 40,
|
// 'spacing.edgeNodeBetweenLayers': 10,
|
||||||
// 'spacing.nodeSelfLoop': 50,
|
// 'spacing.edgeEdge': 10,
|
||||||
|
// 'spacing.edgeEdgeBetweenLayers': 20,
|
||||||
|
// 'spacing.nodeSelfLoop': 20,
|
||||||
|
|
||||||
|
// Tweaking options
|
||||||
|
// 'elk.layered.nodePlacement.favorStraightEdges': true,
|
||||||
|
// 'nodePlacement.feedbackEdges': true,
|
||||||
|
// 'elk.layered.wrapping.multiEdge.improveCuts': true,
|
||||||
|
// 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
|
||||||
|
// 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
|
||||||
|
// 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
|
||||||
|
// 'elk.layered.mergeHierarchyEdges': true,
|
||||||
|
// 'elk.layered.feedbackEdges': true,
|
||||||
|
// 'elk.layered.crossingMinimization.semiInteractive': true,
|
||||||
|
// 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
|
||||||
|
// 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
|
||||||
|
// 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
|
||||||
|
// 'elk.insideSelfLoops.activate': true,
|
||||||
|
// 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',
|
||||||
|
// 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES
|
||||||
|
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
|
@ -71,6 +71,7 @@
|
|||||||
"@mermaid-js/parser": "workspace:^",
|
"@mermaid-js/parser": "workspace:^",
|
||||||
"cytoscape": "^3.29.2",
|
"cytoscape": "^3.29.2",
|
||||||
"cytoscape-cose-bilkent": "^4.1.0",
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
|
"cytoscape-fcose": "^2.2.0",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"d3-sankey": "^0.12.3",
|
"d3-sankey": "^0.12.3",
|
||||||
"dagre-d3-es": "7.0.10",
|
"dagre-d3-es": "7.0.10",
|
||||||
@ -87,6 +88,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@adobe/jsonschema2md": "^8.0.0",
|
"@adobe/jsonschema2md": "^8.0.0",
|
||||||
|
"@types/cytoscape-fcose": "^2.2.4",
|
||||||
"@types/cytoscape": "^3.21.4",
|
"@types/cytoscape": "^3.21.4",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/d3-sankey": "^0.12.4",
|
"@types/d3-sankey": "^0.12.4",
|
||||||
|
@ -99,6 +99,16 @@ export interface MermaidConfig {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
|
nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
|
||||||
|
/**
|
||||||
|
* This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
cycleBreakingStrategy?:
|
||||||
|
| 'GREEDY'
|
||||||
|
| 'DEPTH_FIRST'
|
||||||
|
| 'INTERACTIVE'
|
||||||
|
| 'MODEL_ORDER'
|
||||||
|
| 'GREEDY_MODEL_ORDER';
|
||||||
};
|
};
|
||||||
darkMode?: boolean;
|
darkMode?: boolean;
|
||||||
htmlLabels?: boolean;
|
htmlLabels?: boolean;
|
||||||
@ -181,6 +191,7 @@ export interface MermaidConfig {
|
|||||||
quadrantChart?: QuadrantChartConfig;
|
quadrantChart?: QuadrantChartConfig;
|
||||||
xyChart?: XYChartConfig;
|
xyChart?: XYChartConfig;
|
||||||
requirement?: RequirementDiagramConfig;
|
requirement?: RequirementDiagramConfig;
|
||||||
|
architecture?: ArchitectureDiagramConfig;
|
||||||
mindmap?: MindmapDiagramConfig;
|
mindmap?: MindmapDiagramConfig;
|
||||||
gitGraph?: GitGraphDiagramConfig;
|
gitGraph?: GitGraphDiagramConfig;
|
||||||
c4?: C4DiagramConfig;
|
c4?: C4DiagramConfig;
|
||||||
@ -991,6 +1002,17 @@ export interface RequirementDiagramConfig extends BaseDiagramConfig {
|
|||||||
rect_padding?: number;
|
rect_padding?: number;
|
||||||
line_height?: number;
|
line_height?: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The object containing configurations specific for architecture diagrams
|
||||||
|
*
|
||||||
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
|
* via the `definition` "ArchitectureDiagramConfig".
|
||||||
|
*/
|
||||||
|
export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
|
||||||
|
padding?: number;
|
||||||
|
iconSize?: number;
|
||||||
|
fontSize?: number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The object containing configurations specific for mindmap diagrams
|
* The object containing configurations specific for mindmap diagrams
|
||||||
*
|
*
|
||||||
|
@ -22,6 +22,7 @@ import mindmap from '../diagrams/mindmap/detector.js';
|
|||||||
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
||||||
import { packet } from '../diagrams/packet/detector.js';
|
import { packet } from '../diagrams/packet/detector.js';
|
||||||
import block from '../diagrams/block/blockDetector.js';
|
import block from '../diagrams/block/blockDetector.js';
|
||||||
|
import architecture from '../diagrams/architecture/architectureDetector.js';
|
||||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ export const addDiagrams = () => {
|
|||||||
sankey,
|
sankey,
|
||||||
packet,
|
packet,
|
||||||
xychart,
|
xychart,
|
||||||
block
|
block,
|
||||||
|
architecture
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
333
packages/mermaid/src/diagrams/architecture/architectureDb.ts
Normal file
333
packages/mermaid/src/diagrams/architecture/architectureDb.ts
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||||
|
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||||
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import type { D3Element } from '../../types.js';
|
||||||
|
import { ImperativeState } from '../../utils/imperativeState.js';
|
||||||
|
import {
|
||||||
|
clear as commonClear,
|
||||||
|
getAccDescription,
|
||||||
|
getAccTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
setAccDescription,
|
||||||
|
setAccTitle,
|
||||||
|
setDiagramTitle,
|
||||||
|
} from '../common/commonDb.js';
|
||||||
|
import type {
|
||||||
|
ArchitectureDB,
|
||||||
|
ArchitectureDirectionPair,
|
||||||
|
ArchitectureDirectionPairMap,
|
||||||
|
ArchitectureEdge,
|
||||||
|
ArchitectureGroup,
|
||||||
|
ArchitectureJunction,
|
||||||
|
ArchitectureNode,
|
||||||
|
ArchitectureService,
|
||||||
|
ArchitectureSpatialMap,
|
||||||
|
ArchitectureState,
|
||||||
|
} from './architectureTypes.js';
|
||||||
|
import {
|
||||||
|
getArchitectureDirectionPair,
|
||||||
|
isArchitectureDirection,
|
||||||
|
isArchitectureJunction,
|
||||||
|
isArchitectureService,
|
||||||
|
shiftPositionByArchitectureDirectionPair,
|
||||||
|
} from './architectureTypes.js';
|
||||||
|
|
||||||
|
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
|
||||||
|
DEFAULT_CONFIG.architecture;
|
||||||
|
|
||||||
|
const state = new ImperativeState<ArchitectureState>(() => ({
|
||||||
|
nodes: {},
|
||||||
|
groups: {},
|
||||||
|
edges: [],
|
||||||
|
registeredIds: {},
|
||||||
|
config: DEFAULT_ARCHITECTURE_CONFIG,
|
||||||
|
dataStructures: undefined,
|
||||||
|
elements: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const clear = (): void => {
|
||||||
|
state.reset();
|
||||||
|
commonClear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addService = function ({
|
||||||
|
id,
|
||||||
|
icon,
|
||||||
|
in: parent,
|
||||||
|
title,
|
||||||
|
iconText,
|
||||||
|
}: Omit<ArchitectureService, 'edges'>) {
|
||||||
|
if (state.records.registeredIds[id] !== undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (parent !== undefined) {
|
||||||
|
if (id === parent) {
|
||||||
|
throw new Error(`The service [${id}] cannot be placed within itself`);
|
||||||
|
}
|
||||||
|
if (state.records.registeredIds[parent] === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The service [${id}]'s parent does not exist. Please make sure the parent is created before this service`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (state.records.registeredIds[parent] === 'node') {
|
||||||
|
throw new Error(`The service [${id}]'s parent is not a group`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.records.registeredIds[id] = 'node';
|
||||||
|
|
||||||
|
state.records.nodes[id] = {
|
||||||
|
id,
|
||||||
|
type: 'service',
|
||||||
|
icon,
|
||||||
|
iconText,
|
||||||
|
title,
|
||||||
|
edges: [],
|
||||||
|
in: parent,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getServices = (): ArchitectureService[] =>
|
||||||
|
Object.values(state.records.nodes).filter<ArchitectureService>(isArchitectureService);
|
||||||
|
|
||||||
|
const addJunction = function ({ id, in: parent }: Omit<ArchitectureJunction, 'edges'>) {
|
||||||
|
state.records.registeredIds[id] = 'node';
|
||||||
|
|
||||||
|
state.records.nodes[id] = {
|
||||||
|
id,
|
||||||
|
type: 'junction',
|
||||||
|
edges: [],
|
||||||
|
in: parent,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getJunctions = (): ArchitectureJunction[] =>
|
||||||
|
Object.values(state.records.nodes).filter<ArchitectureJunction>(isArchitectureJunction);
|
||||||
|
|
||||||
|
const getNodes = (): ArchitectureNode[] => Object.values(state.records.nodes);
|
||||||
|
|
||||||
|
const getNode = (id: string): ArchitectureNode | null => state.records.nodes[id];
|
||||||
|
|
||||||
|
const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) {
|
||||||
|
if (state.records.registeredIds[id] !== undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (parent !== undefined) {
|
||||||
|
if (id === parent) {
|
||||||
|
throw new Error(`The group [${id}] cannot be placed within itself`);
|
||||||
|
}
|
||||||
|
if (state.records.registeredIds[parent] === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The group [${id}]'s parent does not exist. Please make sure the parent is created before this group`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (state.records.registeredIds[parent] === 'node') {
|
||||||
|
throw new Error(`The group [${id}]'s parent is not a group`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.records.registeredIds[id] = 'group';
|
||||||
|
|
||||||
|
state.records.groups[id] = {
|
||||||
|
id,
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
in: parent,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const getGroups = (): ArchitectureGroup[] => {
|
||||||
|
return Object.values(state.records.groups);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addEdge = function ({
|
||||||
|
lhsId,
|
||||||
|
rhsId,
|
||||||
|
lhsDir,
|
||||||
|
rhsDir,
|
||||||
|
lhsInto,
|
||||||
|
rhsInto,
|
||||||
|
lhsGroup,
|
||||||
|
rhsGroup,
|
||||||
|
title,
|
||||||
|
}: ArchitectureEdge<string>) {
|
||||||
|
if (!isArchitectureDirection(lhsDir)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!isArchitectureDirection(rhsDir)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid direction given for right hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${rhsDir}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.records.nodes[lhsId] === undefined && state.records.groups[lhsId] === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The left-hand id [${lhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (state.records.nodes[rhsId] === undefined && state.records.groups[lhsId] === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`The right-hand id [${rhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lhsGroupId = state.records.nodes[lhsId].in;
|
||||||
|
const rhsGroupId = state.records.nodes[rhsId].in;
|
||||||
|
if (lhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
|
||||||
|
throw new Error(
|
||||||
|
`The left-hand id [${lhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (rhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
|
||||||
|
throw new Error(
|
||||||
|
`The right-hand id [${rhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const edge = {
|
||||||
|
lhsId,
|
||||||
|
lhsDir,
|
||||||
|
lhsInto,
|
||||||
|
lhsGroup,
|
||||||
|
rhsId,
|
||||||
|
rhsDir,
|
||||||
|
rhsInto,
|
||||||
|
rhsGroup,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.records.edges.push(edge);
|
||||||
|
if (state.records.nodes[lhsId] && state.records.nodes[rhsId]) {
|
||||||
|
state.records.nodes[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
|
||||||
|
state.records.nodes[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEdges = (): ArchitectureEdge[] => state.records.edges;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current diagram's adjacency list & spatial map.
|
||||||
|
* If they have not been created, run the algorithms to generate them.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getDataStructures = () => {
|
||||||
|
if (state.records.dataStructures === undefined) {
|
||||||
|
// Create an adjacency list of the diagram to perform BFS on
|
||||||
|
// Outer reduce applied on all services
|
||||||
|
// Inner reduce applied on the edges for a service
|
||||||
|
const adjList = Object.entries(state.records.nodes).reduce<
|
||||||
|
Record<string, ArchitectureDirectionPairMap>
|
||||||
|
>((prevOuter, [id, service]) => {
|
||||||
|
prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
|
||||||
|
if (edge.lhsId === id) {
|
||||||
|
// source is LHS
|
||||||
|
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
|
||||||
|
if (pair) {
|
||||||
|
prevInner[pair] = edge.rhsId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// source is RHS
|
||||||
|
const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
|
||||||
|
if (pair) {
|
||||||
|
prevInner[pair] = edge.lhsId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prevInner;
|
||||||
|
}, {});
|
||||||
|
return prevOuter;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Configuration for the initial pass of BFS
|
||||||
|
const firstId = Object.keys(adjList)[0];
|
||||||
|
const visited = { [firstId]: 1 };
|
||||||
|
const notVisited = Object.keys(adjList).reduce(
|
||||||
|
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
|
||||||
|
{} as Record<string, number>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform BFS on the adjacency list
|
||||||
|
const BFS = (startingId: string): ArchitectureSpatialMap => {
|
||||||
|
const spatialMap = { [startingId]: [0, 0] };
|
||||||
|
const queue = [startingId];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const id = queue.shift();
|
||||||
|
if (id) {
|
||||||
|
visited[id] = 1;
|
||||||
|
delete notVisited[id];
|
||||||
|
const adj = adjList[id];
|
||||||
|
const [posX, posY] = spatialMap[id];
|
||||||
|
Object.entries(adj).forEach(([dir, rhsId]) => {
|
||||||
|
if (!visited[rhsId]) {
|
||||||
|
spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
|
||||||
|
[posX, posY],
|
||||||
|
dir as ArchitectureDirectionPair
|
||||||
|
);
|
||||||
|
queue.push(rhsId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spatialMap;
|
||||||
|
};
|
||||||
|
const spatialMaps = [BFS(firstId)];
|
||||||
|
|
||||||
|
// If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
|
||||||
|
while (Object.keys(notVisited).length > 0) {
|
||||||
|
spatialMaps.push(BFS(Object.keys(notVisited)[0]));
|
||||||
|
}
|
||||||
|
state.records.dataStructures = {
|
||||||
|
adjList,
|
||||||
|
spatialMaps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state.records.dataStructures;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setElementForId = (id: string, element: D3Element) => {
|
||||||
|
state.records.elements[id] = element;
|
||||||
|
};
|
||||||
|
const getElementById = (id: string) => state.records.elements[id];
|
||||||
|
|
||||||
|
export const db: ArchitectureDB = {
|
||||||
|
clear,
|
||||||
|
setDiagramTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
setAccTitle,
|
||||||
|
getAccTitle,
|
||||||
|
setAccDescription,
|
||||||
|
getAccDescription,
|
||||||
|
|
||||||
|
addService,
|
||||||
|
getServices,
|
||||||
|
addJunction,
|
||||||
|
getJunctions,
|
||||||
|
getNodes,
|
||||||
|
getNode,
|
||||||
|
addGroup,
|
||||||
|
getGroups,
|
||||||
|
addEdge,
|
||||||
|
getEdges,
|
||||||
|
setElementForId,
|
||||||
|
getElementById,
|
||||||
|
getDataStructures,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
|
||||||
|
* @param field - the config field to access
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
|
||||||
|
field: T
|
||||||
|
): Required<ArchitectureDiagramConfig>[T] {
|
||||||
|
const arch = getConfig().architecture;
|
||||||
|
if (arch?.[field]) {
|
||||||
|
return arch[field] as Required<ArchitectureDiagramConfig>[T];
|
||||||
|
}
|
||||||
|
return DEFAULT_ARCHITECTURE_CONFIG[field];
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import type {
|
||||||
|
DiagramDetector,
|
||||||
|
DiagramLoader,
|
||||||
|
ExternalDiagramDefinition,
|
||||||
|
} from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
const id = 'architecture';
|
||||||
|
|
||||||
|
const detector: DiagramDetector = (txt) => {
|
||||||
|
return /^\s*architecture/.test(txt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader: DiagramLoader = async () => {
|
||||||
|
const { diagram } = await import('./architectureDiagram.js');
|
||||||
|
return { id, diagram };
|
||||||
|
};
|
||||||
|
|
||||||
|
const architecture: ExternalDiagramDefinition = {
|
||||||
|
id,
|
||||||
|
detector,
|
||||||
|
loader,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default architecture;
|
@ -0,0 +1,12 @@
|
|||||||
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
|
import { parser } from './architectureParser.js';
|
||||||
|
import { db } from './architectureDb.js';
|
||||||
|
import styles from './architectureStyles.js';
|
||||||
|
import { renderer } from './architectureRenderer.js';
|
||||||
|
|
||||||
|
export const diagram: DiagramDefinition = {
|
||||||
|
parser,
|
||||||
|
db,
|
||||||
|
renderer,
|
||||||
|
styles,
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { Architecture } from '@mermaid-js/parser';
|
||||||
|
import { parse } from '@mermaid-js/parser';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||||
|
import { populateCommonDb } from '../common/populateCommonDb.js';
|
||||||
|
import type { ArchitectureDB } from './architectureTypes.js';
|
||||||
|
import { db } from './architectureDb.js';
|
||||||
|
|
||||||
|
const populateDb = (ast: Architecture, db: ArchitectureDB) => {
|
||||||
|
populateCommonDb(ast, db);
|
||||||
|
ast.groups.map(db.addGroup);
|
||||||
|
ast.services.map((service) => db.addService({ ...service, type: 'service' }));
|
||||||
|
ast.junctions.map((service) => db.addJunction({ ...service, type: 'junction' }));
|
||||||
|
// @ts-ignore TODO our parser guarantees the type is L/R/T/B and not string. How to change to union type?
|
||||||
|
ast.edges.map(db.addEdge);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parser: ParserDefinition = {
|
||||||
|
parse: async (input: string): Promise<void> => {
|
||||||
|
const ast: Architecture = await parse('architecture', input);
|
||||||
|
log.debug(ast);
|
||||||
|
populateDb(ast, db);
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,461 @@
|
|||||||
|
import type { Position } from 'cytoscape';
|
||||||
|
import cytoscape from 'cytoscape';
|
||||||
|
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
|
||||||
|
import fcose from 'cytoscape-fcose';
|
||||||
|
import { select } from 'd3';
|
||||||
|
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
|
||||||
|
import type { Diagram } from '../../Diagram.js';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||||
|
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||||
|
import { getConfigField } from './architectureDb.js';
|
||||||
|
import type {
|
||||||
|
ArchitectureDataStructures,
|
||||||
|
ArchitectureJunction,
|
||||||
|
ArchitectureSpatialMap,
|
||||||
|
EdgeSingular,
|
||||||
|
EdgeSingularData,
|
||||||
|
NodeSingularData,
|
||||||
|
} from './architectureTypes.js';
|
||||||
|
import {
|
||||||
|
type ArchitectureDB,
|
||||||
|
type ArchitectureDirection,
|
||||||
|
type ArchitectureEdge,
|
||||||
|
type ArchitectureGroup,
|
||||||
|
type ArchitectureService,
|
||||||
|
ArchitectureDirectionName,
|
||||||
|
edgeData,
|
||||||
|
getOppositeArchitectureDirection,
|
||||||
|
isArchitectureDirectionXY,
|
||||||
|
isArchitectureDirectionY,
|
||||||
|
nodeData,
|
||||||
|
} from './architectureTypes.js';
|
||||||
|
import { defaultIconLibrary } from './icons/default.js';
|
||||||
|
import { registerIconLibrary } from './icons/svgRegister.js';
|
||||||
|
import { drawEdges, drawGroups, drawJunctions, drawServices } from './svgDraw.js';
|
||||||
|
|
||||||
|
registerIconLibrary(defaultIconLibrary);
|
||||||
|
cytoscape.use(fcose);
|
||||||
|
|
||||||
|
function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
|
||||||
|
services.forEach((service) => {
|
||||||
|
cy.add({
|
||||||
|
group: 'nodes',
|
||||||
|
data: {
|
||||||
|
type: 'service',
|
||||||
|
id: service.id,
|
||||||
|
icon: service.icon,
|
||||||
|
label: service.title,
|
||||||
|
parent: service.in,
|
||||||
|
width: getConfigField('iconSize'),
|
||||||
|
height: getConfigField('iconSize'),
|
||||||
|
} as NodeSingularData,
|
||||||
|
classes: 'node-service',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addJunctions(junctions: ArchitectureJunction[], cy: cytoscape.Core) {
|
||||||
|
junctions.forEach((junction) => {
|
||||||
|
cy.add({
|
||||||
|
group: 'nodes',
|
||||||
|
data: {
|
||||||
|
type: 'junction',
|
||||||
|
id: junction.id,
|
||||||
|
parent: junction.in,
|
||||||
|
width: getConfigField('iconSize'),
|
||||||
|
height: getConfigField('iconSize'),
|
||||||
|
} as NodeSingularData,
|
||||||
|
classes: 'node-junction',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionNodes(db: ArchitectureDB, cy: cytoscape.Core) {
|
||||||
|
cy.nodes().map((node) => {
|
||||||
|
const data = nodeData(node);
|
||||||
|
if (data.type === 'group') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.x = node.position().x;
|
||||||
|
data.y = node.position().y;
|
||||||
|
|
||||||
|
const nodeElem = db.getElementById(data.id);
|
||||||
|
nodeElem.attr('transform', 'translate(' + (data.x || 0) + ',' + (data.y || 0) + ')');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
|
||||||
|
groups.forEach((group) => {
|
||||||
|
cy.add({
|
||||||
|
group: 'nodes',
|
||||||
|
data: {
|
||||||
|
type: 'group',
|
||||||
|
id: group.id,
|
||||||
|
icon: group.icon,
|
||||||
|
label: group.title,
|
||||||
|
parent: group.in,
|
||||||
|
} as NodeSingularData,
|
||||||
|
classes: 'node-group',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
|
||||||
|
edges.forEach((parsedEdge) => {
|
||||||
|
const { lhsId, rhsId, lhsInto, lhsGroup, rhsInto, lhsDir, rhsDir, rhsGroup, title } =
|
||||||
|
parsedEdge;
|
||||||
|
const edgeType = isArchitectureDirectionXY(parsedEdge.lhsDir, parsedEdge.rhsDir)
|
||||||
|
? 'segments'
|
||||||
|
: 'straight';
|
||||||
|
const edge: EdgeSingularData = {
|
||||||
|
id: `${lhsId}-${rhsId}`,
|
||||||
|
label: title,
|
||||||
|
source: lhsId,
|
||||||
|
sourceDir: lhsDir,
|
||||||
|
sourceArrow: lhsInto,
|
||||||
|
sourceGroup: lhsGroup,
|
||||||
|
sourceEndpoint:
|
||||||
|
lhsDir === 'L'
|
||||||
|
? '0 50%'
|
||||||
|
: lhsDir === 'R'
|
||||||
|
? '100% 50%'
|
||||||
|
: lhsDir === 'T'
|
||||||
|
? '50% 0'
|
||||||
|
: '50% 100%',
|
||||||
|
target: rhsId,
|
||||||
|
targetDir: rhsDir,
|
||||||
|
targetArrow: rhsInto,
|
||||||
|
targetGroup: rhsGroup,
|
||||||
|
targetEndpoint:
|
||||||
|
rhsDir === 'L'
|
||||||
|
? '0 50%'
|
||||||
|
: rhsDir === 'R'
|
||||||
|
? '100% 50%'
|
||||||
|
: rhsDir === 'T'
|
||||||
|
? '50% 0'
|
||||||
|
: '50% 100%',
|
||||||
|
};
|
||||||
|
cy.add({
|
||||||
|
group: 'edges',
|
||||||
|
data: edge,
|
||||||
|
classes: edgeType,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignmentConstraint {
|
||||||
|
const alignments = spatialMaps.map((spatialMap) => {
|
||||||
|
const horizontalAlignments: Record<number, string[]> = {};
|
||||||
|
const verticalAlignments: Record<number, string[]> = {};
|
||||||
|
// Group service ids in an object with their x and y coordinate as the key
|
||||||
|
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
|
||||||
|
if (!horizontalAlignments[y]) {
|
||||||
|
horizontalAlignments[y] = [];
|
||||||
|
}
|
||||||
|
if (!verticalAlignments[x]) {
|
||||||
|
verticalAlignments[x] = [];
|
||||||
|
}
|
||||||
|
horizontalAlignments[y].push(id);
|
||||||
|
verticalAlignments[x].push(id);
|
||||||
|
});
|
||||||
|
// Merge the values of each object into a list if the inner list has at least 2 elements
|
||||||
|
return {
|
||||||
|
horiz: Object.values(horizontalAlignments).filter((arr) => arr.length > 1),
|
||||||
|
vert: Object.values(verticalAlignments).filter((arr) => arr.length > 1),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Merge the alignment lists for each spatial map into one 2d array per axis
|
||||||
|
const [horizontal, vertical] = alignments.reduce(
|
||||||
|
([prevHoriz, prevVert], { horiz, vert }) => {
|
||||||
|
return [
|
||||||
|
[...prevHoriz, ...horiz],
|
||||||
|
[...prevVert, ...vert],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
[[] as string[][], [] as string[][]]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
horizontal,
|
||||||
|
vertical,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRelativeConstraints(
|
||||||
|
spatialMaps: ArchitectureSpatialMap[]
|
||||||
|
): fcose.FcoseRelativePlacementConstraint[] {
|
||||||
|
const relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
|
||||||
|
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
|
||||||
|
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
|
||||||
|
|
||||||
|
spatialMaps.forEach((spatialMap) => {
|
||||||
|
const invSpatialMap = Object.fromEntries(
|
||||||
|
Object.entries(spatialMap).map(([id, pos]) => [posToStr(pos), id])
|
||||||
|
);
|
||||||
|
|
||||||
|
// perform BFS
|
||||||
|
const queue = [posToStr([0, 0])];
|
||||||
|
const visited: Record<string, number> = {};
|
||||||
|
const directions: Record<ArchitectureDirection, number[]> = {
|
||||||
|
L: [-1, 0],
|
||||||
|
R: [1, 0],
|
||||||
|
T: [0, 1],
|
||||||
|
B: [0, -1],
|
||||||
|
};
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const curr = queue.shift();
|
||||||
|
if (curr) {
|
||||||
|
visited[curr] = 1;
|
||||||
|
const currId = invSpatialMap[curr];
|
||||||
|
if (currId) {
|
||||||
|
const currPos = strToPos(curr);
|
||||||
|
Object.entries(directions).forEach(([dir, shift]) => {
|
||||||
|
const newPos = posToStr([currPos[0] + shift[0], currPos[1] + shift[1]]);
|
||||||
|
const newId = invSpatialMap[newPos];
|
||||||
|
// If there is an adjacent service to the current one and it has not yet been visited
|
||||||
|
if (newId && !visited[newPos]) {
|
||||||
|
queue.push(newPos);
|
||||||
|
// @ts-ignore cannot determine if left/right or top/bottom are paired together
|
||||||
|
relativeConstraints.push({
|
||||||
|
[ArchitectureDirectionName[dir as ArchitectureDirection]]: newId,
|
||||||
|
[ArchitectureDirectionName[
|
||||||
|
getOppositeArchitectureDirection(dir as ArchitectureDirection)
|
||||||
|
]]: currId,
|
||||||
|
gap: 1.5 * getConfigField('iconSize'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return relativeConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutArchitecture(
|
||||||
|
services: ArchitectureService[],
|
||||||
|
junctions: ArchitectureJunction[],
|
||||||
|
groups: ArchitectureGroup[],
|
||||||
|
edges: ArchitectureEdge[],
|
||||||
|
{ spatialMaps }: ArchitectureDataStructures
|
||||||
|
): Promise<cytoscape.Core> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
|
||||||
|
const cy = cytoscape({
|
||||||
|
container: document.getElementById('cy'),
|
||||||
|
style: [
|
||||||
|
{
|
||||||
|
selector: 'edge',
|
||||||
|
style: {
|
||||||
|
'curve-style': 'straight',
|
||||||
|
label: 'data(label)',
|
||||||
|
'source-endpoint': 'data(sourceEndpoint)',
|
||||||
|
'target-endpoint': 'data(targetEndpoint)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'edge.segments',
|
||||||
|
style: {
|
||||||
|
'curve-style': 'segments',
|
||||||
|
'segment-weights': '0',
|
||||||
|
'segment-distances': [0.5],
|
||||||
|
// @ts-ignore Incorrect library types
|
||||||
|
'edge-distances': 'endpoints',
|
||||||
|
'source-endpoint': 'data(sourceEndpoint)',
|
||||||
|
'target-endpoint': 'data(targetEndpoint)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node',
|
||||||
|
style: {
|
||||||
|
// @ts-ignore Incorrect library types
|
||||||
|
'compound-sizing-wrt-labels': 'include',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node[label]',
|
||||||
|
style: {
|
||||||
|
'text-valign': 'bottom',
|
||||||
|
'text-halign': 'center',
|
||||||
|
'font-size': `${getConfigField('fontSize')}px`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '.node-service',
|
||||||
|
style: {
|
||||||
|
label: 'data(label)',
|
||||||
|
width: 'data(width)',
|
||||||
|
height: 'data(height)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '.node-junction',
|
||||||
|
style: {
|
||||||
|
width: 'data(width)',
|
||||||
|
height: 'data(height)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '.node-group',
|
||||||
|
style: {
|
||||||
|
// @ts-ignore Incorrect library types
|
||||||
|
padding: `${getConfigField('padding')}px`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// Remove element after layout
|
||||||
|
renderEl.remove();
|
||||||
|
|
||||||
|
addGroups(groups, cy);
|
||||||
|
addServices(services, cy);
|
||||||
|
addJunctions(junctions, cy);
|
||||||
|
addEdges(edges, cy);
|
||||||
|
|
||||||
|
// Use the spatial map to create alignment arrays for fcose
|
||||||
|
const alignmentConstraint = getAlignments(spatialMaps);
|
||||||
|
|
||||||
|
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
|
||||||
|
const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
|
||||||
|
|
||||||
|
const layout = cy.layout({
|
||||||
|
name: 'fcose',
|
||||||
|
quality: 'proof',
|
||||||
|
styleEnabled: false,
|
||||||
|
animate: false,
|
||||||
|
nodeDimensionsIncludeLabels: false,
|
||||||
|
// Adjust the edge parameters if it passes through the border of a group
|
||||||
|
// Hacky fix for: https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues/67
|
||||||
|
idealEdgeLength(edge: EdgeSingular) {
|
||||||
|
const [nodeA, nodeB] = edge.connectedNodes();
|
||||||
|
const { parent: parentA } = nodeData(nodeA);
|
||||||
|
const { parent: parentB } = nodeData(nodeB);
|
||||||
|
const elasticity =
|
||||||
|
parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize');
|
||||||
|
return elasticity;
|
||||||
|
},
|
||||||
|
edgeElasticity(edge: EdgeSingular) {
|
||||||
|
const [nodeA, nodeB] = edge.connectedNodes();
|
||||||
|
const { parent: parentA } = nodeData(nodeA);
|
||||||
|
const { parent: parentB } = nodeData(nodeB);
|
||||||
|
const elasticity = parentA === parentB ? 0.45 : 0.001;
|
||||||
|
return elasticity;
|
||||||
|
},
|
||||||
|
alignmentConstraint,
|
||||||
|
relativePlacementConstraint,
|
||||||
|
} as FcoseLayoutOptions);
|
||||||
|
|
||||||
|
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
|
||||||
|
layout.one('layoutstop', () => {
|
||||||
|
function getSegmentWeights(
|
||||||
|
source: Position,
|
||||||
|
target: Position,
|
||||||
|
pointX: number,
|
||||||
|
pointY: number
|
||||||
|
) {
|
||||||
|
let W, D;
|
||||||
|
const { x: sX, y: sY } = source;
|
||||||
|
const { x: tX, y: tY } = target;
|
||||||
|
|
||||||
|
D =
|
||||||
|
(pointY - sY + ((sX - pointX) * (sY - tY)) / (sX - tX)) /
|
||||||
|
Math.sqrt(1 + Math.pow((sY - tY) / (sX - tX), 2));
|
||||||
|
W = Math.sqrt(Math.pow(pointY - sY, 2) + Math.pow(pointX - sX, 2) - Math.pow(D, 2));
|
||||||
|
|
||||||
|
const distAB = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2));
|
||||||
|
W = W / distAB;
|
||||||
|
|
||||||
|
//check whether the point (pointX, pointY) is on right or left of the line src to tgt. for instance : a point C(X, Y) and line (AB). d=(xB-xA)(yC-yA)-(yB-yA)(xC-xA). if d>0, then C is on left of the line. if d<0, it is on right. if d=0, it is on the line.
|
||||||
|
let delta1 = (tX - sX) * (pointY - sY) - (tY - sY) * (pointX - sX);
|
||||||
|
switch (true) {
|
||||||
|
case delta1 >= 0:
|
||||||
|
delta1 = 1;
|
||||||
|
break;
|
||||||
|
case delta1 < 0:
|
||||||
|
delta1 = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//check whether the point (pointX, pointY) is "behind" the line src to tgt
|
||||||
|
let delta2 = (tX - sX) * (pointX - sX) + (tY - sY) * (pointY - sY);
|
||||||
|
switch (true) {
|
||||||
|
case delta2 >= 0:
|
||||||
|
delta2 = 1;
|
||||||
|
break;
|
||||||
|
case delta2 < 0:
|
||||||
|
delta2 = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
D = Math.abs(D) * delta1; //ensure that sign of D is same as sign of delta1. Hence we need to take absolute value of D and multiply by delta1
|
||||||
|
W = W * delta2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
distances: D,
|
||||||
|
weights: W,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cy.startBatch();
|
||||||
|
for (const edge of Object.values(cy.edges())) {
|
||||||
|
if (edge.data?.()) {
|
||||||
|
const { x: sX, y: sY } = edge.source().position();
|
||||||
|
const { x: tX, y: tY } = edge.target().position();
|
||||||
|
if (sX !== tX && sY !== tY) {
|
||||||
|
const sEP = edge.sourceEndpoint();
|
||||||
|
const tEP = edge.targetEndpoint();
|
||||||
|
const { sourceDir } = edgeData(edge);
|
||||||
|
const [pointX, pointY] = isArchitectureDirectionY(sourceDir)
|
||||||
|
? [sEP.x, tEP.y]
|
||||||
|
: [tEP.x, sEP.y];
|
||||||
|
const { weights, distances } = getSegmentWeights(sEP, tEP, pointX, pointY);
|
||||||
|
edge.style('segment-distances', distances);
|
||||||
|
edge.style('segment-weights', weights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cy.endBatch();
|
||||||
|
layout.run();
|
||||||
|
});
|
||||||
|
layout.run();
|
||||||
|
|
||||||
|
cy.ready((e) => {
|
||||||
|
log.info('Ready', e);
|
||||||
|
resolve(cy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
|
||||||
|
const db = diagObj.db as ArchitectureDB;
|
||||||
|
|
||||||
|
const services = db.getServices();
|
||||||
|
const junctions = db.getJunctions();
|
||||||
|
const groups = db.getGroups();
|
||||||
|
const edges = db.getEdges();
|
||||||
|
const ds = db.getDataStructures();
|
||||||
|
|
||||||
|
const svg: SVG = selectSvgElement(id);
|
||||||
|
|
||||||
|
const edgesElem = svg.append('g');
|
||||||
|
edgesElem.attr('class', 'architecture-edges');
|
||||||
|
|
||||||
|
const servicesElem = svg.append('g');
|
||||||
|
servicesElem.attr('class', 'architecture-services');
|
||||||
|
|
||||||
|
const groupElem = svg.append('g');
|
||||||
|
groupElem.attr('class', 'architecture-groups');
|
||||||
|
|
||||||
|
await drawServices(db, servicesElem, services);
|
||||||
|
drawJunctions(db, servicesElem, junctions);
|
||||||
|
|
||||||
|
const cy = await layoutArchitecture(services, junctions, groups, edges, ds);
|
||||||
|
|
||||||
|
await drawEdges(edgesElem, cy);
|
||||||
|
await drawGroups(groupElem, cy);
|
||||||
|
positionNodes(db, cy);
|
||||||
|
|
||||||
|
setupGraphViewbox(undefined, svg, getConfigField('padding'), getConfigField('useMaxWidth'));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderer = { draw };
|
@ -0,0 +1,38 @@
|
|||||||
|
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
|
||||||
|
import type { ArchitectureStyleOptions } from './architectureTypes.js';
|
||||||
|
|
||||||
|
const getStyles: DiagramStylesProvider = (options: ArchitectureStyleOptions) =>
|
||||||
|
`
|
||||||
|
.edge {
|
||||||
|
stroke-width: ${options.archEdgeWidth};
|
||||||
|
stroke: ${options.archEdgeColor};
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
fill: ${options.archEdgeArrowColor};
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-bkg {
|
||||||
|
fill: none;
|
||||||
|
stroke: ${options.archGroupBorderColor};
|
||||||
|
stroke-width: ${options.archGroupBorderWidth};
|
||||||
|
stroke-dasharray: 8;
|
||||||
|
}
|
||||||
|
.node-icon-text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-icon-text > div {
|
||||||
|
color: #fff;
|
||||||
|
margin: 1px;
|
||||||
|
height: fit-content;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default getStyles;
|
351
packages/mermaid/src/diagrams/architecture/architectureTypes.ts
Normal file
351
packages/mermaid/src/diagrams/architecture/architectureTypes.ts
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
|
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||||
|
import type { D3Element } from '../../types.js';
|
||||||
|
import type cytoscape from 'cytoscape';
|
||||||
|
|
||||||
|
/*=======================================*\
|
||||||
|
| Architecture Diagram Types |
|
||||||
|
\*=======================================*/
|
||||||
|
|
||||||
|
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
|
||||||
|
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
|
||||||
|
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains LL, RR, TT, BB which are impossible connections
|
||||||
|
*/
|
||||||
|
export type InvalidArchitectureDirectionPair = `${ArchitectureDirection}${ArchitectureDirection}`;
|
||||||
|
export type ArchitectureDirectionPair = Exclude<
|
||||||
|
InvalidArchitectureDirectionPair,
|
||||||
|
'LL' | 'RR' | 'TT' | 'BB'
|
||||||
|
>;
|
||||||
|
export type ArchitectureDirectionPairXY = Exclude<
|
||||||
|
InvalidArchitectureDirectionPair,
|
||||||
|
'LL' | 'RR' | 'TT' | 'BB' | 'LR' | 'RL' | 'TB' | 'BT'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ArchitectureDirectionName = {
|
||||||
|
L: 'left',
|
||||||
|
R: 'right',
|
||||||
|
T: 'top',
|
||||||
|
B: 'bottom',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ArchitectureDirectionArrow = {
|
||||||
|
L: (scale: number) => `${scale},${scale / 2} 0,${scale} 0,0`,
|
||||||
|
R: (scale: number) => `0,${scale / 2} ${scale},0 ${scale},${scale}`,
|
||||||
|
T: (scale: number) => `0,0 ${scale},0 ${scale / 2},${scale}`,
|
||||||
|
B: (scale: number) => `${scale / 2},0 ${scale},${scale} 0,${scale}`,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ArchitectureDirectionArrowShift = {
|
||||||
|
L: (orig: number, arrowSize: number) => orig - arrowSize + 2,
|
||||||
|
R: (orig: number, _arrowSize: number) => orig - 2,
|
||||||
|
T: (orig: number, arrowSize: number) => orig - arrowSize + 2,
|
||||||
|
B: (orig: number, _arrowSize: number) => orig - 2,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getOppositeArchitectureDirection = function (
|
||||||
|
x: ArchitectureDirection
|
||||||
|
): ArchitectureDirection {
|
||||||
|
if (isArchitectureDirectionX(x)) {
|
||||||
|
return x === 'L' ? 'R' : 'L';
|
||||||
|
} else {
|
||||||
|
return x === 'T' ? 'B' : 'T';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitectureDirection = function (x: unknown): x is ArchitectureDirection {
|
||||||
|
const temp = x as ArchitectureDirection;
|
||||||
|
return temp === 'L' || temp === 'R' || temp === 'T' || temp === 'B';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitectureDirectionX = function (
|
||||||
|
x: ArchitectureDirection
|
||||||
|
): x is ArchitectureDirectionX {
|
||||||
|
const temp = x as ArchitectureDirectionX;
|
||||||
|
return temp === 'L' || temp === 'R';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitectureDirectionY = function (
|
||||||
|
x: ArchitectureDirection
|
||||||
|
): x is ArchitectureDirectionY {
|
||||||
|
const temp = x as ArchitectureDirectionY;
|
||||||
|
return temp === 'T' || temp === 'B';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitectureDirectionXY = function (
|
||||||
|
a: ArchitectureDirection,
|
||||||
|
b: ArchitectureDirection
|
||||||
|
) {
|
||||||
|
const aX_bY = isArchitectureDirectionX(a) && isArchitectureDirectionY(b);
|
||||||
|
const aY_bX = isArchitectureDirectionY(a) && isArchitectureDirectionX(b);
|
||||||
|
return aX_bY || aY_bX;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitecturePairXY = function (
|
||||||
|
pair: ArchitectureDirectionPair
|
||||||
|
): pair is ArchitectureDirectionPairXY {
|
||||||
|
const lhs = pair[0] as ArchitectureDirection;
|
||||||
|
const rhs = pair[1] as ArchitectureDirection;
|
||||||
|
const aX_bY = isArchitectureDirectionX(lhs) && isArchitectureDirectionY(rhs);
|
||||||
|
const aY_bX = isArchitectureDirectionY(lhs) && isArchitectureDirectionX(rhs);
|
||||||
|
return aX_bY || aY_bX;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that the architecture direction pair does not contain an invalid match (LL, RR, TT, BB)
|
||||||
|
* @param x - architecture direction pair which could potentially be invalid
|
||||||
|
* @returns true if the pair is not LL, RR, TT, or BB
|
||||||
|
*/
|
||||||
|
export const isValidArchitectureDirectionPair = function (
|
||||||
|
x: InvalidArchitectureDirectionPair
|
||||||
|
): x is ArchitectureDirectionPair {
|
||||||
|
return x !== 'LL' && x !== 'RR' && x !== 'TT' && x !== 'BB';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ArchitectureDirectionPairMap = {
|
||||||
|
[key in ArchitectureDirectionPair]?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a pair of the directions of each side of an edge. This function should be used instead of manually creating it to ensure that the source is always the first character.
|
||||||
|
*
|
||||||
|
* Note: Undefined is returned when sourceDir and targetDir are the same. In theory this should never happen since the diagram parser throws an error if a user defines it as such.
|
||||||
|
* @param sourceDir - source direction
|
||||||
|
* @param targetDir - target direction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getArchitectureDirectionPair = function (
|
||||||
|
sourceDir: ArchitectureDirection,
|
||||||
|
targetDir: ArchitectureDirection
|
||||||
|
): ArchitectureDirectionPair | undefined {
|
||||||
|
const pair: `${ArchitectureDirection}${ArchitectureDirection}` = `${sourceDir}${targetDir}`;
|
||||||
|
return isValidArchitectureDirectionPair(pair) ? pair : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an x,y position for an arrow and the direction of the edge it belongs to, return a factor for slightly shifting the edge
|
||||||
|
* @param param0 - [x, y] coordinate pair
|
||||||
|
* @param pair - architecture direction pair
|
||||||
|
* @returns a new [x, y] coordinate pair
|
||||||
|
*/
|
||||||
|
export const shiftPositionByArchitectureDirectionPair = function (
|
||||||
|
[x, y]: number[],
|
||||||
|
pair: ArchitectureDirectionPair
|
||||||
|
): number[] {
|
||||||
|
const lhs = pair[0] as ArchitectureDirection;
|
||||||
|
const rhs = pair[1] as ArchitectureDirection;
|
||||||
|
if (isArchitectureDirectionX(lhs)) {
|
||||||
|
if (isArchitectureDirectionY(rhs)) {
|
||||||
|
return [x + (lhs === 'L' ? -1 : 1), y + (rhs === 'T' ? 1 : -1)];
|
||||||
|
} else {
|
||||||
|
return [x + (lhs === 'L' ? -1 : 1), y];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isArchitectureDirectionX(rhs)) {
|
||||||
|
return [x + (rhs === 'L' ? 1 : -1), y + (lhs === 'T' ? 1 : -1)];
|
||||||
|
} else {
|
||||||
|
return [x, y + (lhs === 'T' ? 1 : -1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the directional pair of an XY edge, get the scale factors necessary to shift the coordinates inwards towards the edge
|
||||||
|
* @param pair - XY pair of an edge
|
||||||
|
* @returns - number[] containing [+/- 1, +/- 1]
|
||||||
|
*/
|
||||||
|
export const getArchitectureDirectionXYFactors = function (
|
||||||
|
pair: ArchitectureDirectionPairXY
|
||||||
|
): number[] {
|
||||||
|
if (pair === 'LT' || pair === 'TL') {
|
||||||
|
return [1, 1];
|
||||||
|
} else if (pair === 'BL' || pair === 'LB') {
|
||||||
|
return [1, -1];
|
||||||
|
} else if (pair === 'BR' || pair === 'RB') {
|
||||||
|
return [-1, -1];
|
||||||
|
} else {
|
||||||
|
return [-1, 1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ArchitectureStyleOptions {
|
||||||
|
archEdgeColor: string;
|
||||||
|
archEdgeArrowColor: string;
|
||||||
|
archEdgeWidth: string;
|
||||||
|
archGroupBorderColor: string;
|
||||||
|
archGroupBorderWidth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchitectureService {
|
||||||
|
id: string;
|
||||||
|
type: 'service';
|
||||||
|
edges: ArchitectureEdge[];
|
||||||
|
icon?: string;
|
||||||
|
iconText?: string;
|
||||||
|
title?: string;
|
||||||
|
in?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchitectureJunction {
|
||||||
|
id: string;
|
||||||
|
type: 'junction';
|
||||||
|
edges: ArchitectureEdge[];
|
||||||
|
in?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArchitectureNode = ArchitectureService | ArchitectureJunction;
|
||||||
|
|
||||||
|
export const isArchitectureService = function (x: ArchitectureNode): x is ArchitectureService {
|
||||||
|
const temp = x as ArchitectureService;
|
||||||
|
return temp.type === 'service';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isArchitectureJunction = function (x: ArchitectureNode): x is ArchitectureJunction {
|
||||||
|
const temp = x as ArchitectureJunction;
|
||||||
|
return temp.type === 'junction';
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ArchitectureGroup {
|
||||||
|
id: string;
|
||||||
|
icon?: string;
|
||||||
|
title?: string;
|
||||||
|
in?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchitectureEdge<DT = ArchitectureDirection> {
|
||||||
|
lhsId: string;
|
||||||
|
lhsDir: DT;
|
||||||
|
lhsInto?: boolean;
|
||||||
|
lhsGroup?: boolean;
|
||||||
|
rhsId: string;
|
||||||
|
rhsDir: DT;
|
||||||
|
rhsInto?: boolean;
|
||||||
|
rhsGroup?: boolean;
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchitectureDB extends DiagramDB {
|
||||||
|
clear: () => void;
|
||||||
|
addService: (service: Omit<ArchitectureService, 'edges'>) => void;
|
||||||
|
getServices: () => ArchitectureService[];
|
||||||
|
addJunction: (service: Omit<ArchitectureJunction, 'edges'>) => void;
|
||||||
|
getJunctions: () => ArchitectureJunction[];
|
||||||
|
getNodes: () => ArchitectureNode[];
|
||||||
|
getNode: (id: string) => ArchitectureNode | null;
|
||||||
|
addGroup: (group: ArchitectureGroup) => void;
|
||||||
|
getGroups: () => ArchitectureGroup[];
|
||||||
|
addEdge: (edge: ArchitectureEdge) => void;
|
||||||
|
getEdges: () => ArchitectureEdge[];
|
||||||
|
setElementForId: (id: string, element: D3Element) => void;
|
||||||
|
getElementById: (id: string) => D3Element;
|
||||||
|
getDataStructures: () => ArchitectureDataStructures;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
||||||
|
export type ArchitectureSpatialMap = Record<string, number[]>;
|
||||||
|
export interface ArchitectureDataStructures {
|
||||||
|
adjList: ArchitectureAdjacencyList;
|
||||||
|
spatialMaps: ArchitectureSpatialMap[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchitectureState extends Record<string, unknown> {
|
||||||
|
nodes: Record<string, ArchitectureNode>;
|
||||||
|
groups: Record<string, ArchitectureGroup>;
|
||||||
|
edges: ArchitectureEdge[];
|
||||||
|
registeredIds: Record<string, 'node' | 'group'>;
|
||||||
|
dataStructures?: ArchitectureDataStructures;
|
||||||
|
elements: Record<string, D3Element>;
|
||||||
|
config: ArchitectureDiagramConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*=======================================*\
|
||||||
|
| Cytoscape Override Types |
|
||||||
|
\*=======================================*/
|
||||||
|
|
||||||
|
export interface EdgeSingularData {
|
||||||
|
id: string;
|
||||||
|
label?: string;
|
||||||
|
source: string;
|
||||||
|
sourceDir: ArchitectureDirection;
|
||||||
|
sourceArrow?: boolean;
|
||||||
|
sourceGroup?: boolean;
|
||||||
|
target: string;
|
||||||
|
targetDir: ArchitectureDirection;
|
||||||
|
targetArrow?: boolean;
|
||||||
|
targetGroup?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const edgeData = (edge: cytoscape.EdgeSingular) => {
|
||||||
|
return edge.data() as EdgeSingularData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface EdgeSingular extends cytoscape.EdgeSingular {
|
||||||
|
_private: {
|
||||||
|
bodyBounds: unknown;
|
||||||
|
rscratch: {
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
midX: number;
|
||||||
|
midY: number;
|
||||||
|
endX: number;
|
||||||
|
endY: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
data(): EdgeSingularData;
|
||||||
|
data<T extends keyof EdgeSingularData>(key: T): EdgeSingularData[T];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeSingularData =
|
||||||
|
| {
|
||||||
|
type: 'service';
|
||||||
|
id: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
parent?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'junction';
|
||||||
|
id: string;
|
||||||
|
parent?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'group';
|
||||||
|
id: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
parent?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nodeData = (node: cytoscape.NodeSingular) => {
|
||||||
|
return node.data() as NodeSingularData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface NodeSingular extends cytoscape.NodeSingular {
|
||||||
|
_private: {
|
||||||
|
bodyBounds: {
|
||||||
|
h: number;
|
||||||
|
w: number;
|
||||||
|
x1: number;
|
||||||
|
x2: number;
|
||||||
|
y1: number;
|
||||||
|
y2: number;
|
||||||
|
};
|
||||||
|
children: cytoscape.NodeSingular[];
|
||||||
|
};
|
||||||
|
data(): NodeSingularData;
|
||||||
|
data<T extends keyof NodeSingularData>(key: T): NodeSingularData[T];
|
||||||
|
}
|
99
packages/mermaid/src/diagrams/architecture/icons/default.ts
Normal file
99
packages/mermaid/src/diagrams/architecture/icons/default.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { createIcon, type IconLibrary } from './svgRegister.js';
|
||||||
|
|
||||||
|
export const defaultIconLibrary: IconLibrary = {
|
||||||
|
database: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<path id="b" data-name="4" d="m20,57.86c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<path id="c" data-name="3" d="m20,45.95c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<path id="d" data-name="2" d="m20,34.05c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse id="e" data-name="1" cx="40" cy="22.14" rx="20" ry="7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="20" y1="57.86" x2="20" y2="22.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="60" y1="57.86" x2="60" y2="22.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
server: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<rect x="17.5" y="17.5" width="45" height="45" rx="2" ry="2" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="17.5" y1="32.5" x2="62.5" y2="32.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="17.5" y1="47.5" x2="62.5" y2="47.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<g>
|
||||||
|
<path d="m56.25,25c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/>
|
||||||
|
<path d="m56.25,25c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="m56.25,40c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/>
|
||||||
|
<path d="m56.25,40c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path d="m56.25,55c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/>
|
||||||
|
<path d="m56.25,55c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="32.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="27.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="22.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="32.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="27.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="22.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="32.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="27.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
<circle cx="22.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/>
|
||||||
|
</g>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
disk: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<rect x="20" y="15" width="40" height="50" rx="1" ry="1" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="24" cy="19.17" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="56" cy="19.17" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="24" cy="60.83" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="56" cy="60.83" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="40" cy="33.75" rx="14" ry="14.58" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<ellipse cx="40" cy="33.75" rx="4" ry="4.17" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<path d="m37.51,42.52l-4.83,13.22c-.26.71-1.1,1.02-1.76.64l-4.18-2.42c-.66-.38-.81-1.26-.33-1.84l9.01-10.8c.88-1.05,2.56-.08,2.09,1.2Z" style="fill: #fff; stroke-width: 0px;"/>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
internet: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<circle cx="40" cy="40" r="22.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="40" y1="17.5" x2="40" y2="62.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="17.5" y1="40" x2="62.5" y2="40" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<path d="m39.99,17.51c-15.28,11.1-15.28,33.88,0,44.98" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<path d="m40.01,17.51c15.28,11.1,15.28,33.88,0,44.98" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="19.75" y1="30.1" x2="60.25" y2="30.1" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
<line x1="19.75" y1="49.9" x2="60.25" y2="49.9" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
cloud: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<path d="m65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
unknown: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
<text transform="translate(21.16 64.67)" style="fill: #fff; font-family: ArialMT, Arial; font-size: 67.75px;"><tspan x="0" y="0">?</tspan></text>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
blank: createIcon(
|
||||||
|
`<g>
|
||||||
|
<rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>
|
||||||
|
</g>`,
|
||||||
|
80
|
||||||
|
),
|
||||||
|
};
|
@ -0,0 +1,50 @@
|
|||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import type { Selection } from 'd3-selection';
|
||||||
|
|
||||||
|
export type IconResolver = (
|
||||||
|
parent: Selection<SVGGElement, unknown, Element | null, unknown>,
|
||||||
|
width?: number
|
||||||
|
) => Selection<SVGGElement, unknown, Element | null, unknown>;
|
||||||
|
export type IconLibrary = Record<string, IconResolver>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an SVG Icon passed as a string into a properly formatted IconResolver
|
||||||
|
* @param icon - html code for the svg icon as a string (the SVG tag should not be included)
|
||||||
|
* @param originalSize - the original size of the SVG Icon in pixels
|
||||||
|
* @returns IconResolver
|
||||||
|
*/
|
||||||
|
export const createIcon: (icon: string, originalSize: number) => IconResolver = (
|
||||||
|
icon,
|
||||||
|
originalSize
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
parent: Selection<SVGGElement, unknown, Element | null, unknown>,
|
||||||
|
size: number = originalSize
|
||||||
|
) => {
|
||||||
|
parent.html(`<g style="transform: scale(${size / originalSize})">${icon}</g>`);
|
||||||
|
return parent;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const icons: IconLibrary = {};
|
||||||
|
|
||||||
|
export const isIconNameInUse = (name: string): boolean => {
|
||||||
|
return icons[name] !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerIconLibrary = (library: IconLibrary) => {
|
||||||
|
Object.entries(library).forEach(([name, resolver]) => {
|
||||||
|
if (!isIconNameInUse(name)) {
|
||||||
|
icons[name] = resolver;
|
||||||
|
} else {
|
||||||
|
log.warn(`Icon with name ${name} already exists. Skipping registration.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getIcon = (name: string): IconResolver | null => {
|
||||||
|
if (isIconNameInUse(name)) {
|
||||||
|
return icons[name];
|
||||||
|
}
|
||||||
|
return icons.unknown;
|
||||||
|
};
|
366
packages/mermaid/src/diagrams/architecture/svgDraw.ts
Normal file
366
packages/mermaid/src/diagrams/architecture/svgDraw.ts
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
// TODO remove no-console
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import type cytoscape from 'cytoscape';
|
||||||
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import { createText } from '../../rendering-util/createText.js';
|
||||||
|
import type { D3Element } from '../../types.js';
|
||||||
|
import { db, getConfigField } from './architectureDb.js';
|
||||||
|
import {
|
||||||
|
ArchitectureDirectionArrow,
|
||||||
|
ArchitectureDirectionArrowShift,
|
||||||
|
edgeData,
|
||||||
|
getArchitectureDirectionPair,
|
||||||
|
getArchitectureDirectionXYFactors,
|
||||||
|
isArchitectureDirectionX,
|
||||||
|
isArchitectureDirectionXY,
|
||||||
|
isArchitectureDirectionY,
|
||||||
|
isArchitecturePairXY,
|
||||||
|
nodeData,
|
||||||
|
type ArchitectureDB,
|
||||||
|
type ArchitectureJunction,
|
||||||
|
type ArchitectureService,
|
||||||
|
} from './architectureTypes.js';
|
||||||
|
import { getIcon } from './icons/svgRegister.js';
|
||||||
|
|
||||||
|
export const drawEdges = async function (edgesEl: D3Element, cy: cytoscape.Core) {
|
||||||
|
const padding = getConfigField('padding');
|
||||||
|
const iconSize = getConfigField('iconSize');
|
||||||
|
const halfIconSize = iconSize / 2;
|
||||||
|
const arrowSize = iconSize / 6;
|
||||||
|
const halfArrowSize = arrowSize / 2;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
cy.edges().map(async (edge) => {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
sourceDir,
|
||||||
|
sourceArrow,
|
||||||
|
sourceGroup,
|
||||||
|
target,
|
||||||
|
targetDir,
|
||||||
|
targetArrow,
|
||||||
|
targetGroup,
|
||||||
|
label,
|
||||||
|
} = edgeData(edge);
|
||||||
|
let { x: startX, y: startY } = edge[0].sourceEndpoint();
|
||||||
|
const { x: midX, y: midY } = edge[0].midpoint();
|
||||||
|
let { x: endX, y: endY } = edge[0].targetEndpoint();
|
||||||
|
|
||||||
|
// Adjust the edge distance if it has the {group} modifier
|
||||||
|
const groupEdgeShift = padding + 4;
|
||||||
|
// +18 comes from the service label height that extends the padding on the bottom side of each group
|
||||||
|
if (sourceGroup) {
|
||||||
|
if (isArchitectureDirectionX(sourceDir)) {
|
||||||
|
startX += sourceDir === 'L' ? -groupEdgeShift : groupEdgeShift;
|
||||||
|
} else {
|
||||||
|
startY += sourceDir === 'T' ? -groupEdgeShift : groupEdgeShift + 18;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetGroup) {
|
||||||
|
if (isArchitectureDirectionX(targetDir)) {
|
||||||
|
endX += targetDir === 'L' ? -groupEdgeShift : groupEdgeShift;
|
||||||
|
} else {
|
||||||
|
endY += targetDir === 'T' ? -groupEdgeShift : groupEdgeShift + 18;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the edge distance if it doesn't have the {group} modifier and the endpoint is a junction node
|
||||||
|
if (!sourceGroup && db.getNode(source)?.type === 'junction') {
|
||||||
|
if (isArchitectureDirectionX(sourceDir)) {
|
||||||
|
startX += sourceDir === 'L' ? halfIconSize : -halfIconSize;
|
||||||
|
} else {
|
||||||
|
startY += sourceDir === 'T' ? halfIconSize : -halfIconSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!targetGroup && db.getNode(target)?.type === 'junction') {
|
||||||
|
if (isArchitectureDirectionX(targetDir)) {
|
||||||
|
endX += targetDir === 'L' ? halfIconSize : -halfIconSize;
|
||||||
|
} else {
|
||||||
|
endY += targetDir === 'T' ? halfIconSize : -halfIconSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge[0]._private.rscratch) {
|
||||||
|
// const bounds = edge[0]._private.rscratch;
|
||||||
|
|
||||||
|
const g = edgesEl.insert('g');
|
||||||
|
|
||||||
|
g.insert('path')
|
||||||
|
.attr('d', `M ${startX},${startY} L ${midX},${midY} L${endX},${endY} `)
|
||||||
|
.attr('class', 'edge');
|
||||||
|
|
||||||
|
if (sourceArrow) {
|
||||||
|
const xShift = isArchitectureDirectionX(sourceDir)
|
||||||
|
? ArchitectureDirectionArrowShift[sourceDir](startX, arrowSize)
|
||||||
|
: startX - halfArrowSize;
|
||||||
|
const yShift = isArchitectureDirectionY(sourceDir)
|
||||||
|
? ArchitectureDirectionArrowShift[sourceDir](startY, arrowSize)
|
||||||
|
: startY - halfArrowSize;
|
||||||
|
|
||||||
|
g.insert('polygon')
|
||||||
|
.attr('points', ArchitectureDirectionArrow[sourceDir](arrowSize))
|
||||||
|
.attr('transform', `translate(${xShift},${yShift})`)
|
||||||
|
.attr('class', 'arrow');
|
||||||
|
}
|
||||||
|
if (targetArrow) {
|
||||||
|
const xShift = isArchitectureDirectionX(targetDir)
|
||||||
|
? ArchitectureDirectionArrowShift[targetDir](endX, arrowSize)
|
||||||
|
: endX - halfArrowSize;
|
||||||
|
const yShift = isArchitectureDirectionY(targetDir)
|
||||||
|
? ArchitectureDirectionArrowShift[targetDir](endY, arrowSize)
|
||||||
|
: endY - halfArrowSize;
|
||||||
|
|
||||||
|
g.insert('polygon')
|
||||||
|
.attr('points', ArchitectureDirectionArrow[targetDir](arrowSize))
|
||||||
|
.attr('transform', `translate(${xShift},${yShift})`)
|
||||||
|
.attr('class', 'arrow');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
const axis = !isArchitectureDirectionXY(sourceDir, targetDir)
|
||||||
|
? isArchitectureDirectionX(sourceDir)
|
||||||
|
? 'X'
|
||||||
|
: 'Y'
|
||||||
|
: 'XY';
|
||||||
|
|
||||||
|
let width = 0;
|
||||||
|
if (axis === 'X') {
|
||||||
|
width = Math.abs(startX - endX);
|
||||||
|
} else if (axis === 'Y') {
|
||||||
|
// Reduce width by a factor of 1.5 to avoid overlapping service labels
|
||||||
|
width = Math.abs(startY - endY) / 1.5;
|
||||||
|
} else {
|
||||||
|
width = Math.abs(startX - endX) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textElem = g.append('g');
|
||||||
|
await createText(
|
||||||
|
textElem,
|
||||||
|
label,
|
||||||
|
{
|
||||||
|
useHtmlLabels: false,
|
||||||
|
width,
|
||||||
|
classes: 'architecture-service-label',
|
||||||
|
},
|
||||||
|
getConfig()
|
||||||
|
);
|
||||||
|
|
||||||
|
textElem
|
||||||
|
.attr('dy', '1em')
|
||||||
|
.attr('alignment-baseline', 'middle')
|
||||||
|
.attr('dominant-baseline', 'middle')
|
||||||
|
.attr('text-anchor', 'middle');
|
||||||
|
|
||||||
|
if (axis === 'X') {
|
||||||
|
textElem.attr('transform', 'translate(' + midX + ', ' + midY + ')');
|
||||||
|
} else if (axis === 'Y') {
|
||||||
|
textElem.attr('transform', 'translate(' + midX + ', ' + midY + ') rotate(-90)');
|
||||||
|
} else if (axis === 'XY') {
|
||||||
|
const pair = getArchitectureDirectionPair(sourceDir, targetDir);
|
||||||
|
if (pair && isArchitecturePairXY(pair)) {
|
||||||
|
const bboxOrig = textElem.node().getBoundingClientRect();
|
||||||
|
const [x, y] = getArchitectureDirectionXYFactors(pair);
|
||||||
|
|
||||||
|
textElem
|
||||||
|
.attr('dominant-baseline', 'auto')
|
||||||
|
.attr('transform', `rotate(${-1 * x * y * 45})`);
|
||||||
|
|
||||||
|
// Calculate the new width/height with the rotation applied, and transform to the proper position
|
||||||
|
const bboxNew = textElem.node().getBoundingClientRect();
|
||||||
|
textElem.attr(
|
||||||
|
'transform',
|
||||||
|
`
|
||||||
|
translate(${midX}, ${midY - bboxOrig.height / 2})
|
||||||
|
translate(${(x * bboxNew.width) / 2}, ${(y * bboxNew.height) / 2})
|
||||||
|
rotate(${-1 * x * y * 45}, 0, ${bboxOrig.height / 2})
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawGroups = async function (groupsEl: D3Element, cy: cytoscape.Core) {
|
||||||
|
const padding = getConfigField('padding');
|
||||||
|
const groupIconSize = padding * 0.75;
|
||||||
|
|
||||||
|
const fontSize = getConfigField('fontSize');
|
||||||
|
|
||||||
|
const iconSize = getConfigField('iconSize');
|
||||||
|
const halfIconSize = iconSize / 2;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
cy.nodes().map(async (node) => {
|
||||||
|
const data = nodeData(node);
|
||||||
|
if (data.type === 'group') {
|
||||||
|
const { h, w, x1, y1 } = node.boundingBox();
|
||||||
|
console.log(`Draw group (${data.id}): pos=(${x1}, ${y1}), dim=(${w}, ${h})`);
|
||||||
|
|
||||||
|
groupsEl
|
||||||
|
.append('rect')
|
||||||
|
.attr('x', x1 + halfIconSize)
|
||||||
|
.attr('y', y1 + halfIconSize)
|
||||||
|
.attr('width', w)
|
||||||
|
.attr('height', h)
|
||||||
|
.attr('class', 'node-bkg');
|
||||||
|
|
||||||
|
const groupLabelContainer = groupsEl.append('g');
|
||||||
|
let shiftedX1 = x1;
|
||||||
|
let shiftedY1 = y1;
|
||||||
|
if (data.icon) {
|
||||||
|
const bkgElem = groupLabelContainer.append('g');
|
||||||
|
getIcon(data.icon)?.(bkgElem, groupIconSize);
|
||||||
|
bkgElem.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' +
|
||||||
|
(shiftedX1 + halfIconSize + 1) +
|
||||||
|
', ' +
|
||||||
|
(shiftedY1 + halfIconSize + 1) +
|
||||||
|
')'
|
||||||
|
);
|
||||||
|
shiftedX1 += groupIconSize;
|
||||||
|
// TODO: test with more values
|
||||||
|
// - 1 - 2 comes from the Y axis transform of the icon and label
|
||||||
|
shiftedY1 += fontSize / 2 - 1 - 2;
|
||||||
|
}
|
||||||
|
if (data.label) {
|
||||||
|
const textElem = groupLabelContainer.append('g');
|
||||||
|
await createText(
|
||||||
|
textElem,
|
||||||
|
data.label,
|
||||||
|
{
|
||||||
|
useHtmlLabels: false,
|
||||||
|
width: w,
|
||||||
|
classes: 'architecture-service-label',
|
||||||
|
},
|
||||||
|
getConfig()
|
||||||
|
);
|
||||||
|
textElem
|
||||||
|
.attr('dy', '1em')
|
||||||
|
.attr('alignment-baseline', 'middle')
|
||||||
|
.attr('dominant-baseline', 'start')
|
||||||
|
.attr('text-anchor', 'start');
|
||||||
|
|
||||||
|
textElem.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' +
|
||||||
|
(shiftedX1 + halfIconSize + 4) +
|
||||||
|
', ' +
|
||||||
|
(shiftedY1 + halfIconSize + 2) +
|
||||||
|
')'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawServices = async function (
|
||||||
|
db: ArchitectureDB,
|
||||||
|
elem: D3Element,
|
||||||
|
services: ArchitectureService[]
|
||||||
|
): Promise<number> {
|
||||||
|
for (const service of services) {
|
||||||
|
const serviceElem = elem.append('g');
|
||||||
|
const iconSize = getConfigField('iconSize');
|
||||||
|
|
||||||
|
if (service.title) {
|
||||||
|
const textElem = serviceElem.append('g');
|
||||||
|
await createText(
|
||||||
|
textElem,
|
||||||
|
service.title,
|
||||||
|
{
|
||||||
|
useHtmlLabels: false,
|
||||||
|
width: iconSize * 1.5,
|
||||||
|
classes: 'architecture-service-label',
|
||||||
|
},
|
||||||
|
getConfig()
|
||||||
|
);
|
||||||
|
|
||||||
|
textElem
|
||||||
|
.attr('dy', '1em')
|
||||||
|
.attr('alignment-baseline', 'middle')
|
||||||
|
.attr('dominant-baseline', 'middle')
|
||||||
|
.attr('text-anchor', 'middle');
|
||||||
|
|
||||||
|
textElem.attr('transform', 'translate(' + iconSize / 2 + ', ' + iconSize + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
let bkgElem = serviceElem.append('g');
|
||||||
|
if (service.icon) {
|
||||||
|
// TODO: should a warning be given to end-users saying which icon names are available?
|
||||||
|
// if (!isIconNameInUse(service.icon)) {
|
||||||
|
// throw new Error(`Invalid SVG Icon name: "${service.icon}"`);
|
||||||
|
// }
|
||||||
|
bkgElem = getIcon(service.icon)?.(bkgElem, iconSize);
|
||||||
|
} else if (service.iconText) {
|
||||||
|
bkgElem = getIcon('blank')?.(bkgElem, iconSize);
|
||||||
|
const textElemContainer = bkgElem.append('g');
|
||||||
|
const fo = textElemContainer
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('width', iconSize)
|
||||||
|
.attr('height', iconSize);
|
||||||
|
const divElem = fo
|
||||||
|
.append('div')
|
||||||
|
.attr('class', 'node-icon-text')
|
||||||
|
.attr('style', `height: ${iconSize}px;`)
|
||||||
|
.append('div')
|
||||||
|
.html(service.iconText);
|
||||||
|
const fontSize =
|
||||||
|
parseInt(
|
||||||
|
window
|
||||||
|
.getComputedStyle(divElem.node(), null)
|
||||||
|
.getPropertyValue('font-size')
|
||||||
|
.replace(/\D/g, '')
|
||||||
|
) ?? 16;
|
||||||
|
divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`);
|
||||||
|
} else {
|
||||||
|
bkgElem
|
||||||
|
.append('path')
|
||||||
|
.attr('class', 'node-bkg')
|
||||||
|
.attr('id', 'node-' + service.id)
|
||||||
|
.attr(
|
||||||
|
'd',
|
||||||
|
`M0 ${iconSize} v${-iconSize} q0,-5 5,-5 h${iconSize} q5,0 5,5 v${iconSize} H0 Z`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceElem.attr('class', 'architecture-service');
|
||||||
|
|
||||||
|
const { width, height } = serviceElem._groups[0][0].getBBox();
|
||||||
|
service.width = width;
|
||||||
|
service.height = height;
|
||||||
|
db.setElementForId(service.id, serviceElem);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawJunctions = function (
|
||||||
|
db: ArchitectureDB,
|
||||||
|
elem: D3Element,
|
||||||
|
junctions: ArchitectureJunction[]
|
||||||
|
) {
|
||||||
|
junctions.forEach((junction) => {
|
||||||
|
const junctionElem = elem.append('g');
|
||||||
|
const iconSize = getConfigField('iconSize');
|
||||||
|
|
||||||
|
const bkgElem = junctionElem.append('g');
|
||||||
|
bkgElem
|
||||||
|
.append('rect')
|
||||||
|
.attr('id', 'node-' + junction.id)
|
||||||
|
.attr('fill-opacity', '0')
|
||||||
|
.attr('width', iconSize)
|
||||||
|
.attr('height', iconSize);
|
||||||
|
|
||||||
|
junctionElem.attr('class', 'architecture-junction');
|
||||||
|
|
||||||
|
const { width, height } = junctionElem._groups[0][0].getBBox();
|
||||||
|
junctionElem.width = width;
|
||||||
|
junctionElem.height = height;
|
||||||
|
db.setElementForId(junction.id, junctionElem);
|
||||||
|
});
|
||||||
|
};
|
@ -519,6 +519,8 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
|||||||
// Append a text node containing the label
|
// Append a text node containing the label
|
||||||
const labelId = 'rel' + relCnt;
|
const labelId = 'rel' + relCnt;
|
||||||
|
|
||||||
|
const labelText = rel.roleA.split(/<br ?\/>/g);
|
||||||
|
|
||||||
const labelNode = svg
|
const labelNode = svg
|
||||||
.append('text')
|
.append('text')
|
||||||
.classed('er relationshipLabel', true)
|
.classed('er relationshipLabel', true)
|
||||||
@ -528,8 +530,20 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
|||||||
.style('text-anchor', 'middle')
|
.style('text-anchor', 'middle')
|
||||||
.style('dominant-baseline', 'middle')
|
.style('dominant-baseline', 'middle')
|
||||||
.style('font-family', getConfig().fontFamily)
|
.style('font-family', getConfig().fontFamily)
|
||||||
.style('font-size', conf.fontSize + 'px')
|
.style('font-size', conf.fontSize + 'px');
|
||||||
.text(rel.roleA);
|
|
||||||
|
if (labelText.length == 1) {
|
||||||
|
labelNode.text(rel.roleA);
|
||||||
|
} else {
|
||||||
|
const firstShift = -(labelText.length - 1) * 0.5;
|
||||||
|
labelText.forEach((txt, i) => {
|
||||||
|
labelNode
|
||||||
|
.append('tspan')
|
||||||
|
.attr('x', labelPoint.x)
|
||||||
|
.attr('dy', `${i === 0 ? firstShift : 1}em`)
|
||||||
|
.text(txt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Figure out how big the opaque 'container' rectangle needs to be
|
// Figure out how big the opaque 'container' rectangle needs to be
|
||||||
const labelBBox = labelNode.node().getBBox();
|
const labelBBox = labelNode.node().getBBox();
|
||||||
|
@ -7,13 +7,14 @@ import type {
|
|||||||
const id = 'flowchart-v2';
|
const id = 'flowchart-v2';
|
||||||
|
|
||||||
const detector: DiagramDetector = (txt, config) => {
|
const detector: DiagramDetector = (txt, config) => {
|
||||||
if (
|
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
|
||||||
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
|
|
||||||
config?.flowchart?.defaultRenderer === 'elk'
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config?.flowchart?.defaultRenderer === 'elk') {
|
||||||
|
config.layout = 'elk';
|
||||||
|
}
|
||||||
|
|
||||||
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
|
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
|
||||||
if (/^\s*graph/.test(txt) && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
if (/^\s*graph/.test(txt) && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -157,6 +157,7 @@ function sidebarSyntax() {
|
|||||||
{ text: 'XY Chart 🔥', link: '/syntax/xyChart' },
|
{ text: 'XY Chart 🔥', link: '/syntax/xyChart' },
|
||||||
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
|
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
|
||||||
{ text: 'Packet 🔥', link: '/syntax/packet' },
|
{ text: 'Packet 🔥', link: '/syntax/packet' },
|
||||||
|
{ text: 'Architecture 🔥', link: '/syntax/architecture' },
|
||||||
{ text: 'Other Examples', link: '/syntax/examples' },
|
{ text: 'Other Examples', link: '/syntax/examples' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -176,6 +177,7 @@ function sidebarConfig() {
|
|||||||
{ text: 'Directives', link: '/config/directives' },
|
{ text: 'Directives', link: '/config/directives' },
|
||||||
{ text: 'Theming', link: '/config/theming' },
|
{ text: 'Theming', link: '/config/theming' },
|
||||||
{ text: 'Math', link: '/config/math' },
|
{ text: 'Math', link: '/config/math' },
|
||||||
|
{ text: 'Icons', link: '/config/icons' },
|
||||||
{ text: 'Accessibility', link: '/config/accessibility' },
|
{ text: 'Accessibility', link: '/config/accessibility' },
|
||||||
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
|
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
|
||||||
{ text: 'FAQ', link: '/config/faq' },
|
{ text: 'FAQ', link: '/config/faq' },
|
||||||
|
@ -69,6 +69,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
|||||||
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
|
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
|
||||||
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
|
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
|
||||||
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
|
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
|
||||||
|
- [Microsoft Loop](https://loop.cloud.microsoft) ✅
|
||||||
|
|
||||||
### LLM integrations
|
### LLM integrations
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ import matplotlib.pyplot as plt
|
|||||||
|
|
||||||
def mm(graph):
|
def mm(graph):
|
||||||
graphbytes = graph.encode("utf8")
|
graphbytes = graph.encode("utf8")
|
||||||
base64_bytes = base64.b64encode(graphbytes)
|
base64_bytes = base64.urlsafe_b64encode(graphbytes)
|
||||||
base64_string = base64_bytes.decode("ascii")
|
base64_string = base64_bytes.decode("ascii")
|
||||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||||
|
|
||||||
|
156
packages/mermaid/src/docs/syntax/architecture.md
Normal file
156
packages/mermaid/src/docs/syntax/architecture.md
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# Architecture Diagrams Documentation (v<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
architecture-beta
|
||||||
|
group api(cloud)[API]
|
||||||
|
|
||||||
|
service db(database)[Database] in api
|
||||||
|
service disk1(disk)[Storage] in api
|
||||||
|
service disk2(disk)[Storage] in api
|
||||||
|
service server(server)[Server] in api
|
||||||
|
|
||||||
|
db:L -- R:server
|
||||||
|
disk1:T -- B:server
|
||||||
|
disk2:T -- B:db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
The building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.
|
||||||
|
|
||||||
|
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
|
||||||
|
|
||||||
|
To begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
The syntax for declaring a group is:
|
||||||
|
|
||||||
|
```
|
||||||
|
group {group id}({icon name})[{title}] (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
Put together:
|
||||||
|
|
||||||
|
```
|
||||||
|
group public_api(cloud)[Public API]
|
||||||
|
```
|
||||||
|
|
||||||
|
creates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.
|
||||||
|
|
||||||
|
Additionally, groups can be placed within a group using the optional `in` keyword
|
||||||
|
|
||||||
|
```
|
||||||
|
group private_api(cloud)[Private API] in public_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Services
|
||||||
|
|
||||||
|
The syntax for declaring a service is:
|
||||||
|
|
||||||
|
```
|
||||||
|
service {service id}({icon name})[{title}] (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
Put together:
|
||||||
|
|
||||||
|
```
|
||||||
|
service database(db)[Database]
|
||||||
|
```
|
||||||
|
|
||||||
|
creates the service identified as `database`, using the icon `db`, with the label `Database`.
|
||||||
|
|
||||||
|
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
|
||||||
|
|
||||||
|
```
|
||||||
|
service database(db)[Database] in private_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edges
|
||||||
|
|
||||||
|
The syntax for declaring an edge is:
|
||||||
|
|
||||||
|
```
|
||||||
|
{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Edge Direction
|
||||||
|
|
||||||
|
The side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
db:R -- L:server
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.
|
||||||
|
|
||||||
|
```
|
||||||
|
db:T -- L:server
|
||||||
|
```
|
||||||
|
|
||||||
|
creates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.
|
||||||
|
|
||||||
|
#### Arrows
|
||||||
|
|
||||||
|
Arrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
subnet:R --> L:gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge with the arrow going into the `gateway` service
|
||||||
|
|
||||||
|
#### Edges out of Groups
|
||||||
|
|
||||||
|
To have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
service server[Server] in groupOne
|
||||||
|
service subnet[Subnet] in groupTwo
|
||||||
|
|
||||||
|
server{group}:B --> T:subnet{group}
|
||||||
|
```
|
||||||
|
|
||||||
|
creates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.
|
||||||
|
|
||||||
|
It's important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.
|
||||||
|
|
||||||
|
### Junctions
|
||||||
|
|
||||||
|
Junctions are a special type of node which acts as a potential 4-way split between edges.
|
||||||
|
|
||||||
|
The syntax for declaring a junction is:
|
||||||
|
|
||||||
|
```
|
||||||
|
junction {junction id} (in {parent id})?
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
architecture-beta
|
||||||
|
service left_disk(disk)[Disk]
|
||||||
|
service top_disk(disk)[Disk]
|
||||||
|
service bottom_disk(disk)[Disk]
|
||||||
|
service top_gateway(internet)[Gateway]
|
||||||
|
service bottom_gateway(internet)[Gateway]
|
||||||
|
junction junctionCenter
|
||||||
|
junction junctionRight
|
||||||
|
|
||||||
|
left_disk:R -- L:junctionCenter
|
||||||
|
top_disk:B -- T:junctionCenter
|
||||||
|
bottom_disk:T -- B:junctionCenter
|
||||||
|
junctionCenter:R -- L:junctionRight
|
||||||
|
top_gateway:B -- T:junctionRight
|
||||||
|
bottom_gateway:T -- B:junctionRight
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
@ -192,6 +192,7 @@ erDiagram
|
|||||||
|
|
||||||
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
||||||
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
||||||
|
- (v<MERMAID_RELEASE_VERSION>+) If you want a multi-line label on a relationship, use `<br />` between the two lines (`"first line<br />second line"`)
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
|
@ -6,26 +6,26 @@
|
|||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import { compile, serialize, stringify } from 'stylis';
|
import { compile, serialize, stringify } from 'stylis';
|
||||||
// @ts-ignore: TODO Fix ts errors
|
// @ts-ignore: TODO Fix ts errors
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import isEmpty from 'lodash-es/isEmpty.js';
|
||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
import { addSVGa11yTitleDescription, setA11yDiagramInfo } from './accessibility.js';
|
||||||
|
import assignWithDepth from './assignWithDepth.js';
|
||||||
import * as configApi from './config.js';
|
import * as configApi from './config.js';
|
||||||
|
import type { MermaidConfig } from './config.type.js';
|
||||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||||
|
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
|
||||||
import { Diagram } from './Diagram.js';
|
import { Diagram } from './Diagram.js';
|
||||||
|
import { evaluate } from './diagrams/common/common.js';
|
||||||
import errorRenderer from './diagrams/error/errorRenderer.js';
|
import errorRenderer from './diagrams/error/errorRenderer.js';
|
||||||
import { attachFunctions } from './interactionDb.js';
|
import { attachFunctions } from './interactionDb.js';
|
||||||
import { log, setLogLevel } from './logger.js';
|
import { log, setLogLevel } from './logger.js';
|
||||||
|
import { preprocessDiagram } from './preprocess.js';
|
||||||
import getStyles from './styles.js';
|
import getStyles from './styles.js';
|
||||||
import theme from './themes/index.js';
|
import theme from './themes/index.js';
|
||||||
import DOMPurify from 'dompurify';
|
import type { D3Element, ParseOptions, ParseResult, RenderResult } from './types.js';
|
||||||
import type { MermaidConfig } from './config.type.js';
|
|
||||||
import { evaluate } from './diagrams/common/common.js';
|
|
||||||
import isEmpty from 'lodash-es/isEmpty.js';
|
|
||||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
|
||||||
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
|
|
||||||
import { preprocessDiagram } from './preprocess.js';
|
|
||||||
import { decodeEntities } from './utils.js';
|
import { decodeEntities } from './utils.js';
|
||||||
import { toBase64 } from './utils/base64.js';
|
import { toBase64 } from './utils/base64.js';
|
||||||
import type { D3Element, ParseOptions, ParseResult, RenderResult } from './types.js';
|
|
||||||
import assignWithDepth from './assignWithDepth.js';
|
|
||||||
|
|
||||||
const MAX_TEXTLENGTH = 50_000;
|
const MAX_TEXTLENGTH = 50_000;
|
||||||
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
||||||
@ -477,7 +477,7 @@ const render = async function (
|
|||||||
* @param userOptions - Initial Mermaid options
|
* @param userOptions - Initial Mermaid options
|
||||||
*/
|
*/
|
||||||
function initialize(userOptions: MermaidConfig = {}) {
|
function initialize(userOptions: MermaidConfig = {}) {
|
||||||
const options = assignWithDepth({}, userOptions);
|
const options: MermaidConfig = assignWithDepth({}, userOptions);
|
||||||
// Handle legacy location of font-family configuration
|
// Handle legacy location of font-family configuration
|
||||||
if (options?.fontFamily && !options.themeVariables?.fontFamily) {
|
if (options?.fontFamily && !options.themeVariables?.fontFamily) {
|
||||||
if (!options.themeVariables) {
|
if (!options.themeVariables) {
|
||||||
|
@ -28,7 +28,7 @@ import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js'
|
|||||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
||||||
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
|
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
||||||
const dir = graph.graph().rankdir;
|
const dir = graph.graph().rankdir;
|
||||||
log.trace('Dir in recursive render - dir:', dir);
|
log.trace('Dir in recursive render - dir:', dir);
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
if (graph.children(v).length > 0) {
|
if (graph.children(v).length > 0) {
|
||||||
// This is a cluster but not to be rendered recursively
|
// This is a cluster but not to be rendered recursively
|
||||||
// Render as before
|
// Render as before
|
||||||
log.info(
|
log.trace(
|
||||||
'Cluster - the non recursive path XBX',
|
'Cluster - the non recursive path XBX',
|
||||||
v,
|
v,
|
||||||
node.id,
|
node.id,
|
||||||
@ -120,11 +120,11 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
'Graph:',
|
'Graph:',
|
||||||
graph
|
graph
|
||||||
);
|
);
|
||||||
log.info(findNonClusterChild(node.id, graph));
|
log.trace(findNonClusterChild(node.id, graph));
|
||||||
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
|
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
|
||||||
// insertCluster(clusters, graph.node(v));
|
// insertCluster(clusters, graph.node(v));
|
||||||
} else {
|
} else {
|
||||||
log.trace('Node - the non recursive path XAX', v, node.id, node);
|
log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
|
||||||
await insertNode(nodes, graph.node(v), dir);
|
await insertNode(nodes, graph.node(v), dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,12 +305,64 @@ export const render = async (data4Layout, svg) => {
|
|||||||
|
|
||||||
log.debug('Edges:', data4Layout.edges);
|
log.debug('Edges:', data4Layout.edges);
|
||||||
data4Layout.edges.forEach((edge) => {
|
data4Layout.edges.forEach((edge) => {
|
||||||
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
|
// Handle self-loops
|
||||||
|
if (edge.start === edge.end) {
|
||||||
|
const nodeId = edge.start;
|
||||||
|
const specialId1 = nodeId + '---' + nodeId + '---1';
|
||||||
|
const specialId2 = nodeId + '---' + nodeId + '---2';
|
||||||
|
const node = graph.node(nodeId);
|
||||||
|
graph.setNode(specialId1, {
|
||||||
|
domId: specialId1,
|
||||||
|
id: specialId1,
|
||||||
|
parentId: node.parentId,
|
||||||
|
labelStyle: '',
|
||||||
|
label: '',
|
||||||
|
padding: 0,
|
||||||
|
shape: 'labelRect',
|
||||||
|
// shape: 'rect',
|
||||||
|
style: '',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
});
|
||||||
|
graph.setParent(specialId1, node.parentId);
|
||||||
|
graph.setNode(specialId2, {
|
||||||
|
domId: specialId2,
|
||||||
|
id: specialId2,
|
||||||
|
parentId: node.parentId,
|
||||||
|
labelStyle: '',
|
||||||
|
padding: 0,
|
||||||
|
// shape: 'rect',
|
||||||
|
shape: 'labelRect',
|
||||||
|
label: '',
|
||||||
|
style: '',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
});
|
||||||
|
graph.setParent(specialId2, node.parentId);
|
||||||
|
|
||||||
|
const edge1 = structuredClone(edge);
|
||||||
|
const edgeMid = structuredClone(edge);
|
||||||
|
const edge2 = structuredClone(edge);
|
||||||
|
edge1.label = '';
|
||||||
|
edge1.arrowTypeEnd = 'none';
|
||||||
|
edge1.id = nodeId + '-cyclic-special-1';
|
||||||
|
edgeMid.arrowTypeEnd = 'none';
|
||||||
|
edgeMid.id = nodeId + '-cyclic-special-mid';
|
||||||
|
edge2.label = '';
|
||||||
|
edge1.fromCluster = nodeId;
|
||||||
|
edge2.toCluster = nodeId;
|
||||||
|
edge2.id = nodeId + '-cyclic-special-2';
|
||||||
|
graph.setEdge(nodeId, specialId1, edge1, nodeId + '-cyclic-special-0');
|
||||||
|
graph.setEdge(specialId1, specialId2, edgeMid, nodeId + '-cyclic-special-1');
|
||||||
|
graph.setEdge(specialId2, nodeId, edge2, nodeId + '-cyc<lic-special-2');
|
||||||
|
} else {
|
||||||
|
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
adjustClustersAndEdges(graph);
|
adjustClustersAndEdges(graph);
|
||||||
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
|
log.warn('Graph after XAX:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
const siteConfig = getConfig();
|
const siteConfig = getConfig();
|
||||||
await recursiveRender(
|
await recursiveRender(
|
||||||
element,
|
element,
|
||||||
|
@ -267,51 +267,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
|||||||
' --- ',
|
' --- ',
|
||||||
clusterDb.get(e.w)
|
clusterDb.get(e.w)
|
||||||
);
|
);
|
||||||
if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
|
if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
|
||||||
log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
|
|
||||||
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
|
||||||
v = getAnchorId(e.v);
|
|
||||||
w = getAnchorId(e.w);
|
|
||||||
graph.removeEdge(e.v, e.w, e.name);
|
|
||||||
const specialId1 = e.w + '---' + e.v + '---1';
|
|
||||||
const specialId2 = e.w + '---' + e.v + '---2';
|
|
||||||
graph.setNode(specialId1, {
|
|
||||||
domId: specialId1,
|
|
||||||
id: specialId1,
|
|
||||||
labelStyle: '',
|
|
||||||
label: '',
|
|
||||||
padding: 0,
|
|
||||||
shape: 'labelRect',
|
|
||||||
style: '',
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
});
|
|
||||||
graph.setNode(specialId2, {
|
|
||||||
domId: specialId2,
|
|
||||||
id: specialId2,
|
|
||||||
labelStyle: '',
|
|
||||||
padding: 0,
|
|
||||||
shape: 'labelRect',
|
|
||||||
style: '',
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
});
|
|
||||||
const edge1 = structuredClone(edge);
|
|
||||||
const edgeMid = structuredClone(edge);
|
|
||||||
const edge2 = structuredClone(edge);
|
|
||||||
edge1.label = '';
|
|
||||||
edge1.arrowTypeEnd = 'none';
|
|
||||||
edge1.id = e.name + '-cyclic-special-1';
|
|
||||||
edgeMid.arrowTypeEnd = 'none';
|
|
||||||
edgeMid.id = e.name + '-cyclic-special-mid';
|
|
||||||
edge2.label = '';
|
|
||||||
edge1.fromCluster = e.v;
|
|
||||||
edge2.toCluster = e.v;
|
|
||||||
edge2.id = e.name + '-cyclic-special-2';
|
|
||||||
graph.setEdge(v, specialId1, edge1, e.name + '-cyclic-special-0');
|
|
||||||
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
|
|
||||||
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
|
|
||||||
} else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
|
|
||||||
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
||||||
v = getAnchorId(e.v);
|
v = getAnchorId(e.v);
|
||||||
w = getAnchorId(e.w);
|
w = getAnchorId(e.w);
|
||||||
@ -334,13 +290,6 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
|||||||
extractor(graph, 0);
|
extractor(graph, 0);
|
||||||
|
|
||||||
log.trace(clusterDb);
|
log.trace(clusterDb);
|
||||||
|
|
||||||
// Remove references to extracted cluster
|
|
||||||
// graph.edges().forEach((edge) => {
|
|
||||||
// if (isDescendant(edge.v, clusterId) || isDescendant(edge.w, clusterId)) {
|
|
||||||
// graph.removeEdge(edge);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const extractor = (graph, depth) => {
|
export const extractor = (graph, depth) => {
|
||||||
@ -441,7 +390,7 @@ export const extractor = (graph, depth) => {
|
|||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const data = graph.node(node);
|
const data = graph.node(node);
|
||||||
log.warn(' Now next level', node, data);
|
log.warn(' Now next level', node, data);
|
||||||
if (data.clusterNode) {
|
if (data?.clusterNode) {
|
||||||
extractor(data.graph, depth + 1);
|
extractor(data.graph, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ required:
|
|||||||
- quadrantChart
|
- quadrantChart
|
||||||
- xyChart
|
- xyChart
|
||||||
- requirement
|
- requirement
|
||||||
|
- architecture
|
||||||
- mindmap
|
- mindmap
|
||||||
- gitGraph
|
- gitGraph
|
||||||
- c4
|
- c4
|
||||||
@ -119,6 +120,17 @@ properties:
|
|||||||
- LINEAR_SEGMENTS
|
- LINEAR_SEGMENTS
|
||||||
- BRANDES_KOEPF
|
- BRANDES_KOEPF
|
||||||
default: BRANDES_KOEPF
|
default: BRANDES_KOEPF
|
||||||
|
cycleBreakingStrategy:
|
||||||
|
description: |
|
||||||
|
This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- GREEDY
|
||||||
|
- DEPTH_FIRST
|
||||||
|
- INTERACTIVE
|
||||||
|
- MODEL_ORDER
|
||||||
|
- GREEDY_MODEL_ORDER
|
||||||
|
default: GREEDY_MODEL_ORDER
|
||||||
darkMode:
|
darkMode:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
@ -263,6 +275,8 @@ properties:
|
|||||||
$ref: '#/$defs/XYChartConfig'
|
$ref: '#/$defs/XYChartConfig'
|
||||||
requirement:
|
requirement:
|
||||||
$ref: '#/$defs/RequirementDiagramConfig'
|
$ref: '#/$defs/RequirementDiagramConfig'
|
||||||
|
architecture:
|
||||||
|
$ref: '#/$defs/ArchitectureDiagramConfig'
|
||||||
mindmap:
|
mindmap:
|
||||||
$ref: '#/$defs/MindmapDiagramConfig'
|
$ref: '#/$defs/MindmapDiagramConfig'
|
||||||
gitGraph:
|
gitGraph:
|
||||||
@ -910,6 +924,28 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||||||
type: number
|
type: number
|
||||||
default: 20
|
default: 20
|
||||||
|
|
||||||
|
ArchitectureDiagramConfig:
|
||||||
|
title: Architecture Diagram Config
|
||||||
|
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
||||||
|
description: The object containing configurations specific for architecture diagrams
|
||||||
|
type: object
|
||||||
|
unevaluatedProperties: false
|
||||||
|
required:
|
||||||
|
- useMaxWidth
|
||||||
|
- padding
|
||||||
|
- iconSize
|
||||||
|
- fontSize
|
||||||
|
properties:
|
||||||
|
padding:
|
||||||
|
type: number
|
||||||
|
default: 40
|
||||||
|
iconSize:
|
||||||
|
type: number
|
||||||
|
default: 80
|
||||||
|
fontSize:
|
||||||
|
type: number
|
||||||
|
default: 16
|
||||||
|
|
||||||
MindmapDiagramConfig:
|
MindmapDiagramConfig:
|
||||||
title: Mindmap Diagram Config
|
title: Mindmap Diagram Config
|
||||||
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
||||||
|
@ -220,6 +220,13 @@ class Theme {
|
|||||||
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
|
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
|
||||||
this.pieOpacity = this.pieOpacity || '0.7';
|
this.pieOpacity = this.pieOpacity || '0.7';
|
||||||
|
|
||||||
|
/* architecture */
|
||||||
|
this.archEdgeColor = this.archEdgeColor || '#777';
|
||||||
|
this.archEdgeArrowColor = this.archEdgeArrowColor || '#777';
|
||||||
|
this.archEdgeWidth = this.archEdgeWidth || '3';
|
||||||
|
this.archGroupBorderColor = this.archGroupBorderColor || '#000';
|
||||||
|
this.archGroupBorderWidth = this.archGroupBorderWidth || '2px';
|
||||||
|
|
||||||
/* quadrant-graph */
|
/* quadrant-graph */
|
||||||
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
|
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
|
||||||
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
|
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
|
||||||
|
@ -84,6 +84,13 @@ class Theme {
|
|||||||
this.personBorder = this.primaryBorderColor;
|
this.personBorder = this.primaryBorderColor;
|
||||||
this.personBkg = this.mainBkg;
|
this.personBkg = this.mainBkg;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = 'calculated';
|
||||||
|
this.archEdgeArrowColor = 'calculated';
|
||||||
|
this.archEdgeWidth = '3';
|
||||||
|
this.archGroupBorderColor = this.primaryBorderColor;
|
||||||
|
this.archGroupBorderWidth = '2px';
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.labelColor = 'calculated';
|
this.labelColor = 'calculated';
|
||||||
|
|
||||||
@ -132,6 +139,10 @@ class Theme {
|
|||||||
this.doneTaskBkgColor = this.mainContrastColor;
|
this.doneTaskBkgColor = this.mainContrastColor;
|
||||||
this.taskTextDarkColor = this.darkTextColor;
|
this.taskTextDarkColor = this.darkTextColor;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = this.lineColor;
|
||||||
|
this.archEdgeArrowColor = this.lineColor;
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.transitionColor = this.transitionColor || this.lineColor;
|
this.transitionColor = this.transitionColor || this.lineColor;
|
||||||
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
||||||
|
@ -112,6 +112,13 @@ class Theme {
|
|||||||
this.personBorder = this.primaryBorderColor;
|
this.personBorder = this.primaryBorderColor;
|
||||||
this.personBkg = this.mainBkg;
|
this.personBkg = this.mainBkg;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = 'calculated';
|
||||||
|
this.archEdgeArrowColor = 'calculated';
|
||||||
|
this.archEdgeWidth = '3';
|
||||||
|
this.archGroupBorderColor = this.primaryBorderColor;
|
||||||
|
this.archGroupBorderWidth = '2px';
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.labelColor = 'black';
|
this.labelColor = 'black';
|
||||||
this.errorBkgColor = '#552222';
|
this.errorBkgColor = '#552222';
|
||||||
@ -194,6 +201,10 @@ class Theme {
|
|||||||
this.taskTextColor = this.taskTextLightColor;
|
this.taskTextColor = this.taskTextLightColor;
|
||||||
this.taskTextOutsideColor = this.taskTextDarkColor;
|
this.taskTextOutsideColor = this.taskTextDarkColor;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = this.lineColor;
|
||||||
|
this.archEdgeArrowColor = this.lineColor;
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.transitionColor = this.transitionColor || this.lineColor;
|
this.transitionColor = this.transitionColor || this.lineColor;
|
||||||
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
||||||
|
@ -86,6 +86,13 @@ class Theme {
|
|||||||
this.personBorder = this.primaryBorderColor;
|
this.personBorder = this.primaryBorderColor;
|
||||||
this.personBkg = this.mainBkg;
|
this.personBkg = this.mainBkg;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = 'calculated';
|
||||||
|
this.archEdgeArrowColor = 'calculated';
|
||||||
|
this.archEdgeWidth = '3';
|
||||||
|
this.archGroupBorderColor = this.primaryBorderColor;
|
||||||
|
this.archGroupBorderWidth = '2px';
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.labelColor = 'black';
|
this.labelColor = 'black';
|
||||||
|
|
||||||
@ -162,6 +169,10 @@ class Theme {
|
|||||||
this.activeTaskBorderColor = this.taskBorderColor;
|
this.activeTaskBorderColor = this.taskBorderColor;
|
||||||
this.activeTaskBkgColor = this.mainBkg;
|
this.activeTaskBkgColor = this.mainBkg;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = this.lineColor;
|
||||||
|
this.archEdgeArrowColor = this.lineColor;
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.transitionColor = this.transitionColor || this.lineColor;
|
this.transitionColor = this.transitionColor || this.lineColor;
|
||||||
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
||||||
|
@ -98,6 +98,13 @@ class Theme {
|
|||||||
this.personBorder = this.primaryBorderColor;
|
this.personBorder = this.primaryBorderColor;
|
||||||
this.personBkg = this.mainBkg;
|
this.personBkg = this.mainBkg;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = 'calculated';
|
||||||
|
this.archEdgeArrowColor = 'calculated';
|
||||||
|
this.archEdgeWidth = '3';
|
||||||
|
this.archGroupBorderColor = this.primaryBorderColor;
|
||||||
|
this.archGroupBorderWidth = '2px';
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.labelColor = 'black';
|
this.labelColor = 'black';
|
||||||
|
|
||||||
@ -199,6 +206,10 @@ class Theme {
|
|||||||
|
|
||||||
this.todayLineColor = this.critBkgColor;
|
this.todayLineColor = this.critBkgColor;
|
||||||
|
|
||||||
|
/* Architecture Diagram variables */
|
||||||
|
this.archEdgeColor = this.lineColor;
|
||||||
|
this.archEdgeArrowColor = this.lineColor;
|
||||||
|
|
||||||
/* state colors */
|
/* state colors */
|
||||||
this.transitionColor = this.transitionColor || '#000';
|
this.transitionColor = this.transitionColor || '#000';
|
||||||
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
this.transitionLabelColor = this.transitionLabelColor || this.textColor;
|
||||||
|
@ -16,6 +16,11 @@
|
|||||||
"grammar": "src/language/pie/pie.langium",
|
"grammar": "src/language/pie/pie.langium",
|
||||||
"fileExtensions": [".mmd", ".mermaid"]
|
"fileExtensions": [".mmd", ".mermaid"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "architecture",
|
||||||
|
"grammar": "src/language/architecture/architecture.langium",
|
||||||
|
"fileExtensions": [".mmd", ".mermaid"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "gitGraph",
|
"id": "gitGraph",
|
||||||
"grammar": "src/language/gitGraph/gitGraph.langium",
|
"grammar": "src/language/gitGraph/gitGraph.langium",
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
grammar Architecture
|
||||||
|
import "../common/common";
|
||||||
|
|
||||||
|
entry Architecture:
|
||||||
|
NEWLINE*
|
||||||
|
"architecture-beta"
|
||||||
|
(
|
||||||
|
NEWLINE* TitleAndAccessibilities
|
||||||
|
| NEWLINE* Statement*
|
||||||
|
| NEWLINE*
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment Statement:
|
||||||
|
groups+=Group
|
||||||
|
| services+=Service
|
||||||
|
| junctions+=Junction
|
||||||
|
| edges+=Edge
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment LeftPort:
|
||||||
|
':'lhsDir=ARROW_DIRECTION
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment RightPort:
|
||||||
|
rhsDir=ARROW_DIRECTION':'
|
||||||
|
;
|
||||||
|
|
||||||
|
fragment Arrow:
|
||||||
|
LeftPort lhsInto?=ARROW_INTO? ('--' | '-' title=ARCH_TITLE '-') rhsInto?=ARROW_INTO? RightPort
|
||||||
|
;
|
||||||
|
|
||||||
|
Group:
|
||||||
|
'group' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
|
||||||
|
;
|
||||||
|
|
||||||
|
Service:
|
||||||
|
'service' id=ARCH_ID (iconText=ARCH_TEXT_ICON | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
|
||||||
|
;
|
||||||
|
|
||||||
|
Junction:
|
||||||
|
'junction' id=ARCH_ID ('in' in=ARCH_ID)? EOL
|
||||||
|
;
|
||||||
|
|
||||||
|
Edge:
|
||||||
|
lhsId=ARCH_ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ARCH_ID rhsGroup?=ARROW_GROUP? EOL
|
||||||
|
;
|
||||||
|
|
||||||
|
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
|
||||||
|
terminal ARCH_ID: /[\w]+/;
|
||||||
|
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
|
||||||
|
terminal ARCH_ICON: /\([\w:]+\)/;
|
||||||
|
terminal ARCH_TITLE: /\[[\w ]+\]/;
|
||||||
|
terminal ARROW_GROUP: /\{group\}/;
|
||||||
|
terminal ARROW_INTO: /<|>/;
|
1
packages/parser/src/language/architecture/index.ts
Normal file
1
packages/parser/src/language/architecture/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './module.js';
|
79
packages/parser/src/language/architecture/module.ts
Normal file
79
packages/parser/src/language/architecture/module.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import type {
|
||||||
|
DefaultSharedCoreModuleContext,
|
||||||
|
LangiumCoreServices,
|
||||||
|
LangiumSharedCoreServices,
|
||||||
|
Module,
|
||||||
|
PartialLangiumCoreServices,
|
||||||
|
} from 'langium';
|
||||||
|
import {
|
||||||
|
EmptyFileSystem,
|
||||||
|
createDefaultCoreModule,
|
||||||
|
createDefaultSharedCoreModule,
|
||||||
|
inject,
|
||||||
|
} from 'langium';
|
||||||
|
|
||||||
|
import { MermaidGeneratedSharedModule, ArchitectureGeneratedModule } from '../generated/module.js';
|
||||||
|
import { ArchitectureTokenBuilder } from './tokenBuilder.js';
|
||||||
|
import { ArchitectureValueConverter } from './valueConverter.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration of `Architecture` services.
|
||||||
|
*/
|
||||||
|
interface ArchitectureAddedServices {
|
||||||
|
parser: {
|
||||||
|
TokenBuilder: ArchitectureTokenBuilder;
|
||||||
|
ValueConverter: ArchitectureValueConverter;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of Langium default services and `Architecture` services.
|
||||||
|
*/
|
||||||
|
export type ArchitectureServices = LangiumCoreServices & ArchitectureAddedServices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency injection module that overrides Langium default services and
|
||||||
|
* contributes the declared `Architecture` services.
|
||||||
|
*/
|
||||||
|
export const ArchitectureModule: Module<
|
||||||
|
ArchitectureServices,
|
||||||
|
PartialLangiumCoreServices & ArchitectureAddedServices
|
||||||
|
> = {
|
||||||
|
parser: {
|
||||||
|
TokenBuilder: () => new ArchitectureTokenBuilder(),
|
||||||
|
ValueConverter: () => new ArchitectureValueConverter(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the full set of services required by Langium.
|
||||||
|
*
|
||||||
|
* First inject the shared services by merging two modules:
|
||||||
|
* - Langium default shared services
|
||||||
|
* - Services generated by langium-cli
|
||||||
|
*
|
||||||
|
* Then inject the language-specific services by merging three modules:
|
||||||
|
* - Langium default language-specific services
|
||||||
|
* - Services generated by langium-cli
|
||||||
|
* - Services specified in this file
|
||||||
|
* @param context - Optional module context with the LSP connection
|
||||||
|
* @returns An object wrapping the shared services and the language-specific services
|
||||||
|
*/
|
||||||
|
export function createArchitectureServices(
|
||||||
|
context: DefaultSharedCoreModuleContext = EmptyFileSystem
|
||||||
|
): {
|
||||||
|
shared: LangiumSharedCoreServices;
|
||||||
|
Architecture: ArchitectureServices;
|
||||||
|
} {
|
||||||
|
const shared: LangiumSharedCoreServices = inject(
|
||||||
|
createDefaultSharedCoreModule(context),
|
||||||
|
MermaidGeneratedSharedModule
|
||||||
|
);
|
||||||
|
const Architecture: ArchitectureServices = inject(
|
||||||
|
createDefaultCoreModule({ shared }),
|
||||||
|
ArchitectureGeneratedModule,
|
||||||
|
ArchitectureModule
|
||||||
|
);
|
||||||
|
shared.ServiceRegistry.register(Architecture);
|
||||||
|
return { shared, Architecture };
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||||
|
|
||||||
|
export class ArchitectureTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||||
|
public constructor() {
|
||||||
|
super(['architecture']);
|
||||||
|
}
|
||||||
|
}
|
20
packages/parser/src/language/architecture/valueConverter.ts
Normal file
20
packages/parser/src/language/architecture/valueConverter.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||||
|
|
||||||
|
import { AbstractMermaidValueConverter } from '../common/index.js';
|
||||||
|
|
||||||
|
export class ArchitectureValueConverter extends AbstractMermaidValueConverter {
|
||||||
|
protected runCustomConverter(
|
||||||
|
rule: GrammarAST.AbstractRule,
|
||||||
|
input: string,
|
||||||
|
_cstNode: CstNode
|
||||||
|
): ValueType | undefined {
|
||||||
|
if (rule.name === 'ARCH_ICON') {
|
||||||
|
return input.replace(/[()]/g, '').trim();
|
||||||
|
} else if (rule.name === 'ARCH_TEXT_ICON') {
|
||||||
|
return input.replace(/["()]/g, '');
|
||||||
|
} else if (rule.name === 'ARCH_TITLE') {
|
||||||
|
return input.replace(/[[\]]/g, '').trim();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ export {
|
|||||||
PacketBlock,
|
PacketBlock,
|
||||||
Pie,
|
Pie,
|
||||||
PieSection,
|
PieSection,
|
||||||
|
Architecture,
|
||||||
GitGraph,
|
GitGraph,
|
||||||
Branch,
|
Branch,
|
||||||
Commit,
|
Commit,
|
||||||
@ -16,16 +17,19 @@ export {
|
|||||||
isPacketBlock,
|
isPacketBlock,
|
||||||
isPie,
|
isPie,
|
||||||
isPieSection,
|
isPieSection,
|
||||||
|
isArchitecture,
|
||||||
isGitGraph,
|
isGitGraph,
|
||||||
isBranch,
|
isBranch,
|
||||||
isCommit,
|
isCommit,
|
||||||
isMerge,
|
isMerge,
|
||||||
} from './generated/ast.js';
|
} from './generated/ast.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
InfoGeneratedModule,
|
InfoGeneratedModule,
|
||||||
MermaidGeneratedSharedModule,
|
MermaidGeneratedSharedModule,
|
||||||
PacketGeneratedModule,
|
PacketGeneratedModule,
|
||||||
PieGeneratedModule,
|
PieGeneratedModule,
|
||||||
|
ArchitectureGeneratedModule,
|
||||||
GitGraphGeneratedModule,
|
GitGraphGeneratedModule,
|
||||||
} from './generated/module.js';
|
} from './generated/module.js';
|
||||||
|
|
||||||
@ -34,3 +38,4 @@ export * from './common/index.js';
|
|||||||
export * from './info/index.js';
|
export * from './info/index.js';
|
||||||
export * from './packet/index.js';
|
export * from './packet/index.js';
|
||||||
export * from './pie/index.js';
|
export * from './pie/index.js';
|
||||||
|
export * from './architecture/index.js';
|
||||||
|
@ -6,7 +6,6 @@ export class PieValueConverter extends AbstractMermaidValueConverter {
|
|||||||
protected runCustomConverter(
|
protected runCustomConverter(
|
||||||
rule: GrammarAST.AbstractRule,
|
rule: GrammarAST.AbstractRule,
|
||||||
input: string,
|
input: string,
|
||||||
|
|
||||||
_cstNode: CstNode
|
_cstNode: CstNode
|
||||||
): ValueType | undefined {
|
): ValueType | undefined {
|
||||||
if (rule.name !== 'PIE_SECTION_LABEL') {
|
if (rule.name !== 'PIE_SECTION_LABEL') {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type { LangiumParser, ParseResult } from 'langium';
|
import type { LangiumParser, ParseResult } from 'langium';
|
||||||
|
|
||||||
import type { Info, Packet, Pie, GitGraph } from './index.js';
|
import type { Info, Packet, Pie, Architecture, GitGraph } from './index.js';
|
||||||
|
|
||||||
export type DiagramAST = Info | Packet | Pie | GitGraph;
|
export type DiagramAST = Info | Packet | Pie | Architecture | GitGraph;
|
||||||
|
|
||||||
const parsers: Record<string, LangiumParser> = {};
|
const parsers: Record<string, LangiumParser> = {};
|
||||||
const initializers = {
|
const initializers = {
|
||||||
@ -21,6 +21,11 @@ const initializers = {
|
|||||||
const parser = createPieServices().Pie.parser.LangiumParser;
|
const parser = createPieServices().Pie.parser.LangiumParser;
|
||||||
parsers.pie = parser;
|
parsers.pie = parser;
|
||||||
},
|
},
|
||||||
|
architecture: async () => {
|
||||||
|
const { createArchitectureServices } = await import('./language/architecture/index.js');
|
||||||
|
const parser = createArchitectureServices().Architecture.parser.LangiumParser;
|
||||||
|
parsers.architecture = parser;
|
||||||
|
},
|
||||||
gitGraph: async () => {
|
gitGraph: async () => {
|
||||||
const { createGitGraphServices } = await import('./language/gitGraph/index.js');
|
const { createGitGraphServices } = await import('./language/gitGraph/index.js');
|
||||||
const parser = createGitGraphServices().GitGraph.parser.LangiumParser;
|
const parser = createGitGraphServices().GitGraph.parser.LangiumParser;
|
||||||
@ -31,6 +36,7 @@ const initializers = {
|
|||||||
export async function parse(diagramType: 'info', text: string): Promise<Info>;
|
export async function parse(diagramType: 'info', text: string): Promise<Info>;
|
||||||
export async function parse(diagramType: 'packet', text: string): Promise<Packet>;
|
export async function parse(diagramType: 'packet', text: string): Promise<Packet>;
|
||||||
export async function parse(diagramType: 'pie', text: string): Promise<Pie>;
|
export async function parse(diagramType: 'pie', text: string): Promise<Pie>;
|
||||||
|
export async function parse(diagramType: 'architecture', text: string): Promise<Architecture>;
|
||||||
export async function parse(diagramType: 'gitGraph', text: string): Promise<GitGraph>;
|
export async function parse(diagramType: 'gitGraph', text: string): Promise<GitGraph>;
|
||||||
|
|
||||||
export async function parse<T extends DiagramAST>(
|
export async function parse<T extends DiagramAST>(
|
||||||
|
2697
pnpm-lock.yaml
generated
2697
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user