Compare commits

..

57 Commits

Author SHA1 Message Date
Sidharth Vinod
0518e9cfd7 Merge pull request #5984 from mermaid-js/renovate/eslint
chore(deps): update eslint (minor)
2025-02-21 10:26:12 +00:00
autofix-ci[bot]
f43e35039f [autofix.ci] apply automated fixes 2025-02-20 18:31:20 +00:00
renovate[bot]
dbf885b077 chore(deps): update eslint 2025-02-20 18:25:55 +00:00
Sidharth Vinod
ad2f17205a Merge pull request #6272 from mermaid-js/saurabh/refactor/convert-sequenceDb-to-class
Refactor: Change SequenceDB to class based architecture.
2025-02-20 07:20:14 +00:00
Sidharth Vinod
98fe7a6e03 Merge pull request #5997 from mermaid-js/renovate/major-eslint
chore(deps): update eslint (major)
2025-02-19 12:48:14 +05:30
saurabhg772244
5857953d51 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-19 11:26:22 +05:30
Sidharth Vinod
34a12103d0 Merge branch 'renovate/major-eslint' of https://github.com/mermaid-js/mermaid into renovate/major-eslint
* 'renovate/major-eslint' of https://github.com/mermaid-js/mermaid:
  chore(deps): update eslint
2025-02-19 00:34:03 +05:30
Sidharth Vinod
46b8a75199 chore: Remove deprecated rule 2025-02-19 00:33:56 +05:30
renovate[bot]
1dca45a76d chore(deps): update eslint 2025-02-18 19:01:39 +00:00
Sidharth Vinod
d1d5bd14f6 Merge branch 'develop' into renovate/major-eslint
* develop:
  chore: Add tslib
  [autofix.ci] apply automated fixes
  fix(deps): update all patch dependencies
2025-02-19 00:28:32 +05:30
Sidharth Vinod
974867a089 Merge pull request #5961 from mermaid-js/renovate/patch-all-patch
fix(deps): update all patch dependencies (patch)
2025-02-19 00:24:41 +05:30
Sidharth Vinod
52532a7801 Merge pull request #6295 from mermaid-js/omkar/fix-state-diagram-default-direction
Fix: state diagram default direction update
2025-02-18 18:53:57 +00:00
Sidharth Vinod
6eb56b9363 Merge branch 'develop' into renovate/patch-all-patch
* develop: (97 commits)
  chore: Update actions/cache
  chore: Update actions/cache
  fix: CodeQL double escape warning
  chore(deps): update eslint
  Added changeset.
  refactor: handle StateDB `.extract()` properly
  chore(deps): update dependency esbuild to ^0.25.0 [security]
  Update .changeset/grumpy-cheetahs-deliver.md
  chore: Use RUN_VISUAL_TEST in cypress config
  chore: Use argos tokenless auth
  chore: Add tokenless auth to argos
  fix(deps): update dependency dompurify to v3.2.4 [security]
  chore: Update argos token
  Ignore huehive
  chore: Run argos on all PRs, except renovate bot's
  chore: Fix exclude
  [autofix.ci] apply automated fixes
  chore(deps): update dependency vitest to v1.6.1 [security]
  docs: Cleanup minor issues
  Add files via upload
  ...
2025-02-19 00:08:50 +05:30
Sidharth Vinod
f7c91a72cb chore: Add tslib 2025-02-19 00:06:45 +05:30
renovate[bot]
c53e044559 chore(deps): update eslint 2025-02-18 18:26:30 +00:00
Sidharth Vinod
d846a8c65a chore: Minor refactor 2025-02-18 23:48:19 +05:30
Sidharth Vinod
c3ba23a4e9 Merge branch 'develop' into omkar/fix-state-diagram-default-direction 2025-02-18 23:44:19 +05:30
Sidharth Vinod
c688c387a0 Merge pull request #6298 from mermaid-js/saurabh/fix/sanitizeText-codeql-warning
fix: CodeQL double escape warning
2025-02-18 23:41:31 +05:30
Sidharth Vinod
300fb85a73 chore: Update actions/cache 2025-02-18 23:41:01 +05:30
Sidharth Vinod
9c6e554153 chore: Update actions/cache 2025-02-18 23:40:53 +05:30
Sidharth Vinod
b5c78b7225 Merge pull request #5908 from mermaid-js/renovate/patch-eslint
chore(deps): update eslint (patch)
2025-02-18 21:42:23 +05:30
omkarht
da6361f652 added changeset 2025-02-18 18:36:52 +05:30
omkarht
167fef5cff added test cases to check state diagram direction 2025-02-18 17:23:48 +05:30
saurabhg772244
3233c63fc0 Updated variable name 2025-02-18 16:59:35 +05:30
saurabhg772244
1182aaaf4d fix: CodeQL double escape warning 2025-02-18 15:42:25 +05:30
omkarht
b78c061a8f refatored code 2025-02-18 15:40:45 +05:30
renovate[bot]
ebfcf3ea07 chore(deps): update eslint 2025-02-17 15:45:31 +00:00
Sidharth Vinod
62edea6b33 Merge pull request #6293 from mermaid-js/saurabh/refactor/handle-StateDB-.extract-properly
Handle StateDB `.extract()` properly
2025-02-17 21:13:26 +05:30
omkarht
72eda9ce52 Fix: state diagram default direction update 2025-02-17 20:53:31 +05:30
saurabhg772244
cfd84e54d5 Added changeset. 2025-02-17 16:14:55 +05:30
Alois Klink
cfe710f42b refactor: handle StateDB .extract() properly
Fix the StateDB properly for stateDiagram-v1 vs stateDiagram-v2.
2025-02-17 15:54:19 +05:30
saurabhg772244
fe6f05eec9 Merge branch 'develop' of github.com-mermaid:mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-15 22:56:53 +05:30
Sidharth Vinod
548256507d Merge pull request #6269 from mermaid-js/renovate/npm-esbuild-vulnerability
chore(deps): update dependency esbuild to ^0.25.0 [security]
2025-02-15 16:51:26 +00:00
Sidharth Vinod
d8ebf7a136 Merge pull request #6250 from mermaid-js/saurabh/refactor/convert-stateDb-to-class-v2
Refactor: V2. Change StateDB to class based architecture.
2025-02-15 16:50:55 +00:00
renovate[bot]
a758b24b75 chore(deps): update dependency esbuild to ^0.25.0 [security] 2025-02-15 16:16:28 +00:00
Saurabh Gore
88abf46fc3 Update .changeset/grumpy-cheetahs-deliver.md
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2025-02-15 21:33:58 +05:30
Sidharth Vinod
2eeba0e999 Merge branch 'develop' into saurabh/refactor/convert-stateDb-to-class-v2 2025-02-15 21:31:14 +05:30
Sidharth Vinod
397684fe49 Merge pull request #6284 from mermaid-js/renovate/npm-dompurify-vulnerability
fix(deps): update dependency dompurify to v3.2.4 [security]
2025-02-15 16:00:56 +00:00
Sidharth Vinod
bc1f769f13 Merge pull request #6287 from sidharthv96/sidv/tokenlessArgos
chore: Add tokenless auth to argos
2025-02-15 21:30:39 +05:30
Sidharth Vinod
f6592235a2 chore: Use RUN_VISUAL_TEST in cypress config 2025-02-15 21:05:47 +05:30
Sidharth Vinod
57805dafa4 chore: Use argos tokenless auth 2025-02-15 20:51:56 +05:30
renovate[bot]
7a7e4b4e6f fix(deps): update dependency dompurify to v3.2.4 [security] 2025-02-15 14:39:21 +00:00
Sidharth Vinod
62f9261c4e Merge branch 'develop' into saurabh/refactor/convert-stateDb-to-class-v2 2025-02-14 21:21:00 +05:30
saurabhg772244
451c886f50 Code refactor. 2025-02-14 19:17:09 +05:30
saurabhg772244
0a1a112605 Move setWrap below clear in constructor. 2025-02-14 17:51:17 +05:30
saurabhg772244
6ca8f46d37 Updated unit tests. 2025-02-12 16:15:06 +05:30
saurabhg772244
b2dfbb6ffd Merge branch 'develop' of https://github.com/mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-12 16:04:54 +05:30
saurabhg772244
ffa7804af0 added changeset 2025-02-12 16:04:22 +05:30
saurabhg772244
f454865b97 Updated unit tests to reflect new DB instance per parse. 2025-02-12 16:00:14 +05:30
saurabhg772244
d83b1a5dbc Refactored arrow function to regular function and assigned unique ID to messages during parsing. 2025-02-12 15:54:51 +05:30
saurabhg772244
4dbaa2b5d6 code refactor 2025-02-04 19:18:46 +05:30
saurabhg772244
9cad3c7aea added changeset 2025-02-04 17:09:09 +05:30
saurabhg772244
56d66cdabc Fix issue where data was not being set in the db after parsing. 2025-02-04 16:45:45 +05:30
saurabhg772244
a0e5408850 convert stateDb to class, added test case. 2025-02-04 16:41:54 +05:30
saurabhg772244
5d3d1047a4 convert sequenceDb to class. 2025-01-15 15:37:33 +05:30
autofix-ci[bot]
59352ad4d8 [autofix.ci] apply automated fixes 2025-01-11 16:39:40 +00:00
renovate[bot]
ba70f15de5 fix(deps): update all patch dependencies 2025-01-11 16:32:14 +00:00
36 changed files with 3205 additions and 2333 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
`mermaidAPI.getDiagramFromText()` now returns a new db instance on each call for state diagrams

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
Added versioning to StateDB and updated tests and diagrams to use it.

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `mermaidAPI.getDiagramFromText()` now returns a new different db for each sequence diagram. Added unique IDs for messages.

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `getDirection` and `setDirection` in `stateDb` refactored to return and set actual direction

View File

@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
with: with:
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
@@ -48,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@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
# 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
@@ -62,4 +62,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13

View File

@@ -17,4 +17,4 @@ jobs:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5

View File

@@ -26,11 +26,11 @@ jobs:
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
- name: Install dependencies - name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with: with:
runTests: false runTests: false
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
id: cypress id: cypress
with: with:
install: false install: false

View File

@@ -45,9 +45,8 @@ jobs:
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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
save-always: true
path: ./cypress/snapshots path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }} key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
@@ -60,7 +59,7 @@ jobs:
- 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@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with: with:
# just perform install # just perform install
runTests: false runTests: false
@@ -96,13 +95,13 @@ jobs:
# 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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
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@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with: with:
runTests: false runTests: false
@@ -118,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@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
id: cypress id: cypress
with: with:
install: false install: false
@@ -129,14 +128,9 @@ jobs:
# e.g. if this action was run from a fork # e.g. if this action was run from a fork
record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }} record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }}
env: env:
# Only set Argos environment variables if the visual test comment trigger is present
ARGOS_TOKEN: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.ARGOS_TOKEN || '' }}
ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }} ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }}
ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }} ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }}
ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }} ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_JOB: ${{ github.job }}
GITHUB_RUN_ID: ${{ github.run_id }}
CYPRESS_COMMIT: ${{ github.sha }} CYPRESS_COMMIT: ${{ github.sha }}
CYPRESS_RECORD_KEY: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY || ''}} CYPRESS_RECORD_KEY: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY || ''}}
SPLIT: ${{ strategy.job-total }} SPLIT: ${{ strategy.job-total }}

View File

@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Restore lychee cache - name: Restore lychee cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: .lycheecache path: .lycheecache
key: cache-lychee-${{ github.sha }} key: cache-lychee-${{ github.sha }}

View File

@@ -36,7 +36,7 @@ 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@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8 uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
with: with:
version: pnpm changeset:version version: pnpm changeset:version
publish: pnpm changeset:publish publish: pnpm changeset:publish

View File

@@ -32,6 +32,6 @@ jobs:
path: results.sarif path: results.sarif
retention-days: 5 retention-days: 5
- name: Upload to code-scanning - name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -5,14 +5,6 @@ import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import cypressSplit from 'cypress-split'; import cypressSplit from 'cypress-split';
const encodeArgosToken = (options: {
owner: string;
repository: string;
jobId: string;
runId: string;
}) => `tokenless-github-${Buffer.from(JSON.stringify(options), 'utf8').toString('base64')}`;
// cspell:ignore tokenless
export default eyesPlugin( export default eyesPlugin(
defineConfig({ defineConfig({
projectId: 'n2sma2', projectId: 'n2sma2',
@@ -31,26 +23,10 @@ export default eyesPlugin(
}); });
// copy any needed variables from process.env to config.env // copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false; config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.CI && !!process.env.ARGOS_TOKEN; config.env.useArgos = process.env.RUN_VISUAL_TEST === 'true';
if (config.env.useArgos) { if (config.env.useArgos) {
if (!process.env.GITHUB_REPOSITORY) { registerArgosTask(on, config);
throw new Error('GITHUB_REPOSITORY is not set');
}
if (!process.env.GITHUB_JOB) {
throw new Error('GITHUB_JOB is not set');
}
if (!process.env.GITHUB_RUN_ID) {
throw new Error('GITHUB_RUN_ID is not set');
}
registerArgosTask(on, config, {
token: encodeArgosToken({
owner: process.env.GITHUB_REPOSITORY.split('/')[0],
repository: process.env.GITHUB_REPOSITORY.split('/')[1],
jobId: process.env.GITHUB_JOB,
runId: process.env.GITHUB_RUN_ID,
}),
});
} else { } else {
addMatchImageSnapshotPlugin(on, config); addMatchImageSnapshotPlugin(on, config);
} }

View File

@@ -11,7 +11,7 @@
rel="stylesheet" rel="stylesheet"
/> />
<link <link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
rel="stylesheet" rel="stylesheet"
/> />
<link <link

View File

@@ -137,7 +137,6 @@ export default tseslint.config(
'unicorn/no-instanceof-array': 'error', 'unicorn/no-instanceof-array': 'error',
'unicorn/no-typeof-undefined': 'error', 'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-await': 'error', 'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unsafe-regex': 'warn',
'unicorn/no-useless-promise-resolve-reject': 'error', 'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/prefer-array-find': 'error', 'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error', 'unicorn/prefer-array-flat-map': 'error',

View File

@@ -92,10 +92,10 @@
"cypress": "^13.14.1", "cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.0", "cypress-split": "^1.24.0",
"esbuild": "^0.21.5", "esbuild": "^0.25.0",
"eslint": "^9.4.0", "eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^10.0.0",
"eslint-plugin-cypress": "^3.3.0", "eslint-plugin-cypress": "^4.0.0",
"eslint-plugin-html": "^8.1.1", "eslint-plugin-html": "^8.1.1",
"eslint-plugin-jest": "^28.6.0", "eslint-plugin-jest": "^28.6.0",
"eslint-plugin-jsdoc": "^50.0.0", "eslint-plugin-jsdoc": "^50.0.0",
@@ -103,8 +103,8 @@
"eslint-plugin-lodash": "^8.0.0", "eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-markdown": "^5.0.0", "eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-tsdoc": "^0.3.0", "eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^56.0.0", "eslint-plugin-unicorn": "^57.0.0",
"express": "^4.19.1", "express": "^4.19.1",
"globals": "^15.4.0", "globals": "^15.4.0",
"globby": "^14.0.1", "globby": "^14.0.1",
@@ -123,6 +123,7 @@
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"start-server-and-test": "^2.0.3", "start-server-and-test": "^2.0.3",
"tslib": "^2.8.1",
"tsx": "^4.7.1", "tsx": "^4.7.1",
"typescript": "~5.4.5", "typescript": "~5.4.5",
"typescript-eslint": "^8.0.0-alpha.34", "typescript-eslint": "^8.0.0-alpha.34",

View File

@@ -230,7 +230,7 @@ const ConfigWarning = {
} as const; } as const;
type ConfigWarningStrings = keyof typeof ConfigWarning; type ConfigWarningStrings = keyof typeof ConfigWarning;
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {}; const issuedWarnings: Partial<Record<ConfigWarningStrings, boolean>> = {};
const issueWarning = (warning: ConfigWarningStrings) => { const issueWarning = (warning: ConfigWarningStrings) => {
if (issuedWarnings[warning]) { if (issuedWarnings[warning]) {
return; return;

View File

@@ -106,9 +106,7 @@ export const isValidArchitectureDirectionPair = function (
return x !== 'LL' && x !== 'RR' && x !== 'TT' && x !== 'BB'; return x !== 'LL' && x !== 'RR' && x !== 'TT' && x !== 'BB';
}; };
export type ArchitectureDirectionPairMap = { export type ArchitectureDirectionPairMap = Partial<Record<ArchitectureDirectionPair, string>>;
[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. * 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.

View File

@@ -1,4 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js'; import { ImperativeState } from '../../utils/imperativeState.js';
import { sanitizeText } from '../common/common.js'; import { sanitizeText } from '../common/common.js';
@@ -28,273 +29,7 @@ interface SequenceState {
lastDestroyed?: Actor; lastDestroyed?: Actor;
} }
const state = new ImperativeState<SequenceState>(() => ({ const LINETYPE = {
prevActor: undefined,
actors: new Map(),
createdActors: new Map(),
destroyedActors: new Map(),
boxes: [],
messages: [],
notes: [],
sequenceNumbersEnabled: false,
wrapEnabled: undefined,
currentBox: undefined,
lastCreated: undefined,
lastDestroyed: undefined,
}));
export const addBox = function (data: { text: string; color: string; wrap: boolean }) {
state.records.boxes.push({
name: data.text,
wrap: data.wrap ?? autoWrap(),
fill: data.color,
actorKeys: [],
});
state.records.currentBox = state.records.boxes.slice(-1)[0];
};
export const addActor = function (
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
type: string
) {
let assignedBox = state.records.currentBox;
const old = state.records.actors.get(id);
if (old) {
// If already set and trying to set to a new one throw error
if (state.records.currentBox && old.box && state.records.currentBox !== old.box) {
throw new Error(
`A same participant should only be defined in one Box: ${old.name} can't be in '${old.box.name}' and in '${state.records.currentBox.name}' at the same time.`
);
}
// Don't change the box if already
assignedBox = old.box ? old.box : state.records.currentBox;
old.box = assignedBox;
// Don't allow description nulling
if (old && name === old.name && description == null) {
return;
}
}
// Don't allow null descriptions, either
if (description?.text == null) {
description = { text: name, type };
}
if (type == null || description.text == null) {
description = { text: name, type };
}
state.records.actors.set(id, {
box: assignedBox,
name: name,
description: description.text,
wrap: description.wrap ?? autoWrap(),
prevActor: state.records.prevActor,
links: {},
properties: {},
actorCnt: null,
rectData: null,
type: type ?? 'participant',
});
if (state.records.prevActor) {
const prevActorInRecords = state.records.actors.get(state.records.prevActor);
if (prevActorInRecords) {
prevActorInRecords.nextActor = id;
}
}
if (state.records.currentBox) {
state.records.currentBox.actorKeys.push(id);
}
state.records.prevActor = id;
};
const activationCount = (part: string) => {
let i;
let count = 0;
if (!part) {
return 0;
}
for (i = 0; i < state.records.messages.length; i++) {
if (
state.records.messages[i].type === LINETYPE.ACTIVE_START &&
state.records.messages[i].from === part
) {
count++;
}
if (
state.records.messages[i].type === LINETYPE.ACTIVE_END &&
state.records.messages[i].from === part
) {
count--;
}
}
return count;
};
export const addMessage = function (
idFrom: Message['from'],
idTo: Message['to'],
message: { text: string; wrap?: boolean },
answer: Message['answer']
) {
state.records.messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: message.wrap ?? autoWrap(),
answer: answer,
});
};
export const addSignal = function (
idFrom?: Message['from'],
idTo?: Message['to'],
message?: { text: string; wrap: boolean },
messageType?: number,
activate = false
) {
if (messageType === LINETYPE.ACTIVE_END) {
const cnt = activationCount(idFrom ?? '');
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
const error = new Error('Trying to inactivate an inactive participant (' + idFrom + ')');
// @ts-ignore: we are passing hash param to the error object, however we should define our own custom error class to make it type safe
error.hash = {
text: '->>-',
token: '->>-',
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ["'ACTIVE_PARTICIPANT'"],
};
throw error;
}
}
state.records.messages.push({
from: idFrom,
to: idTo,
message: message?.text ?? '',
wrap: message?.wrap ?? autoWrap(),
type: messageType,
activate,
});
return true;
};
export const hasAtLeastOneBox = function () {
return state.records.boxes.length > 0;
};
export const hasAtLeastOneBoxWithTitle = function () {
return state.records.boxes.some((b) => b.name);
};
export const getMessages = function () {
return state.records.messages;
};
export const getBoxes = function () {
return state.records.boxes;
};
export const getActors = function () {
return state.records.actors;
};
export const getCreatedActors = function () {
return state.records.createdActors;
};
export const getDestroyedActors = function () {
return state.records.destroyedActors;
};
export const getActor = function (id: string) {
// TODO: do we ever use this function in a way that it might return undefined?
return state.records.actors.get(id)!;
};
export const getActorKeys = function () {
return [...state.records.actors.keys()];
};
export const enableSequenceNumbers = function () {
state.records.sequenceNumbersEnabled = true;
};
export const disableSequenceNumbers = function () {
state.records.sequenceNumbersEnabled = false;
};
export const showSequenceNumbers = () => state.records.sequenceNumbersEnabled;
export const setWrap = function (wrapSetting?: boolean) {
state.records.wrapEnabled = wrapSetting;
};
const extractWrap = (text?: string): { cleanedText?: string; wrap?: boolean } => {
if (text === undefined) {
return {};
}
text = text.trim();
const wrap =
/^:?wrap:/.exec(text) !== null ? true : /^:?nowrap:/.exec(text) !== null ? false : undefined;
const cleanedText = (wrap === undefined ? text : text.replace(/^:?(?:no)?wrap:/, '')).trim();
return { cleanedText, wrap };
};
export const autoWrap = () => {
// if setWrap has been called, use that value, otherwise use the value from the config
// TODO: refactor, always use the config value let setWrap update the config value
if (state.records.wrapEnabled !== undefined) {
return state.records.wrapEnabled;
}
return getConfig().sequence?.wrap ?? false;
};
export const clear = function () {
state.reset();
commonClear();
};
export const parseMessage = function (str: string) {
const trimmedStr = str.trim();
const { wrap, cleanedText } = extractWrap(trimmedStr);
const message = {
text: cleanedText,
wrap,
};
log.debug(`parseMessage: ${JSON.stringify(message)}`);
return message;
};
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
export const parseBoxData = function (str: string) {
const match = /^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/.exec(str);
let color = match?.[1] ? match[1].trim() : 'transparent';
let title = match?.[2] ? match[2].trim() : undefined;
// check that the string is a color
if (window?.CSS) {
if (!window.CSS.supports('color', color)) {
color = 'transparent';
title = str.trim();
}
} else {
const style = new Option().style;
style.color = color;
if (style.color !== color) {
color = 'transparent';
title = str.trim();
}
}
const { wrap, cleanedText } = extractWrap(title);
return {
text: cleanedText ? sanitizeText(cleanedText, getConfig()) : undefined,
color,
wrap,
};
};
export const LINETYPE = {
SOLID: 0, SOLID: 0,
DOTTED: 1, DOTTED: 1,
NOTE: 2, NOTE: 2,
@@ -327,20 +62,309 @@ export const LINETYPE = {
PAR_OVER_START: 32, PAR_OVER_START: 32,
BIDIRECTIONAL_SOLID: 33, BIDIRECTIONAL_SOLID: 33,
BIDIRECTIONAL_DOTTED: 34, BIDIRECTIONAL_DOTTED: 34,
}; } as const;
export const ARROWTYPE = { const ARROWTYPE = {
FILLED: 0, FILLED: 0,
OPEN: 1, OPEN: 1,
}; } as const;
export const PLACEMENT = { const PLACEMENT = {
LEFTOF: 0, LEFTOF: 0,
RIGHTOF: 1, RIGHTOF: 1,
OVER: 2, OVER: 2,
}; } as const;
export const addNote = function ( export class SequenceDB implements DiagramDB {
private readonly state = new ImperativeState<SequenceState>(() => ({
prevActor: undefined,
actors: new Map(),
createdActors: new Map(),
destroyedActors: new Map(),
boxes: [],
messages: [],
notes: [],
sequenceNumbersEnabled: false,
wrapEnabled: undefined,
currentBox: undefined,
lastCreated: undefined,
lastDestroyed: undefined,
}));
constructor() {
// Needed for JISON since it only supports direct properties
this.apply = this.apply.bind(this);
this.parseBoxData = this.parseBoxData.bind(this);
this.parseMessage = this.parseMessage.bind(this);
this.clear();
this.setWrap(getConfig().wrap);
this.LINETYPE = LINETYPE;
this.ARROWTYPE = ARROWTYPE;
this.PLACEMENT = PLACEMENT;
}
public addBox(data: { text: string; color: string; wrap: boolean }) {
this.state.records.boxes.push({
name: data.text,
wrap: data.wrap ?? this.autoWrap(),
fill: data.color,
actorKeys: [],
});
this.state.records.currentBox = this.state.records.boxes.slice(-1)[0];
}
public addActor(
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
type: string
) {
let assignedBox = this.state.records.currentBox;
const old = this.state.records.actors.get(id);
if (old) {
// If already set and trying to set to a new one throw error
if (this.state.records.currentBox && old.box && this.state.records.currentBox !== old.box) {
throw new Error(
`A same participant should only be defined in one Box: ${old.name} can't be in '${old.box.name}' and in '${this.state.records.currentBox.name}' at the same time.`
);
}
// Don't change the box if already
assignedBox = old.box ? old.box : this.state.records.currentBox;
old.box = assignedBox;
// Don't allow description nulling
if (old && name === old.name && description == null) {
return;
}
}
// Don't allow null descriptions, either
if (description?.text == null) {
description = { text: name, type };
}
if (type == null || description.text == null) {
description = { text: name, type };
}
this.state.records.actors.set(id, {
box: assignedBox,
name: name,
description: description.text,
wrap: description.wrap ?? this.autoWrap(),
prevActor: this.state.records.prevActor,
links: {},
properties: {},
actorCnt: null,
rectData: null,
type: type ?? 'participant',
});
if (this.state.records.prevActor) {
const prevActorInRecords = this.state.records.actors.get(this.state.records.prevActor);
if (prevActorInRecords) {
prevActorInRecords.nextActor = id;
}
}
if (this.state.records.currentBox) {
this.state.records.currentBox.actorKeys.push(id);
}
this.state.records.prevActor = id;
}
private activationCount(part: string) {
let i;
let count = 0;
if (!part) {
return 0;
}
for (i = 0; i < this.state.records.messages.length; i++) {
if (
this.state.records.messages[i].type === this.LINETYPE.ACTIVE_START &&
this.state.records.messages[i].from === part
) {
count++;
}
if (
this.state.records.messages[i].type === this.LINETYPE.ACTIVE_END &&
this.state.records.messages[i].from === part
) {
count--;
}
}
return count;
}
public addMessage(
idFrom: Message['from'],
idTo: Message['to'],
message: { text: string; wrap?: boolean },
answer: Message['answer']
) {
this.state.records.messages.push({
id: this.state.records.messages.length.toString(),
from: idFrom,
to: idTo,
message: message.text,
wrap: message.wrap ?? this.autoWrap(),
answer: answer,
});
}
public addSignal(
idFrom?: Message['from'],
idTo?: Message['to'],
message?: { text: string; wrap: boolean },
messageType?: number,
activate = false
) {
if (messageType === this.LINETYPE.ACTIVE_END) {
const cnt = this.activationCount(idFrom ?? '');
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
const error = new Error('Trying to inactivate an inactive participant (' + idFrom + ')');
// @ts-ignore: we are passing hash param to the error object, however we should define our own custom error class to make it type safe
error.hash = {
text: '->>-',
token: '->>-',
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ["'ACTIVE_PARTICIPANT'"],
};
throw error;
}
}
this.state.records.messages.push({
id: this.state.records.messages.length.toString(),
from: idFrom,
to: idTo,
message: message?.text ?? '',
wrap: message?.wrap ?? this.autoWrap(),
type: messageType,
activate,
});
return true;
}
public hasAtLeastOneBox() {
return this.state.records.boxes.length > 0;
}
public hasAtLeastOneBoxWithTitle() {
return this.state.records.boxes.some((b) => b.name);
}
public getMessages() {
return this.state.records.messages;
}
public getBoxes() {
return this.state.records.boxes;
}
public getActors() {
return this.state.records.actors;
}
public getCreatedActors() {
return this.state.records.createdActors;
}
public getDestroyedActors() {
return this.state.records.destroyedActors;
}
public getActor(id: string) {
// TODO: do we ever use this function in a way that it might return undefined?
return this.state.records.actors.get(id)!;
}
public getActorKeys() {
return [...this.state.records.actors.keys()];
}
public enableSequenceNumbers() {
this.state.records.sequenceNumbersEnabled = true;
}
public disableSequenceNumbers() {
this.state.records.sequenceNumbersEnabled = false;
}
public showSequenceNumbers() {
return this.state.records.sequenceNumbersEnabled;
}
public setWrap(wrapSetting?: boolean) {
this.state.records.wrapEnabled = wrapSetting;
}
private extractWrap(text?: string): { cleanedText?: string; wrap?: boolean } {
if (text === undefined) {
return {};
}
text = text.trim();
const wrap =
/^:?wrap:/.exec(text) !== null ? true : /^:?nowrap:/.exec(text) !== null ? false : undefined;
const cleanedText = (wrap === undefined ? text : text.replace(/^:?(?:no)?wrap:/, '')).trim();
return { cleanedText, wrap };
}
public autoWrap() {
// if setWrap has been called, use that value, otherwise use the value from the config
// TODO: refactor, always use the config value let setWrap update the config value
if (this.state.records.wrapEnabled !== undefined) {
return this.state.records.wrapEnabled;
}
return getConfig().sequence?.wrap ?? false;
}
public clear() {
this.state.reset();
commonClear();
}
public parseMessage(str: string) {
const trimmedStr = str.trim();
const { wrap, cleanedText } = this.extractWrap(trimmedStr);
const message = {
text: cleanedText,
wrap,
};
log.debug(`parseMessage: ${JSON.stringify(message)}`);
return message;
}
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
public parseBoxData(str: string) {
const match = /^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/.exec(str);
let color = match?.[1] ? match[1].trim() : 'transparent';
let title = match?.[2] ? match[2].trim() : undefined;
// check that the string is a color
if (window?.CSS) {
if (!window.CSS.supports('color', color)) {
color = 'transparent';
title = str.trim();
}
} else {
const style = new Option().style;
style.color = color;
if (style.color !== color) {
color = 'transparent';
title = str.trim();
}
}
const { wrap, cleanedText } = this.extractWrap(title);
return {
text: cleanedText ? sanitizeText(cleanedText, getConfig()) : undefined,
color,
wrap,
};
}
public readonly LINETYPE: typeof LINETYPE;
public readonly ARROWTYPE: typeof ARROWTYPE;
public readonly PLACEMENT: typeof PLACEMENT;
public addNote(
actor: { actor: string }, actor: { actor: string },
placement: Message['placement'], placement: Message['placement'],
message: { text: string; wrap?: boolean } message: { text: string; wrap?: boolean }
@@ -349,64 +373,61 @@ export const addNote = function (
actor: actor, actor: actor,
placement: placement, placement: placement,
message: message.text, message: message.text,
wrap: message.wrap ?? autoWrap(), wrap: message.wrap ?? this.autoWrap(),
}; };
//@ts-ignore: Coerce actor into a [to, from, ...] array //@ts-ignore: Coerce actor into a [to, from, ...] array
// eslint-disable-next-line unicorn/prefer-spread // eslint-disable-next-line unicorn/prefer-spread
const actors = [].concat(actor, actor); const actors = [].concat(actor, actor);
state.records.notes.push(note); this.state.records.notes.push(note);
state.records.messages.push({ this.state.records.messages.push({
id: this.state.records.messages.length.toString(),
from: actors[0], from: actors[0],
to: actors[1], to: actors[1],
message: message.text, message: message.text,
wrap: message.wrap ?? autoWrap(), wrap: message.wrap ?? this.autoWrap(),
type: LINETYPE.NOTE, type: this.LINETYPE.NOTE,
placement: placement, placement: placement,
}); });
}; }
export const addLinks = function (actorId: string, text: { text: string }) { public addLinks(actorId: string, text: { text: string }) {
// find the actor // find the actor
const actor = getActor(actorId); const actor = this.getActor(actorId);
// JSON.parse the text // JSON.parse the text
try { try {
let sanitizedText = sanitizeText(text.text, getConfig()); let sanitizedText = sanitizeText(text.text, getConfig());
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
sanitizedText = sanitizedText.replace(/&equals;/g, '='); sanitizedText = sanitizedText.replace(/&equals;/g, '=');
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
const links = JSON.parse(sanitizedText); const links = JSON.parse(sanitizedText);
// add the deserialized text to the actor's links field. // add the deserialized text to the actor's links field.
insertLinks(actor, links); this.insertLinks(actor, links);
} catch (e) { } catch (e) {
log.error('error while parsing actor link text', e); log.error('error while parsing actor link text', e);
} }
}; }
export const addALink = function (actorId: string, text: { text: string }) { public addALink(actorId: string, text: { text: string }) {
// find the actor // find the actor
const actor = getActor(actorId); const actor = this.getActor(actorId);
try { try {
const links: Record<string, string> = {}; const links: Record<string, string> = {};
let sanitizedText = sanitizeText(text.text, getConfig()); let sanitizedText = sanitizeText(text.text, getConfig());
const sep = sanitizedText.indexOf('@'); const sep = sanitizedText.indexOf('@');
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
sanitizedText = sanitizedText.replace(/&equals;/g, '='); sanitizedText = sanitizedText.replace(/&equals;/g, '=');
sanitizedText = sanitizedText.replace(/&amp;/g, '&');
const label = sanitizedText.slice(0, sep - 1).trim(); const label = sanitizedText.slice(0, sep - 1).trim();
const link = sanitizedText.slice(sep + 1).trim(); const link = sanitizedText.slice(sep + 1).trim();
links[label] = link; links[label] = link;
// add the deserialized text to the actor's links field. // add the deserialized text to the actor's links field.
insertLinks(actor, links); this.insertLinks(actor, links);
} catch (e) { } catch (e) {
log.error('error while parsing actor link text', e); log.error('error while parsing actor link text', e);
} }
}; }
/** private insertLinks(actor: Actor, links: Record<string, string>) {
* @param actor - the actor to add the links to
* @param links - the links to add to the actor
*/
function insertLinks(actor: Actor, links: Record<string, string>) {
if (actor.links == null) { if (actor.links == null) {
actor.links = links; actor.links = links;
} else { } else {
@@ -416,25 +437,21 @@ function insertLinks(actor: Actor, links: Record<string, string>) {
} }
} }
export const addProperties = function (actorId: string, text: { text: string }) { public addProperties(actorId: string, text: { text: string }) {
// find the actor // find the actor
const actor = getActor(actorId); const actor = this.getActor(actorId);
// JSON.parse the text // JSON.parse the text
try { try {
const sanitizedText = sanitizeText(text.text, getConfig()); const sanitizedText = sanitizeText(text.text, getConfig());
const properties: Record<string, unknown> = JSON.parse(sanitizedText); const properties: Record<string, unknown> = JSON.parse(sanitizedText);
// add the deserialized text to the actor's property field. // add the deserialized text to the actor's property field.
insertProperties(actor, properties); this.insertProperties(actor, properties);
} catch (e) { } catch (e) {
log.error('error while parsing actor properties text', e); log.error('error while parsing actor properties text', e);
} }
}; }
/** private insertProperties(actor: Actor, properties: Record<string, unknown>) {
* @param actor - the actor to add the properties to
* @param properties - the properties to add to the actor's properties
*/
function insertProperties(actor: Actor, properties: Record<string, unknown>) {
if (actor.properties == null) { if (actor.properties == null) {
actor.properties = properties; actor.properties = properties;
} else { } else {
@@ -444,13 +461,13 @@ function insertProperties(actor: Actor, properties: Record<string, unknown>) {
} }
} }
function boxEnd() { private boxEnd() {
state.records.currentBox = undefined; this.state.records.currentBox = undefined;
} }
export const addDetails = function (actorId: string, text: { text: string }) { public addDetails(actorId: string, text: { text: string }) {
// find the actor // find the actor
const actor = getActor(actorId); const actor = this.getActor(actorId);
const elem = document.getElementById(text.text)!; const elem = document.getElementById(text.text)!;
// JSON.parse the text // JSON.parse the text
@@ -459,35 +476,36 @@ export const addDetails = function (actorId: string, text: { text: string }) {
const details = JSON.parse(text); const details = JSON.parse(text);
// add the deserialized text to the actor's property field. // add the deserialized text to the actor's property field.
if (details.properties) { if (details.properties) {
insertProperties(actor, details.properties); this.insertProperties(actor, details.properties);
} }
if (details.links) { if (details.links) {
insertLinks(actor, details.links); this.insertLinks(actor, details.links);
} }
} catch (e) { } catch (e) {
log.error('error while parsing actor details text', e); log.error('error while parsing actor details text', e);
} }
}; }
export const getActorProperty = function (actor: Actor, key: string) { public getActorProperty(actor: Actor, key: string) {
if (actor?.properties !== undefined) { if (actor?.properties !== undefined) {
return actor.properties[key]; return actor.properties[key];
} }
return undefined; return undefined;
}; }
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents
export const apply = function (param: any | AddMessageParams | AddMessageParams[]) { public apply(param: any | AddMessageParams | AddMessageParams[]) {
if (Array.isArray(param)) { if (Array.isArray(param)) {
param.forEach(function (item) { param.forEach((item) => {
apply(item); this.apply(item);
}); });
} else { } else {
switch (param.type) { switch (param.type) {
case 'sequenceIndex': case 'sequenceIndex':
state.records.messages.push({ this.state.records.messages.push({
id: this.state.records.messages.length.toString(),
from: undefined, from: undefined,
to: undefined, to: undefined,
message: { message: {
@@ -500,169 +518,141 @@ export const apply = function (param: any | AddMessageParams | AddMessageParams[
}); });
break; break;
case 'addParticipant': case 'addParticipant':
addActor(param.actor, param.actor, param.description, param.draw); this.addActor(param.actor, param.actor, param.description, param.draw);
break; break;
case 'createParticipant': case 'createParticipant':
if (state.records.actors.has(param.actor)) { if (this.state.records.actors.has(param.actor)) {
throw new Error( throw new Error(
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior" "It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
); );
} }
state.records.lastCreated = param.actor; this.state.records.lastCreated = param.actor;
addActor(param.actor, param.actor, param.description, param.draw); this.addActor(param.actor, param.actor, param.description, param.draw);
state.records.createdActors.set(param.actor, state.records.messages.length); this.state.records.createdActors.set(param.actor, this.state.records.messages.length);
break; break;
case 'destroyParticipant': case 'destroyParticipant':
state.records.lastDestroyed = param.actor; this.state.records.lastDestroyed = param.actor;
state.records.destroyedActors.set(param.actor, state.records.messages.length); this.state.records.destroyedActors.set(param.actor, this.state.records.messages.length);
break; break;
case 'activeStart': case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType); this.addSignal(param.actor, undefined, undefined, param.signalType);
break; break;
case 'activeEnd': case 'activeEnd':
addSignal(param.actor, undefined, undefined, param.signalType); this.addSignal(param.actor, undefined, undefined, param.signalType);
break; break;
case 'addNote': case 'addNote':
addNote(param.actor, param.placement, param.text); this.addNote(param.actor, param.placement, param.text);
break; break;
case 'addLinks': case 'addLinks':
addLinks(param.actor, param.text); this.addLinks(param.actor, param.text);
break; break;
case 'addALink': case 'addALink':
addALink(param.actor, param.text); this.addALink(param.actor, param.text);
break; break;
case 'addProperties': case 'addProperties':
addProperties(param.actor, param.text); this.addProperties(param.actor, param.text);
break; break;
case 'addDetails': case 'addDetails':
addDetails(param.actor, param.text); this.addDetails(param.actor, param.text);
break; break;
case 'addMessage': case 'addMessage':
if (state.records.lastCreated) { if (this.state.records.lastCreated) {
if (param.to !== state.records.lastCreated) { if (param.to !== this.state.records.lastCreated) {
throw new Error( throw new Error(
'The created participant ' + 'The created participant ' +
state.records.lastCreated.name + this.state.records.lastCreated.name +
' does not have an associated creating message after its declaration. Please check the sequence diagram.' ' does not have an associated creating message after its declaration. Please check the sequence diagram.'
); );
} else { } else {
state.records.lastCreated = undefined; this.state.records.lastCreated = undefined;
} }
} else if (state.records.lastDestroyed) { } else if (this.state.records.lastDestroyed) {
if ( if (
param.to !== state.records.lastDestroyed && param.to !== this.state.records.lastDestroyed &&
param.from !== state.records.lastDestroyed param.from !== this.state.records.lastDestroyed
) { ) {
throw new Error( throw new Error(
'The destroyed participant ' + 'The destroyed participant ' +
state.records.lastDestroyed.name + this.state.records.lastDestroyed.name +
' does not have an associated destroying message after its declaration. Please check the sequence diagram.' ' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
); );
} else { } else {
state.records.lastDestroyed = undefined; this.state.records.lastDestroyed = undefined;
} }
} }
addSignal(param.from, param.to, param.msg, param.signalType, param.activate); this.addSignal(param.from, param.to, param.msg, param.signalType, param.activate);
break; break;
case 'boxStart': case 'boxStart':
addBox(param.boxData); this.addBox(param.boxData);
break; break;
case 'boxEnd': case 'boxEnd':
boxEnd(); this.boxEnd();
break; break;
case 'loopStart': case 'loopStart':
addSignal(undefined, undefined, param.loopText, param.signalType); this.addSignal(undefined, undefined, param.loopText, param.signalType);
break; break;
case 'loopEnd': case 'loopEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'rectStart': case 'rectStart':
addSignal(undefined, undefined, param.color, param.signalType); this.addSignal(undefined, undefined, param.color, param.signalType);
break; break;
case 'rectEnd': case 'rectEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'optStart': case 'optStart':
addSignal(undefined, undefined, param.optText, param.signalType); this.addSignal(undefined, undefined, param.optText, param.signalType);
break; break;
case 'optEnd': case 'optEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'altStart': case 'altStart':
addSignal(undefined, undefined, param.altText, param.signalType); this.addSignal(undefined, undefined, param.altText, param.signalType);
break; break;
case 'else': case 'else':
addSignal(undefined, undefined, param.altText, param.signalType); this.addSignal(undefined, undefined, param.altText, param.signalType);
break; break;
case 'altEnd': case 'altEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'setAccTitle': case 'setAccTitle':
setAccTitle(param.text); setAccTitle(param.text);
break; break;
case 'parStart': case 'parStart':
addSignal(undefined, undefined, param.parText, param.signalType); this.addSignal(undefined, undefined, param.parText, param.signalType);
break; break;
case 'and': case 'and':
addSignal(undefined, undefined, param.parText, param.signalType); this.addSignal(undefined, undefined, param.parText, param.signalType);
break; break;
case 'parEnd': case 'parEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'criticalStart': case 'criticalStart':
addSignal(undefined, undefined, param.criticalText, param.signalType); this.addSignal(undefined, undefined, param.criticalText, param.signalType);
break; break;
case 'option': case 'option':
addSignal(undefined, undefined, param.optionText, param.signalType); this.addSignal(undefined, undefined, param.optionText, param.signalType);
break; break;
case 'criticalEnd': case 'criticalEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
case 'breakStart': case 'breakStart':
addSignal(undefined, undefined, param.breakText, param.signalType); this.addSignal(undefined, undefined, param.breakText, param.signalType);
break; break;
case 'breakEnd': case 'breakEnd':
addSignal(undefined, undefined, undefined, param.signalType); this.addSignal(undefined, undefined, undefined, param.signalType);
break; break;
} }
} }
}; }
export default { public setAccTitle = setAccTitle;
addActor, public setAccDescription = setAccDescription;
addMessage, public setDiagramTitle = setDiagramTitle;
addSignal, public getAccTitle = getAccTitle;
addLinks, public getAccDescription = getAccDescription;
addDetails, public getDiagramTitle = getDiagramTitle;
addProperties, public getConfig() {
autoWrap, return getConfig().sequence;
setWrap, }
enableSequenceNumbers, }
disableSequenceNumbers,
showSequenceNumbers,
getMessages,
getActors,
getCreatedActors,
getDestroyedActors,
getActor,
getActorKeys,
getActorProperty,
getAccTitle,
getBoxes,
getDiagramTitle,
setDiagramTitle,
getConfig: () => getConfig().sequence,
clear,
parseMessage,
parseBoxData,
LINETYPE,
ARROWTYPE,
PLACEMENT,
addNote,
setAccTitle,
apply,
setAccDescription,
getAccDescription,
hasAtLeastOneBox,
hasAtLeastOneBoxWithTitle,
};

View File

@@ -1,16 +1,26 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/sequenceDiagram.jison'; import parser from './parser/sequenceDiagram.jison';
import db from './sequenceDb.js'; import { SequenceDB } from './sequenceDb.js';
import styles from './styles.js'; import styles from './styles.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
import renderer from './sequenceRenderer.js'; import renderer from './sequenceRenderer.js';
import type { MermaidConfig } from '../../config.type.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new SequenceDB();
},
renderer, renderer,
styles, styles,
init: ({ wrap }) => { init: (cnf: MermaidConfig) => {
db.setWrap(wrap); if (!cnf.sequence) {
cnf.sequence = {};
}
if (cnf.wrap) {
cnf.sequence.wrap = cnf.wrap;
setConfig({ sequence: { wrap: cnf.wrap } });
}
}, },
}; };

View File

@@ -1538,7 +1538,6 @@ const calculateLoopBounds = async function (messages, actors, _maxWidthPerActor,
let current, noteModel, msgModel; let current, noteModel, msgModel;
for (const msg of messages) { for (const msg of messages) {
msg.id = utils.random({ length: 10 });
switch (msg.type) { switch (msg.type) {
case diagObj.db.LINETYPE.LOOP_START: case diagObj.db.LINETYPE.LOOP_START:
case diagObj.db.LINETYPE.ALT_START: case diagObj.db.LINETYPE.ALT_START:

View File

@@ -20,6 +20,7 @@ export interface Actor {
} }
export interface Message { export interface Message {
id: string;
from?: string; from?: string;
to?: string; to?: string;
message: message:

View File

@@ -1,4 +1,4 @@
import stateDb from '../stateDb.js'; import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison'; import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -7,7 +7,9 @@ setConfig({
}); });
describe('state parser can parse...', () => { describe('state parser can parse...', () => {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB(2);
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
}); });
@@ -18,7 +20,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
state "Small State 1" as namedState1`; state "Small State 1" as namedState1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -31,7 +32,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
namedState1 : Small State 1`; namedState1 : Small State 1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -42,7 +42,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
namedState1:Small State 1`; namedState1:Small State 1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -60,7 +59,6 @@ describe('state parser can parse...', () => {
state assemblies state assemblies
`; `;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('assemble')).not.toBeUndefined(); expect(states.get('assemble')).not.toBeUndefined();
expect(states.get('assemblies')).not.toBeUndefined(); expect(states.get('assemblies')).not.toBeUndefined();
@@ -71,7 +69,6 @@ describe('state parser can parse...', () => {
state "as" as as state "as" as as
`; `;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('as')).not.toBeUndefined(); expect(states.get('as')).not.toBeUndefined();
expect(states.get('as').descriptions.join(' ')).toEqual('as'); expect(states.get('as').descriptions.join(' ')).toEqual('as');
@@ -96,7 +93,6 @@ describe('state parser can parse...', () => {
namedState2 --> bigState2: should point to \\nbigState2 container`; namedState2 --> bigState2: should point to \\nbigState2 container`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -120,7 +116,6 @@ describe('state parser can parse...', () => {
inner1 --> inner2 inner1 --> inner2
}`; }`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('bigState1')).not.toBeUndefined(); expect(states.get('bigState1')).not.toBeUndefined();
@@ -137,7 +132,6 @@ describe('state parser can parse...', () => {
stateDiagram-v2 stateDiagram-v2
[*] --> ${prop} [*] --> ${prop}
${prop} --> [*]`); ${prop} --> [*]`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get(prop)).not.toBeUndefined(); expect(states.get(prop)).not.toBeUndefined();
}); });

View File

@@ -1,13 +1,15 @@
import stateDb from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
setConfig({ setConfig({
securityLevel: 'strict', securityLevel: 'strict',
}); });
describe('ClassDefs and classes when parsing a State diagram', () => { describe('ClassDefs and classes when parsing a State diagram', () => {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB(2);
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
}); });
@@ -16,7 +18,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
describe('defining (classDef)', () => { describe('defining (classDef)', () => {
it('has "classDef" as a keyword, an id, and can set a css style attribute', function () { it('has "classDef" as a keyword, an id, and can set a css style attribute', function () {
stateDiagram.parser.parse('stateDiagram-v2\n classDef exampleClass background:#bbb;'); stateDiagram.parser.parse('stateDiagram-v2\n classDef exampleClass background:#bbb;');
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses(); const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(1); expect(styleClasses.get('exampleClass').styles.length).toEqual(1);
@@ -27,7 +28,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;' 'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses(); const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(3); expect(styleClasses.get('exampleClass').styles.length).toEqual(3);
@@ -41,7 +41,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background:#bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef exampleStyleClass background:#bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -53,7 +52,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background: #bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef exampleStyleClass background: #bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -65,7 +63,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef __proto__ background:#bbb,border:1.5px solid red;\n classDef constructor background:#bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef __proto__ background:#bbb,border:1.5px solid red;\n classDef constructor background:#bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('__proto__').styles.length).toBe(2); expect(classes.get('__proto__').styles.length).toBe(2);
expect(classes.get('constructor').styles.length).toBe(2); expect(classes.get('constructor').styles.length).toBe(2);
@@ -81,7 +78,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a exampleStyleClass'; diagram += 'class a exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDb.getClasses(); const classes = stateDb.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toEqual(2); expect(classes.get('exampleStyleClass').styles.length).toEqual(2);
@@ -102,7 +98,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a_a exampleStyleClass'; diagram += 'class a_a exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -122,7 +117,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'a --> b:::exampleStyleClass' + '\n'; diagram += 'a --> b:::exampleStyleClass' + '\n';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
@@ -141,7 +135,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += '[*]:::exampleStyleClass --> b\n'; diagram += '[*]:::exampleStyleClass --> b\n';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
@@ -161,7 +154,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b exampleStyleClass'; diagram += 'class a,b exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
let classes = stateDiagram.parser.yy.getClasses(); let classes = stateDiagram.parser.yy.getClasses();
let states = stateDiagram.parser.yy.getStates(); let states = stateDiagram.parser.yy.getStates();
@@ -180,7 +172,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b,c, d, e exampleStyleClass'; diagram += 'class a,b,c, d, e exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
@@ -208,7 +199,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += '}\n'; diagram += '}\n';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
@@ -224,7 +214,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(`stateDiagram-v2 stateDiagram.parser.parse(`stateDiagram-v2
id1 id1
style id1 background:#bbb`); style id1 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']); expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -234,7 +223,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id1 id1
id2 id2
style id1,id2 background:#bbb`); style id1,id2 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']); expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -247,7 +235,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id2 id2
style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`); style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual([ expect(data4Layout.nodes[0].cssStyles).toEqual([

View File

@@ -1,6 +1,6 @@
import { line, curveBasis } from 'd3'; import { line, curveBasis } from 'd3';
import idCache from './id-cache.js'; import idCache from './id-cache.js';
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
import common from '../common/common.js'; import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
@@ -414,13 +414,13 @@ let edgeCount = 0;
export const drawEdge = function (elem, path, relation) { export const drawEdge = function (elem, path, relation) {
const getRelationType = function (type) { const getRelationType = function (type) {
switch (type) { switch (type) {
case stateDb.relationType.AGGREGATION: case StateDB.relationType.AGGREGATION:
return 'aggregation'; return 'aggregation';
case stateDb.relationType.EXTENSION: case StateDB.relationType.EXTENSION:
return 'extension'; return 'extension';
case stateDb.relationType.COMPOSITION: case StateDB.relationType.COMPOSITION:
return 'composition'; return 'composition';
case stateDb.relationType.DEPENDENCY: case StateDB.relationType.DEPENDENCY:
return 'dependency'; return 'dependency';
} }
}; };
@@ -459,7 +459,7 @@ export const drawEdge = function (elem, path, relation) {
svgPath.attr( svgPath.attr(
'marker-end', 'marker-end',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')' 'url(' + url + '#' + getRelationType(StateDB.relationType.DEPENDENCY) + 'End' + ')'
); );
if (relation.title !== undefined) { if (relation.title !== undefined) {

View File

@@ -3,11 +3,14 @@
*/ */
// default diagram direction // default diagram direction
export const DEFAULT_DIAGRAM_DIRECTION = 'LR'; export const DEFAULT_DIAGRAM_DIRECTION = 'TB';
// default direction for any nested documents (composites) // default direction for any nested documents (composites)
export const DEFAULT_NESTED_DOC_DIR = 'TB'; export const DEFAULT_NESTED_DOC_DIR = 'TB';
// parsed statement type for a direction
export const STMT_DIRECTION = 'dir';
// parsed statement type for a state // parsed statement type for a state
export const STMT_STATE = 'state'; export const STMT_STATE = 'state';
// parsed statement type for a relation // parsed statement type for a relation

View File

@@ -1,28 +1,29 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
import common from '../common/common.js'; import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
clear as commonClear, clear as commonClear,
setDiagramTitle, getAccDescription,
getAccTitle,
getDiagramTitle, getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js'; } from '../common/commonDb.js';
import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js'; import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js';
import { getDir } from './stateRenderer-v3-unified.js'; import { getDir } from './stateRenderer-v3-unified.js';
import { import {
DEFAULT_DIAGRAM_DIRECTION, DEFAULT_DIAGRAM_DIRECTION,
STMT_STATE,
STMT_RELATION,
STMT_CLASSDEF,
STMT_STYLEDEF,
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE, DEFAULT_STATE_TYPE,
DIVIDER_TYPE, DIVIDER_TYPE,
STMT_APPLYCLASS,
STMT_CLASSDEF,
STMT_DIRECTION,
STMT_RELATION,
STMT_STATE,
STMT_STYLEDEF,
} from './stateCommon.js'; } from './stateCommon.js';
const START_NODE = '[*]'; const START_NODE = '[*]';
@@ -46,15 +47,6 @@ function newClassesList() {
return new Map(); return new Map();
} }
let nodes = [];
let edges = [];
let direction = DEFAULT_DIAGRAM_DIRECTION;
let rootDoc = [];
let classes = newClassesList(); // style classes defined by a classDef
// --------------------------------------
const newDoc = () => { const newDoc = () => {
return { return {
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */ /** @type {{ id1: string, id2: string, relationTitle: string }[]} */
@@ -63,40 +55,109 @@ const newDoc = () => {
documents: {}, documents: {},
}; };
}; };
let documents = {
const clone = (o) => JSON.parse(JSON.stringify(o));
export class StateDB {
/**
* @param {1 | 2} version - v1 renderer or v2 renderer.
*/
constructor(version) {
this.clear();
this.version = version;
// Needed for JISON since it only supports direct properties
this.setRootDoc = this.setRootDoc.bind(this);
this.getDividerId = this.getDividerId.bind(this);
this.setDirection = this.setDirection.bind(this);
this.trimColon = this.trimColon.bind(this);
}
/**
* @private
* @type {1 | 2}
*/
version;
/**
* @private
* @type {Array}
*/
nodes = [];
/**
* @private
* @type {Array}
*/
edges = [];
/**
* @private
* @type {Array}
*/
rootDoc = [];
/**
* @private
* @type {Map<string, any>}
*/
classes = newClassesList(); // style classes defined by a classDef
/**
* @private
* @type {Object}
*/
documents = {
root: newDoc(), root: newDoc(),
}; };
let currentDocument = documents.root; /**
let startEndCount = 0; * @private
let dividerCnt = 0; * @type {Object}
*/
currentDocument = this.documents.root;
/**
* @private
* @type {number}
*/
startEndCount = 0;
/**
* @private
* @type {number}
*/
dividerCnt = 0;
export const lineType = { static relationType = {
LINE: 0,
DOTTED_LINE: 1,
};
export const relationType = {
AGGREGATION: 0, AGGREGATION: 0,
EXTENSION: 1, EXTENSION: 1,
COMPOSITION: 2, COMPOSITION: 2,
DEPENDENCY: 3, DEPENDENCY: 3,
}; };
const clone = (o) => JSON.parse(JSON.stringify(o)); setRootDoc(o) {
const setRootDoc = (o) => {
log.info('Setting root doc', o); log.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o }; // rootDoc = { id: 'root', doc: o };
rootDoc = o; this.rootDoc = o;
}; if (this.version === 1) {
this.extract(o);
} else {
this.extract(this.getRootDocV2());
}
}
const getRootDoc = () => rootDoc; getRootDoc() {
return this.rootDoc;
}
const docTranslator = (parent, node, first) => { /**
* @private
* @param {Object} parent
* @param {Object} node
* @param {boolean} first
*/
docTranslator(parent, node, first) {
if (node.stmt === STMT_RELATION) { if (node.stmt === STMT_RELATION) {
docTranslator(parent, node.state1, true); this.docTranslator(parent, node.state1, true);
docTranslator(parent, node.state2, false); this.docTranslator(parent, node.state2, false);
} else { } else {
if (node.stmt === STMT_STATE) { if (node.stmt === STMT_STATE) {
if (node.id === '[*]') { if (node.id === '[*]') {
@@ -115,7 +176,6 @@ const docTranslator = (parent, node, first) => {
let i; let i;
for (i = 0; i < node.doc.length; i++) { for (i = 0; i < node.doc.length; i++) {
if (node.doc[i].type === DIVIDER_TYPE) { if (node.doc[i].type === DIVIDER_TYPE) {
// debugger;
const newNode = clone(node.doc[i]); const newNode = clone(node.doc[i]);
newNode.doc = clone(currentDoc); newNode.doc = clone(currentDoc);
doc.push(newNode); doc.push(newNode);
@@ -137,15 +197,19 @@ const docTranslator = (parent, node, first) => {
node.doc = doc; node.doc = doc;
} }
node.doc.forEach((docNode) => docTranslator(node, docNode, true)); node.doc.forEach((docNode) => this.docTranslator(node, docNode, true));
} }
} }
}; }
const getRootDocV2 = () => {
docTranslator({ id: 'root' }, { id: 'root', doc: rootDoc }, true); /**
return { id: 'root', doc: rootDoc }; * @private
*/
getRootDocV2() {
this.docTranslator({ id: 'root' }, { id: 'root', doc: this.rootDoc }, true);
return { id: 'root', doc: this.rootDoc };
// Here // Here
}; }
/** /**
* Convert all of the statements (stmts) that were parsed into states and relationships. * Convert all of the statements (stmts) that were parsed into states and relationships.
@@ -155,10 +219,10 @@ const getRootDocV2 = () => {
* refer to the fork as a whole (document). * refer to the fork as a whole (document).
* See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement. * See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement.
* This will push the statement into the list of statements for the current document. * This will push the statement into the list of statements for the current document.
* * @private
* @param _doc * @param _doc
*/ */
const extract = (_doc) => { extract(_doc) {
// const res = { states: [], relations: [] }; // const res = { states: [], relations: [] };
let doc; let doc;
if (_doc.doc) { if (_doc.doc) {
@@ -171,7 +235,7 @@ const extract = (_doc) => {
// doc = root; // doc = root;
// } // }
log.info(doc); log.info(doc);
clear(true); this.clear(true);
log.info('Extract initial document:', doc); log.info('Extract initial document:', doc);
@@ -179,7 +243,7 @@ const extract = (_doc) => {
log.warn('Statement', item.stmt); log.warn('Statement', item.stmt);
switch (item.stmt) { switch (item.stmt) {
case STMT_STATE: case STMT_STATE:
addState( this.addState(
item.id.trim(), item.id.trim(),
item.type, item.type,
item.doc, item.doc,
@@ -191,38 +255,48 @@ const extract = (_doc) => {
); );
break; break;
case STMT_RELATION: case STMT_RELATION:
addRelation(item.state1, item.state2, item.description); this.addRelation(item.state1, item.state2, item.description);
break; break;
case STMT_CLASSDEF: case STMT_CLASSDEF:
addStyleClass(item.id.trim(), item.classes); this.addStyleClass(item.id.trim(), item.classes);
break; break;
case STMT_STYLEDEF: case STMT_STYLEDEF:
{ {
const ids = item.id.trim().split(','); const ids = item.id.trim().split(',');
const styles = item.styleClass.split(','); const styles = item.styleClass.split(',');
ids.forEach((id) => { ids.forEach((id) => {
let foundState = getState(id); let foundState = this.getState(id);
if (foundState === undefined) { if (foundState === undefined) {
const trimmedId = id.trim(); const trimmedId = id.trim();
addState(trimmedId); this.addState(trimmedId);
foundState = getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim()); foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
}); });
} }
break; break;
case STMT_APPLYCLASS: case STMT_APPLYCLASS:
setCssClass(item.id.trim(), item.styleClass); this.setCssClass(item.id.trim(), item.styleClass);
break; break;
} }
}); });
const diagramStates = getStates(); const diagramStates = this.getStates();
const config = getConfig(); const config = getConfig();
const look = config.look; const look = config.look;
resetDataFetching(); resetDataFetching();
dataFetcher(undefined, getRootDocV2(), diagramStates, nodes, edges, true, look, classes); dataFetcher(
nodes.forEach((node) => { undefined,
this.getRootDocV2(),
diagramStates,
this.nodes,
this.edges,
true,
look,
this.classes
);
this.nodes.forEach((node) => {
if (Array.isArray(node.label)) { if (Array.isArray(node.label)) {
// add the rest as description // add the rest as description
node.description = node.label.slice(1); node.description = node.label.slice(1);
@@ -237,7 +311,7 @@ const extract = (_doc) => {
node.label = node.label[0]; node.label = node.label[0];
} }
}); });
}; }
/** /**
* Function called by parser when a node definition has been found. * Function called by parser when a node definition has been found.
@@ -251,7 +325,7 @@ const extract = (_doc) => {
* @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style. * @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style.
* @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style. * @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style.
*/ */
export const addState = function ( addState(
id, id,
type = DEFAULT_STATE_TYPE, type = DEFAULT_STATE_TYPE,
doc = null, doc = null,
@@ -263,9 +337,9 @@ export const addState = function (
) { ) {
const trimmedId = id?.trim(); const trimmedId = id?.trim();
// add the state if needed // add the state if needed
if (!currentDocument.states.has(trimmedId)) { if (!this.currentDocument.states.has(trimmedId)) {
log.info('Adding state ', trimmedId, descr); log.info('Adding state ', trimmedId, descr);
currentDocument.states.set(trimmedId, { this.currentDocument.states.set(trimmedId, {
id: trimmedId, id: trimmedId,
descriptions: [], descriptions: [],
type, type,
@@ -276,27 +350,27 @@ export const addState = function (
textStyles: [], textStyles: [],
}); });
} else { } else {
if (!currentDocument.states.get(trimmedId).doc) { if (!this.currentDocument.states.get(trimmedId).doc) {
currentDocument.states.get(trimmedId).doc = doc; this.currentDocument.states.get(trimmedId).doc = doc;
} }
if (!currentDocument.states.get(trimmedId).type) { if (!this.currentDocument.states.get(trimmedId).type) {
currentDocument.states.get(trimmedId).type = type; this.currentDocument.states.get(trimmedId).type = type;
} }
} }
if (descr) { if (descr) {
log.info('Setting state description', trimmedId, descr); log.info('Setting state description', trimmedId, descr);
if (typeof descr === 'string') { if (typeof descr === 'string') {
addDescription(trimmedId, descr.trim()); this.addDescription(trimmedId, descr.trim());
} }
if (typeof descr === 'object') { if (typeof descr === 'object') {
descr.forEach((des) => addDescription(trimmedId, des.trim())); descr.forEach((des) => this.addDescription(trimmedId, des.trim()));
} }
} }
if (note) { if (note) {
const doc2 = currentDocument.states.get(trimmedId); const doc2 = this.currentDocument.states.get(trimmedId);
doc2.note = note; doc2.note = note;
doc2.note.text = common.sanitizeText(doc2.note.text, getConfig()); doc2.note.text = common.sanitizeText(doc2.note.text, getConfig());
} }
@@ -304,51 +378,50 @@ export const addState = function (
if (classes) { if (classes) {
log.info('Setting state classes', trimmedId, classes); log.info('Setting state classes', trimmedId, classes);
const classesList = typeof classes === 'string' ? [classes] : classes; const classesList = typeof classes === 'string' ? [classes] : classes;
classesList.forEach((cssClass) => setCssClass(trimmedId, cssClass.trim())); classesList.forEach((cssClass) => this.setCssClass(trimmedId, cssClass.trim()));
} }
if (styles) { if (styles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const stylesList = typeof styles === 'string' ? [styles] : styles; const stylesList = typeof styles === 'string' ? [styles] : styles;
stylesList.forEach((style) => setStyle(trimmedId, style.trim())); stylesList.forEach((style) => this.setStyle(trimmedId, style.trim()));
} }
if (textStyles) { if (textStyles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles; const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles;
textStylesList.forEach((textStyle) => setTextStyle(trimmedId, textStyle.trim())); textStylesList.forEach((textStyle) => this.setTextStyle(trimmedId, textStyle.trim()));
}
} }
};
export const clear = function (saveCommon) { clear(saveCommon) {
nodes = []; this.nodes = [];
edges = []; this.edges = [];
documents = { this.documents = {
root: newDoc(), root: newDoc(),
}; };
currentDocument = documents.root; this.currentDocument = this.documents.root;
// number of start and end nodes; used to construct ids // number of start and end nodes; used to construct ids
startEndCount = 0; this.startEndCount = 0;
classes = newClassesList(); this.classes = newClassesList();
if (!saveCommon) { if (!saveCommon) {
commonClear(); commonClear();
} }
}; }
export const getState = function (id) { getState(id) {
return currentDocument.states.get(id); return this.currentDocument.states.get(id);
}; }
getStates() {
export const getStates = function () { return this.currentDocument.states;
return currentDocument.states; }
}; logDocuments() {
export const logDocuments = function () { log.info('Documents = ', this.documents);
log.info('Documents = ', documents); }
}; getRelations() {
export const getRelations = function () { return this.currentDocument.relations;
return currentDocument.relations; }
};
/** /**
* If the id is a start node ( [*] ), then return a new id constructed from * If the id is a start node ( [*] ), then return a new id constructed from
@@ -357,12 +430,13 @@ export const getRelations = function () {
* *
* @param {string} id * @param {string} id
* @returns {string} - the id (original or constructed) * @returns {string} - the id (original or constructed)
* @private
*/ */
function startIdIfNeeded(id = '') { startIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
if (id === START_NODE) { if (id === START_NODE) {
startEndCount++; this.startEndCount++;
fixedId = `${START_TYPE}${startEndCount}`; fixedId = `${START_TYPE}${this.startEndCount}`;
} }
return fixedId; return fixedId;
} }
@@ -374,8 +448,9 @@ function startIdIfNeeded(id = '') {
* @param {string} id * @param {string} id
* @param {string} type * @param {string} type
* @returns {string} - the type that should be used * @returns {string} - the type that should be used
* @private
*/ */
function startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === START_NODE ? START_TYPE : type; return id === START_NODE ? START_TYPE : type;
} }
@@ -386,12 +461,13 @@ function startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
* *
* @param {string} id * @param {string} id
* @returns {string} - the id (original or constructed) * @returns {string} - the id (original or constructed)
* @private
*/ */
function endIdIfNeeded(id = '') { endIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
if (id === END_NODE) { if (id === END_NODE) {
startEndCount++; this.startEndCount++;
fixedId = `${END_TYPE}${startEndCount}`; fixedId = `${END_TYPE}${this.startEndCount}`;
} }
return fixedId; return fixedId;
} }
@@ -403,8 +479,9 @@ function endIdIfNeeded(id = '') {
* @param {string} id * @param {string} id
* @param {string} type * @param {string} type
* @returns {string} - the type that should be used * @returns {string} - the type that should be used
* @private
*/ */
function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === END_NODE ? END_TYPE : type; return id === END_NODE ? END_TYPE : type;
} }
@@ -414,13 +491,13 @@ function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
* @param item2 * @param item2
* @param relationTitle * @param relationTitle
*/ */
export function addRelationObjs(item1, item2, relationTitle) { addRelationObjs(item1, item2, relationTitle) {
let id1 = startIdIfNeeded(item1.id.trim()); let id1 = this.startIdIfNeeded(item1.id.trim());
let type1 = startTypeIfNeeded(item1.id.trim(), item1.type); let type1 = this.startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = startIdIfNeeded(item2.id.trim()); let id2 = this.startIdIfNeeded(item2.id.trim());
let type2 = startTypeIfNeeded(item2.id.trim(), item2.type); let type2 = this.startTypeIfNeeded(item2.id.trim(), item2.type);
addState( this.addState(
id1, id1,
type1, type1,
item1.doc, item1.doc,
@@ -430,7 +507,7 @@ export function addRelationObjs(item1, item2, relationTitle) {
item1.styles, item1.styles,
item1.textStyles item1.textStyles
); );
addState( this.addState(
id2, id2,
type2, type2,
item2.doc, item2.doc,
@@ -441,7 +518,7 @@ export function addRelationObjs(item1, item2, relationTitle) {
item2.textStyles item2.textStyles
); );
currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
id2, id2,
relationTitle: common.sanitizeText(relationTitle, getConfig()), relationTitle: common.sanitizeText(relationTitle, getConfig()),
@@ -455,43 +532,43 @@ export function addRelationObjs(item1, item2, relationTitle) {
* @param {string | object} item2 * @param {string | object} item2
* @param {string} title * @param {string} title
*/ */
export const addRelation = function (item1, item2, title) { addRelation(item1, item2, title) {
if (typeof item1 === 'object') { if (typeof item1 === 'object') {
addRelationObjs(item1, item2, title); this.addRelationObjs(item1, item2, title);
} else { } else {
const id1 = startIdIfNeeded(item1.trim()); const id1 = this.startIdIfNeeded(item1.trim());
const type1 = startTypeIfNeeded(item1); const type1 = this.startTypeIfNeeded(item1);
const id2 = endIdIfNeeded(item2.trim()); const id2 = this.endIdIfNeeded(item2.trim());
const type2 = endTypeIfNeeded(item2); const type2 = this.endTypeIfNeeded(item2);
addState(id1, type1); this.addState(id1, type1);
addState(id2, type2); this.addState(id2, type2);
currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
id2, id2,
title: common.sanitizeText(title, getConfig()), title: common.sanitizeText(title, getConfig()),
}); });
} }
}; }
export const addDescription = function (id, descr) { addDescription(id, descr) {
const theState = currentDocument.states.get(id); const theState = this.currentDocument.states.get(id);
const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr; const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr;
theState.descriptions.push(common.sanitizeText(_descr, getConfig())); theState.descriptions.push(common.sanitizeText(_descr, getConfig()));
}; }
export const cleanupLabel = function (label) { cleanupLabel(label) {
if (label.substring(0, 1) === ':') { if (label.substring(0, 1) === ':') {
return label.substr(2).trim(); return label.substr(2).trim();
} else { } else {
return label.trim(); return label.trim();
} }
}; }
const getDividerId = () => { getDividerId() {
dividerCnt++; this.dividerCnt++;
return 'divider-id-' + dividerCnt; return 'divider-id-' + this.dividerCnt;
}; }
/** /**
* Called when the parser comes across a (style) class definition * Called when the parser comes across a (style) class definition
@@ -500,12 +577,12 @@ const getDividerId = () => {
* @param {string} id - the id of this (style) class * @param {string} id - the id of this (style) class
* @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma) * @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/ */
export const addStyleClass = function (id, styleAttributes = '') { addStyleClass(id, styleAttributes = '') {
// create a new style class object with this id // create a new style class object with this id
if (!classes.has(id)) { if (!this.classes.has(id)) {
classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef this.classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef
} }
const foundClass = classes.get(id); const foundClass = this.classes.get(id);
if (styleAttributes !== undefined && styleAttributes !== null) { if (styleAttributes !== undefined && styleAttributes !== null) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => { styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
// remove any trailing ; // remove any trailing ;
@@ -520,15 +597,15 @@ export const addStyleClass = function (id, styleAttributes = '') {
foundClass.styles.push(fixedAttrib); foundClass.styles.push(fixedAttrib);
}); });
} }
}; }
/** /**
* Return all of the style classes * Return all of the style classes
* @returns {{} | any | classes} * @returns {{} | any | classes}
*/ */
export const getClasses = function () { getClasses() {
return classes; return this.classes;
}; }
/** /**
* Add a (style) class or css class to a state with the given id. * Add a (style) class or css class to a state with the given id.
@@ -538,17 +615,17 @@ export const getClasses = function () {
* @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to * @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to
* @param {string} cssClassName CSS class name * @param {string} cssClassName CSS class name
*/ */
export const setCssClass = function (itemIds, cssClassName) { setCssClass(itemIds, cssClassName) {
itemIds.split(',').forEach(function (id) { itemIds.split(',').forEach((id) => {
let foundState = getState(id); let foundState = this.getState(id);
if (foundState === undefined) { if (foundState === undefined) {
const trimmedId = id.trim(); const trimmedId = id.trim();
addState(trimmedId); this.addState(trimmedId);
foundState = getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.classes.push(cssClassName); foundState.classes.push(cssClassName);
}); });
}; }
/** /**
* Add a style to a state with the given id. * Add a style to a state with the given id.
@@ -560,12 +637,12 @@ export const setCssClass = function (itemIds, cssClassName) {
* @param itemId The id of item to apply the style to * @param itemId The id of item to apply the style to
* @param styleText - the text of the attributes for the style * @param styleText - the text of the attributes for the style
*/ */
export const setStyle = function (itemId, styleText) { setStyle(itemId, styleText) {
const item = getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.styles.push(styleText); item.styles.push(styleText);
} }
}; }
/** /**
* Add a text style to a state with the given id * Add a text style to a state with the given id
@@ -573,54 +650,57 @@ export const setStyle = function (itemId, styleText) {
* @param itemId The id of item to apply the css class to * @param itemId The id of item to apply the css class to
* @param cssClassName CSS class name * @param cssClassName CSS class name
*/ */
export const setTextStyle = function (itemId, cssClassName) { setTextStyle(itemId, cssClassName) {
const item = getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.textStyles.push(cssClassName); item.textStyles.push(cssClassName);
} }
}; }
const getDirection = () => direction; /**
const setDirection = (dir) => { * Finds the direction statement in the root document.
direction = dir; * @private
}; * @returns {{ value: string } | undefined} - the direction statement if present
*/
getDirectionStatement() {
return this.rootDoc.find((doc) => doc.stmt === STMT_DIRECTION);
}
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim()); getDirection() {
return this.getDirectionStatement()?.value ?? DEFAULT_DIAGRAM_DIRECTION;
}
export const getData = () => { setDirection(dir) {
const doc = this.getDirectionStatement();
if (doc) {
doc.value = dir;
} else {
this.rootDoc.unshift({ stmt: STMT_DIRECTION, value: dir });
}
}
trimColon(str) {
return str && str[0] === ':' ? str.substr(1).trim() : str.trim();
}
getData() {
const config = getConfig(); const config = getConfig();
return { nodes, edges, other: {}, config, direction: getDir(getRootDocV2()) }; return {
nodes: this.nodes,
edges: this.edges,
other: {},
config,
direction: getDir(this.getRootDocV2()),
}; };
}
export default { getConfig() {
getConfig: () => getConfig().state, return getConfig().state;
getData, }
addState, getAccTitle = getAccTitle;
clear, setAccTitle = setAccTitle;
getState, getAccDescription = getAccDescription;
getStates, setAccDescription = setAccDescription;
getRelations, setDiagramTitle = setDiagramTitle;
getClasses, getDiagramTitle = getDiagramTitle;
getDirection, }
addRelation,
getDividerId,
setDirection,
cleanupLabel,
lineType,
relationType,
logDocuments,
getRootDoc,
setRootDoc,
getRootDocV2,
extract,
trimColon,
getAccTitle,
setAccTitle,
getAccDescription,
setAccDescription,
addStyleClass,
setCssClass,
addDescription,
setDiagramTitle,
getDiagramTitle,
};

View File

@@ -1,8 +1,9 @@
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
describe('State Diagram stateDb', () => { describe('State Diagram stateDb', () => {
let stateDb;
beforeEach(() => { beforeEach(() => {
stateDb.clear(); stateDb = new StateDB(1);
}); });
describe('addStyleClass', () => { describe('addStyleClass', () => {
@@ -20,8 +21,9 @@ describe('State Diagram stateDb', () => {
}); });
describe('addDescription to a state', () => { describe('addDescription to a state', () => {
let stateDb;
beforeEach(() => { beforeEach(() => {
stateDb.clear(); stateDb = new StateDB(1);
stateDb.addState('state1'); stateDb.addState('state1');
}); });
@@ -73,3 +75,25 @@ describe('State Diagram stateDb', () => {
}); });
}); });
}); });
describe('state db class', () => {
let stateDb;
beforeEach(() => {
stateDb = new StateDB(1);
});
// This is to ensure that functions used in state JISON are exposed as function from StateDb
it('should have functions used in flow JISON as own property', () => {
const functionsUsedInParser = [
'setRootDoc',
'trimColon',
'getDividerId',
'setAccTitle',
'setAccDescription',
'setDirection',
];
for (const fun of functionsUsedInParser) {
expect(Object.hasOwn(stateDb, fun)).toBe(true);
}
});
});

View File

@@ -1,11 +1,13 @@
import { parser } from './parser/stateDiagram.jison'; import stateDiagram, { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js'; import { DEFAULT_DIAGRAM_DIRECTION } from './stateCommon.js';
import stateDiagram from './parser/stateDiagram.jison'; import { StateDB } from './stateDb.js';
describe('state diagram V2, ', function () { describe('state diagram V2, ', function () {
// TODO - these examples should be put into ./parser/stateDiagram.spec.js // TODO - these examples should be put into ./parser/stateDiagram.spec.js
describe('when parsing an info graph it', function () { describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB(2);
parser.yy = stateDb; parser.yy = stateDb;
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
@@ -127,7 +129,6 @@ describe('state diagram V2, ', function () {
`; `;
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const rels = stateDb.getRelations(); const rels = stateDb.getRelations();
const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2'); const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2');
@@ -402,7 +403,6 @@ describe('state diagram V2, ', function () {
`; `;
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDb.getStates(); const states = stateDb.getStates();
expect(states.get('Active').doc[0].id).toEqual('Idle'); expect(states.get('Active').doc[0].id).toEqual('Idle');
@@ -413,5 +413,34 @@ describe('state diagram V2, ', function () {
const rel_Active_Active = rels.find((rel) => rel.id1 === 'Active' && rel.id2 === 'Active'); const rel_Active_Active = rels.find((rel) => rel.id1 === 'Active' && rel.id2 === 'Active');
expect(rel_Active_Active.relationTitle).toEqual('LOG'); expect(rel_Active_Active.relationTitle).toEqual('LOG');
}); });
it('should check default diagram direction', () => {
const diagram = `
stateDiagram
[*] --> Still
Still --> [*]
`;
parser.parse(diagram);
// checking default direction if no direction is specified
const defaultDir = stateDb.getDirection();
expect(defaultDir).toEqual(DEFAULT_DIAGRAM_DIRECTION);
});
it('retrieve the diagram direction correctly', () => {
const diagram = `
stateDiagram
direction LR
[*] --> Still
Still --> [*]
`;
parser.parse(diagram);
//retrieve the diagram direction
const currentDirection = stateDb.getDirection();
expect(currentDirection).toEqual('LR');
});
}); });
}); });

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison'; import parser from './parser/stateDiagram.jison';
import db from './stateDb.js'; import { StateDB } from './stateDb.js';
import styles from './styles.js'; import styles from './styles.js';
import renderer from './stateRenderer-v3-unified.js'; import renderer from './stateRenderer-v3-unified.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new StateDB(2);
},
renderer, renderer,
styles, styles,
init: (cnf) => { init: (cnf) => {

View File

@@ -1,9 +1,11 @@
import { parser } from './parser/stateDiagram.jison'; import { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
describe('state diagram, ', function () { describe('state diagram, ', function () {
describe('when parsing an info graph it', function () { describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB(1);
parser.yy = stateDb; parser.yy = stateDb;
}); });

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison'; import parser from './parser/stateDiagram.jison';
import db from './stateDb.js'; import { StateDB } from './stateDb.js';
import styles from './styles.js'; import styles from './styles.js';
import renderer from './stateRenderer.js'; import renderer from './stateRenderer.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new StateDB(1);
},
renderer, renderer,
styles, styles,
init: (cnf) => { init: (cnf) => {

View File

@@ -36,7 +36,6 @@ export const getClasses = function (
text: string, text: string,
diagramObj: any diagramObj: any
): Map<string, DiagramStyleClassDef> { ): Map<string, DiagramStyleClassDef> {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses(); return diagramObj.db.getClasses();
}; };

View File

@@ -136,7 +136,6 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) =
return {}; return {};
}); });
diagObj.db.extract(doc);
const states = diagObj.db.getStates(); const states = diagObj.db.getStates();
const relations = diagObj.db.getRelations(); const relations = diagObj.db.getRelations();

View File

@@ -67,10 +67,12 @@ vi.mock('stylis', () => {
import { compile, serialize } from 'stylis'; import { compile, serialize } from 'stylis';
import { Diagram } from './Diagram.js'; import { Diagram } from './Diagram.js';
import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js';
import { ClassDB } from './diagrams/class/classDb.js'; import { ClassDB } from './diagrams/class/classDb.js';
import { FlowDB } from './diagrams/flowchart/flowDb.js'; import { FlowDB } from './diagrams/flowchart/flowDb.js';
import { SequenceDB } from './diagrams/sequence/sequenceDb.js';
import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js';
import { StateDB } from './diagrams/state/stateDb.js';
/** /**
* @see https://vitest.dev/guide/mocking.html Mock part of a module * @see https://vitest.dev/guide/mocking.html Mock part of a module
@@ -836,6 +838,31 @@ graph TD;A--x|text including URL space|B;`)
}); });
it('should not modify db when rendering different diagrams', async () => { it('should not modify db when rendering different diagrams', async () => {
const stateDiagram1 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction LR
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
const stateDiagram2 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction TB
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
expect(stateDiagram1.db).not.toBe(stateDiagram2.db);
assert(stateDiagram1.db instanceof StateDB);
assert(stateDiagram2.db instanceof StateDB);
expect(stateDiagram1.db.getDirection()).not.toEqual(stateDiagram2.db.getDirection());
const flowDiagram1 = await mermaidAPI.getDiagramFromText( const flowDiagram1 = await mermaidAPI.getDiagramFromText(
`flowchart LR `flowchart LR
A -- text --> B -- text2 --> C` A -- text --> B -- text2 --> C`
@@ -899,28 +926,18 @@ graph TD;A--x|text including URL space|B;`)
); );
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText( const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram `sequenceDiagram
actor A1
Alice->>+John: Hello John, how are you? Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me? Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you! John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!` John-->>-Alice: I feel great!`
); );
// Since sequenceDiagram will return same Db object each time, we can compare the db to be same.
expect(sequenceDiagram1.db).toBe(sequenceDiagram2.db);
});
});
// Sequence Diagram currently uses a singleton DB, so this test will fail // Since sequenceDiagram will return new Db object each time, we can compare the db to be different.
it.fails('should not modify db when rendering different sequence diagrams', async () => {
const sequenceDiagram1 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
expect(sequenceDiagram1.db).not.toBe(sequenceDiagram2.db); expect(sequenceDiagram1.db).not.toBe(sequenceDiagram2.db);
assert(sequenceDiagram1.db instanceof SequenceDB);
assert(sequenceDiagram2.db instanceof SequenceDB);
expect(sequenceDiagram1.db.getActors()).not.toEqual(sequenceDiagram2.db.getActors());
});
}); });
}); });

2433
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff