mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-13 11:09:39 +02:00
Compare commits
30 Commits
4743-timel
...
demo/useca
Author | SHA1 | Date | |
---|---|---|---|
![]() |
89b29898d2 | ||
![]() |
2972bf25bf | ||
![]() |
6b1a7a9e1a | ||
![]() |
33bc4a0b4e | ||
![]() |
c6f25167a2 | ||
![]() |
82800a2c84 | ||
![]() |
27e700debd | ||
![]() |
01e47333d5 | ||
![]() |
d47ba7c2d1 | ||
![]() |
b1c4eb3f5c | ||
![]() |
310fcd2292 | ||
![]() |
04b6fc1280 | ||
![]() |
f46a151075 | ||
![]() |
b7e9d02b7c | ||
![]() |
0ef3130510 | ||
![]() |
862d40cc3a | ||
![]() |
00f5700320 | ||
![]() |
e32dc8513f | ||
![]() |
50127f3ffe | ||
![]() |
29bb0e3dca | ||
![]() |
1221de4c2d | ||
![]() |
c41e08cb7a | ||
![]() |
4760ed8893 | ||
![]() |
31ecf31c2e | ||
![]() |
b52766653c | ||
![]() |
8322a63598 | ||
![]() |
c0e2d4a23b | ||
![]() |
f7a0844a31 | ||
![]() |
2817383714 | ||
![]() |
80c6faf4d5 |
5
.changeset/clean-wolves-turn.md
Normal file
5
.changeset/clean-wolves-turn.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Render newlines as spaces in class diagrams
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': major
|
||||
---
|
||||
|
||||
Currently, HTML tags such as <em>, <strong>, <sup>, <a>, <ul>, and <li> are supported in Flowchart and Class diagram labels but not in Timeline diagrams. This change introduces support for basic HTML formatting in Timeline labels, enabling richer text formatting and better usability for multi-line content like descriptions, footnotes, and styled annotations
|
3
.github/workflows/e2e-applitools.yml
vendored
3
.github/workflows/e2e-applitools.yml
vendored
@@ -23,9 +23,6 @@ env:
|
||||
jobs:
|
||||
e2e-applitools:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
|
||||
options: --user 1001
|
||||
steps:
|
||||
- if: ${{ ! env.USE_APPLI }}
|
||||
name: Warn if not using Applitools
|
||||
|
2
.github/workflows/e2e-timings.yml
vendored
2
.github/workflows/e2e-timings.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and create pull request
|
||||
uses: peter-evans/create-pull-request@cb4d3bfce175d44325c6b7697f81e0afe8a79bdf
|
||||
uses: peter-evans/create-pull-request@18e469570b1cf0dfc11d60ec121099f8ff3e617a
|
||||
with:
|
||||
add-paths: |
|
||||
cypress/timings.json
|
||||
|
@@ -524,5 +524,18 @@ describe('Class diagram', () => {
|
||||
`,
|
||||
{}
|
||||
);
|
||||
it('should handle an empty class body with empty braces', () => {
|
||||
imgSnapshotTest(
|
||||
` classDiagram
|
||||
class FooBase~T~ {}
|
||||
class Bar {
|
||||
+Zip
|
||||
+Zap()
|
||||
}
|
||||
FooBase <|-- Ba
|
||||
`,
|
||||
{ flowchart: { defaultRenderer: 'elk' } }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -225,24 +225,4 @@ timeline
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('13: should render markdown htmlLabels', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
theme: forest
|
||||
---
|
||||
|
||||
timeline
|
||||
title Timeline of Industrial Revolution
|
||||
section 17th-20th century
|
||||
Industry 1.0 : Machinery, Water power, Steam <br>power
|
||||
Industry 2.0 : Electricity, <strong>Internal combustion engine </strong>, Mass production
|
||||
Industry 3.0 : Electronics, Computers, Automation
|
||||
section 21st century
|
||||
Industry 4.0 : Internet, Robotics, Internet of Things
|
||||
Industry 5.0 : Artificial intelligence, Big data, 3D printing
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -2,219 +2,223 @@
|
||||
"durations": [
|
||||
{
|
||||
"spec": "cypress/integration/other/configuration.spec.js",
|
||||
"duration": 6297
|
||||
"duration": 6162
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/external-diagrams.spec.js",
|
||||
"duration": 2187
|
||||
"duration": 2148
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/ghsa.spec.js",
|
||||
"duration": 3509
|
||||
"duration": 3585
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/iife.spec.js",
|
||||
"duration": 2218
|
||||
"duration": 2099
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/interaction.spec.js",
|
||||
"duration": 12104
|
||||
"duration": 12119
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/rerender.spec.js",
|
||||
"duration": 2151
|
||||
"duration": 2063
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/other/xss.spec.js",
|
||||
"duration": 33064
|
||||
"duration": 31921
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/appli.spec.js",
|
||||
"duration": 3488
|
||||
"duration": 3385
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/architecture.spec.ts",
|
||||
"duration": 106
|
||||
"duration": 108
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/block.spec.js",
|
||||
"duration": 18317
|
||||
"duration": 18063
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/c4.spec.js",
|
||||
"duration": 5592
|
||||
"duration": 5519
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
|
||||
"duration": 39358
|
||||
"duration": 40040
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
|
||||
"duration": 37160
|
||||
"duration": 38665
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
|
||||
"duration": 23660
|
||||
"duration": 22836
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
|
||||
"duration": 36866
|
||||
"duration": 37096
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/classDiagram.spec.js",
|
||||
"duration": 17334
|
||||
"duration": 16452
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
|
||||
"duration": 9871
|
||||
"duration": 10387
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/current.spec.js",
|
||||
"duration": 2833
|
||||
"duration": 2803
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
|
||||
"duration": 85321
|
||||
"duration": 86891
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/erDiagram.spec.js",
|
||||
"duration": 15673
|
||||
"duration": 15206
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
|
||||
"duration": 3724
|
||||
"duration": 3540
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
|
||||
"duration": 41178
|
||||
"duration": 41975
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
|
||||
"duration": 29966
|
||||
"duration": 30909
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
|
||||
"duration": 7689
|
||||
"duration": 7881
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
|
||||
"duration": 24709
|
||||
"duration": 24294
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
|
||||
"duration": 45565
|
||||
"duration": 47652
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/flowchart.spec.js",
|
||||
"duration": 31144
|
||||
"duration": 32049
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/gantt.spec.js",
|
||||
"duration": 20808
|
||||
"duration": 20248
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/gitGraph.spec.js",
|
||||
"duration": 49985
|
||||
"duration": 51202
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/iconShape.spec.ts",
|
||||
"duration": 273272
|
||||
"duration": 283546
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/imageShape.spec.ts",
|
||||
"duration": 55880
|
||||
"duration": 57257
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/info.spec.ts",
|
||||
"duration": 3271
|
||||
"duration": 3352
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/journey.spec.js",
|
||||
"duration": 7293
|
||||
"duration": 7423
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/kanban.spec.ts",
|
||||
"duration": 7861
|
||||
"duration": 7804
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/katex.spec.js",
|
||||
"duration": 3922
|
||||
"duration": 3847
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
|
||||
"duration": 2726
|
||||
"duration": 2637
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/mindmap.spec.ts",
|
||||
"duration": 11670
|
||||
"duration": 11658
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/newShapes.spec.ts",
|
||||
"duration": 146020
|
||||
"duration": 149500
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
|
||||
"duration": 114244
|
||||
"duration": 115427
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/packet.spec.ts",
|
||||
"duration": 5036
|
||||
"duration": 4801
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/pie.spec.ts",
|
||||
"duration": 6545
|
||||
"duration": 6786
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
|
||||
"duration": 9097
|
||||
"duration": 9422
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/radar.spec.js",
|
||||
"duration": 5676
|
||||
"duration": 5652
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/requirement.spec.js",
|
||||
"duration": 2795
|
||||
"duration": 2787
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
|
||||
"duration": 51660
|
||||
"duration": 53631
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/sankey.spec.ts",
|
||||
"duration": 6957
|
||||
"duration": 7075
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/sequencediagram-v2.spec.js",
|
||||
"duration": 20446
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
|
||||
"duration": 36026
|
||||
"duration": 37326
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
|
||||
"duration": 29551
|
||||
"duration": 29208
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
|
||||
"duration": 17364
|
||||
"duration": 16328
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/theme.spec.js",
|
||||
"duration": 30209
|
||||
"duration": 30541
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/timeline.spec.ts",
|
||||
"duration": 8699
|
||||
"duration": 8611
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/treemap.spec.ts",
|
||||
"duration": 12168
|
||||
"duration": 11878
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/xyChart.spec.js",
|
||||
"duration": 21453
|
||||
"duration": 20400
|
||||
},
|
||||
{
|
||||
"spec": "cypress/integration/rendering/zenuml.spec.js",
|
||||
"duration": 3577
|
||||
"duration": 3528
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217)
|
||||
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712)
|
||||
2. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
|
||||
3. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
|
||||
4. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)
|
||||
|
@@ -19,6 +19,7 @@
|
||||
- [addDirective](functions/addDirective.md)
|
||||
- [getConfig](functions/getConfig.md)
|
||||
- [getSiteConfig](functions/getSiteConfig.md)
|
||||
- [getUserDefinedConfig](functions/getUserDefinedConfig.md)
|
||||
- [reset](functions/reset.md)
|
||||
- [sanitize](functions/sanitize.md)
|
||||
- [saveConfigFromInitialize](functions/saveConfigFromInitialize.md)
|
||||
|
19
docs/config/setup/config/functions/getUserDefinedConfig.md
Normal file
19
docs/config/setup/config/functions/getUserDefinedConfig.md
Normal file
@@ -0,0 +1,19 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md).
|
||||
|
||||
[**mermaid**](../../README.md)
|
||||
|
||||
---
|
||||
|
||||
# Function: getUserDefinedConfig()
|
||||
|
||||
> **getUserDefinedConfig**(): [`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
|
||||
|
||||
Defined in: [packages/mermaid/src/config.ts:252](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L252)
|
||||
|
||||
## Returns
|
||||
|
||||
[`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
|
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.0.4",
|
||||
"@iconify/utils": "^2.1.33",
|
||||
"@iconify/utils": "^3.0.1",
|
||||
"@mermaid-js/parser": "workspace:^",
|
||||
"@types/d3": "^7.4.3",
|
||||
"cytoscape": "^3.29.3",
|
||||
|
@@ -78,3 +78,187 @@ describe('when working with site config', () => {
|
||||
expect(config_4.altFontFamily).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserDefinedConfig', () => {
|
||||
beforeEach(() => {
|
||||
configApi.reset();
|
||||
});
|
||||
|
||||
it('should return empty object when no user config is defined', () => {
|
||||
const userConfig = configApi.getUserDefinedConfig();
|
||||
expect(userConfig).toEqual({});
|
||||
});
|
||||
|
||||
it('should return config from initialize only', () => {
|
||||
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial' };
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
|
||||
const userConfig = configApi.getUserDefinedConfig();
|
||||
expect(userConfig).toEqual(initConfig);
|
||||
});
|
||||
|
||||
it('should return config from directives only', () => {
|
||||
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
|
||||
const directive2: MermaidConfig = { theme: 'forest' };
|
||||
|
||||
configApi.addDirective(directive1);
|
||||
configApi.addDirective(directive2);
|
||||
|
||||
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fontFamily": "Arial",
|
||||
"fontSize": 14,
|
||||
"layout": "elk",
|
||||
"theme": "forest",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should combine initialize config and directives', () => {
|
||||
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial', layout: 'dagre' };
|
||||
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
|
||||
const directive2: MermaidConfig = { theme: 'forest' };
|
||||
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
configApi.addDirective(directive1);
|
||||
configApi.addDirective(directive2);
|
||||
|
||||
const userConfig = configApi.getUserDefinedConfig();
|
||||
expect(userConfig).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fontFamily": "Arial",
|
||||
"fontSize": 14,
|
||||
"layout": "elk",
|
||||
"theme": "forest",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle nested config objects properly', () => {
|
||||
const initConfig: MermaidConfig = {
|
||||
flowchart: { nodeSpacing: 50, rankSpacing: 100 },
|
||||
theme: 'default',
|
||||
};
|
||||
const directive: MermaidConfig = {
|
||||
flowchart: { nodeSpacing: 75, curve: 'basis' },
|
||||
mindmap: { padding: 20 },
|
||||
};
|
||||
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
configApi.addDirective(directive);
|
||||
|
||||
const userConfig = configApi.getUserDefinedConfig();
|
||||
expect(userConfig).toMatchInlineSnapshot(`
|
||||
{
|
||||
"flowchart": {
|
||||
"curve": "basis",
|
||||
"nodeSpacing": 75,
|
||||
"rankSpacing": 100,
|
||||
},
|
||||
"mindmap": {
|
||||
"padding": 20,
|
||||
},
|
||||
"theme": "default",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle complex nested overrides', () => {
|
||||
const initConfig: MermaidConfig = {
|
||||
flowchart: {
|
||||
nodeSpacing: 50,
|
||||
rankSpacing: 100,
|
||||
curve: 'linear',
|
||||
},
|
||||
theme: 'default',
|
||||
};
|
||||
const directive1: MermaidConfig = {
|
||||
flowchart: {
|
||||
nodeSpacing: 75,
|
||||
},
|
||||
fontSize: 12,
|
||||
};
|
||||
const directive2: MermaidConfig = {
|
||||
flowchart: {
|
||||
curve: 'basis',
|
||||
nodeSpacing: 100,
|
||||
},
|
||||
mindmap: {
|
||||
padding: 15,
|
||||
},
|
||||
};
|
||||
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
configApi.addDirective(directive1);
|
||||
configApi.addDirective(directive2);
|
||||
|
||||
const userConfig = configApi.getUserDefinedConfig();
|
||||
expect(userConfig).toMatchInlineSnapshot(`
|
||||
{
|
||||
"flowchart": {
|
||||
"curve": "basis",
|
||||
"nodeSpacing": 100,
|
||||
"rankSpacing": 100,
|
||||
},
|
||||
"fontSize": 12,
|
||||
"mindmap": {
|
||||
"padding": 15,
|
||||
},
|
||||
"theme": "default",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return independent copies (not references)', () => {
|
||||
const initConfig: MermaidConfig = { theme: 'dark', flowchart: { nodeSpacing: 50 } };
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
|
||||
const userConfig1 = configApi.getUserDefinedConfig();
|
||||
const userConfig2 = configApi.getUserDefinedConfig();
|
||||
|
||||
userConfig1.theme = 'neutral';
|
||||
userConfig1.flowchart!.nodeSpacing = 999;
|
||||
|
||||
expect(userConfig2).toMatchInlineSnapshot(`
|
||||
{
|
||||
"flowchart": {
|
||||
"nodeSpacing": 50,
|
||||
},
|
||||
"theme": "dark",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle edge cases with undefined values', () => {
|
||||
const initConfig: MermaidConfig = { theme: 'dark', layout: undefined };
|
||||
const directive: MermaidConfig = { fontSize: 14, fontFamily: undefined };
|
||||
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
configApi.addDirective(directive);
|
||||
|
||||
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fontSize": 14,
|
||||
"layout": undefined,
|
||||
"theme": "dark",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should retain config from initialize after reset', () => {
|
||||
const initConfig: MermaidConfig = { theme: 'dark' };
|
||||
const directive: MermaidConfig = { layout: 'elk' };
|
||||
|
||||
configApi.saveConfigFromInitialize(initConfig);
|
||||
configApi.addDirective(directive);
|
||||
|
||||
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"layout": "elk",
|
||||
"theme": "dark",
|
||||
}
|
||||
`);
|
||||
|
||||
configApi.reset();
|
||||
});
|
||||
});
|
||||
|
@@ -248,3 +248,17 @@ const checkConfig = (config: MermaidConfig) => {
|
||||
issueWarning('LAZY_LOAD_DEPRECATED');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserDefinedConfig = (): MermaidConfig => {
|
||||
let userConfig: MermaidConfig = {};
|
||||
|
||||
if (configFromInitialize) {
|
||||
userConfig = assignWithDepth(userConfig, configFromInitialize);
|
||||
}
|
||||
|
||||
for (const d of directives) {
|
||||
userConfig = assignWithDepth(userConfig, d);
|
||||
}
|
||||
|
||||
return userConfig;
|
||||
};
|
||||
|
@@ -28,6 +28,7 @@ import architecture from '../diagrams/architecture/architectureDetector.js';
|
||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||
import { registerDiagram } from './diagramAPI.js';
|
||||
import { treemap } from '../diagrams/treemap/detector.js';
|
||||
import usecase from '../diagrams/useCase/useCaseDetector.js';
|
||||
import '../type.d.ts';
|
||||
|
||||
let hasLoadedDiagrams = false;
|
||||
@@ -101,6 +102,7 @@ export const addDiagrams = () => {
|
||||
xychart,
|
||||
block,
|
||||
radar,
|
||||
treemap
|
||||
treemap,
|
||||
usecase
|
||||
);
|
||||
};
|
||||
|
@@ -1,91 +1,6 @@
|
||||
import { arc as d3arc, select } from 'd3';
|
||||
import { createText } from '../../rendering-util/createText.js';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const MAX_SECTIONS = 12;
|
||||
|
||||
/**
|
||||
* Process HTML content in node descriptions
|
||||
* @param {object} textElem - The SVG element to append text to
|
||||
* @param {object} node - The node object containing description and dimensions
|
||||
* @param {object} conf - Configuration object
|
||||
* @param {boolean} isVirtual - Whether this is for virtual height calculation
|
||||
*/
|
||||
const processHtmlContent = async function (textElem, node, conf, isVirtual = false) {
|
||||
// Create temporary text to get initial dimensions
|
||||
const sanitizedHtml = DOMPurify.sanitize(node.descr, { ALLOWED_TAGS: [] });
|
||||
const tempText = textElem
|
||||
.append('text')
|
||||
.text(sanitizedHtml)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
|
||||
if (!isVirtual) {
|
||||
tempText.attr('visibility', 'hidden');
|
||||
}
|
||||
|
||||
const bbox = tempText.node().getBBox();
|
||||
tempText.remove();
|
||||
|
||||
// Create the actual HTML content
|
||||
const textObj = await createText(
|
||||
textElem,
|
||||
node.descr,
|
||||
{
|
||||
useHtmlLabels: true,
|
||||
width: node.width,
|
||||
classes: 'timeline-node-label',
|
||||
isNode: true,
|
||||
},
|
||||
conf
|
||||
);
|
||||
|
||||
if (!isVirtual) {
|
||||
select(textObj).attr('transform', 'translate(0, 0)');
|
||||
}
|
||||
|
||||
// Process the foreign object
|
||||
const foreignObject = textElem.select('foreignObject');
|
||||
if (foreignObject.node()) {
|
||||
foreignObject.attr('width', `${10 * node.width}px`).attr('height', `${10 * node.width}px`);
|
||||
|
||||
const div = foreignObject.select('div');
|
||||
if (div.node()) {
|
||||
div
|
||||
.style('display', 'table-cell')
|
||||
.style('white-space', 'nowrap')
|
||||
.style('line-height', '1.5')
|
||||
.style('max-width', node.width + 'px')
|
||||
.style('text-align', 'center');
|
||||
|
||||
let divBBox = div.node().getBoundingClientRect();
|
||||
|
||||
if (divBBox.width === node.width) {
|
||||
div
|
||||
.style('display', 'table')
|
||||
.style('white-space', 'break-spaces')
|
||||
.style('width', node.width + 'px');
|
||||
|
||||
divBBox = div.node().getBoundingClientRect();
|
||||
}
|
||||
|
||||
foreignObject.attr('width', node.width).attr('height', divBBox.height);
|
||||
|
||||
if (!isVirtual) {
|
||||
foreignObject.attr('x', -node.width / 2).attr('y', 3);
|
||||
|
||||
div.style('width', node.width + 'px');
|
||||
}
|
||||
bbox.height = divBBox.height;
|
||||
}
|
||||
}
|
||||
|
||||
return bbox;
|
||||
};
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
@@ -494,9 +409,6 @@ const _drawTextCandidateFunc = (function () {
|
||||
.style('display', 'table-cell')
|
||||
.style('text-align', 'center')
|
||||
.style('vertical-align', 'middle')
|
||||
.style('word-wrap', 'break-word')
|
||||
.style('overflow-wrap', 'break-word')
|
||||
.style('white-space', 'normal')
|
||||
.text(content);
|
||||
|
||||
byTspan(content, body, x, y, width, height, textAttrs, conf);
|
||||
@@ -581,7 +493,7 @@ function wrap(text, width) {
|
||||
});
|
||||
}
|
||||
|
||||
export const drawNode = async function (elem, node, fullSection, conf) {
|
||||
export const drawNode = function (elem, node, fullSection, conf) {
|
||||
const section = (fullSection % MAX_SECTIONS) - 1;
|
||||
const nodeElem = elem.append('g');
|
||||
node.section = section;
|
||||
@@ -594,28 +506,19 @@ export const drawNode = async function (elem, node, fullSection, conf) {
|
||||
// Create the wrapped text element
|
||||
const textElem = nodeElem.append('g');
|
||||
|
||||
const hasHtml = /<[a-z][\S\s]*>/i.test(node.descr);
|
||||
|
||||
if (hasHtml) {
|
||||
const bbox = await processHtmlContent(textElem, node, conf, false);
|
||||
node.height = bbox.height + node.padding;
|
||||
node.height = Math.max(node.height, node.maxHeight);
|
||||
node.width = node.width + 2 * node.padding;
|
||||
} else {
|
||||
const txt = textElem
|
||||
.append('text')
|
||||
.text(node.descr)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
const bbox = txt.node().getBBox();
|
||||
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||
node.height = Math.max(node.height, node.maxHeight);
|
||||
node.width = node.width + 2 * node.padding;
|
||||
}
|
||||
const txt = textElem
|
||||
.append('text')
|
||||
.text(node.descr)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
const bbox = txt.node().getBBox();
|
||||
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||
node.height = Math.max(node.height, node.maxHeight);
|
||||
node.width = node.width + 2 * node.padding;
|
||||
|
||||
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
|
||||
|
||||
@@ -625,25 +528,17 @@ export const drawNode = async function (elem, node, fullSection, conf) {
|
||||
return node;
|
||||
};
|
||||
|
||||
export const getVirtualNodeHeight = async function (elem, node, conf) {
|
||||
export const getVirtualNodeHeight = function (elem, node, conf) {
|
||||
const textElem = elem.append('g');
|
||||
|
||||
const hasHtml = /<[a-z][\S\s]*>/i.test(node.descr);
|
||||
|
||||
let bbox;
|
||||
if (hasHtml) {
|
||||
bbox = await processHtmlContent(textElem, node, conf, true);
|
||||
} else {
|
||||
const txt = textElem
|
||||
.append('text')
|
||||
.text(node.descr)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
bbox = txt.node().getBBox();
|
||||
}
|
||||
const txt = textElem
|
||||
.append('text')
|
||||
.text(node.descr)
|
||||
.attr('dy', '1em')
|
||||
.attr('alignment-baseline', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'middle')
|
||||
.call(wrap, node.width);
|
||||
const bbox = txt.node().getBBox();
|
||||
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
|
||||
textElem.remove();
|
||||
return bbox.height + fontSize * 1.1 * 0.5 + node.padding;
|
||||
|
@@ -25,7 +25,7 @@ interface TimelineTask {
|
||||
score: number;
|
||||
events: string[];
|
||||
}
|
||||
export const draw = async function (text: string, id: string, version: string, diagObj: Diagram) {
|
||||
export const draw = function (text: string, id: string, version: string, diagObj: Diagram) {
|
||||
//1. Fetch the configuration
|
||||
const conf = getConfig();
|
||||
const LEFT_MARGIN = conf.timeline?.leftMargin ?? 50;
|
||||
@@ -76,7 +76,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
let hasSections = true;
|
||||
|
||||
//Calculate the max height of the sections
|
||||
for (const section of sections) {
|
||||
sections.forEach(function (section: string) {
|
||||
const sectionNode: Block<string, number> = {
|
||||
number: sectionNumber,
|
||||
descr: section,
|
||||
@@ -85,10 +85,10 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
padding: 20,
|
||||
maxHeight: maxSectionHeight,
|
||||
};
|
||||
const sectionHeight = await svgDraw.getVirtualNodeHeight(svg, sectionNode, conf);
|
||||
const sectionHeight = svgDraw.getVirtualNodeHeight(svg, sectionNode, conf);
|
||||
log.debug('sectionHeight before draw', sectionHeight);
|
||||
maxSectionHeight = Math.max(maxSectionHeight, sectionHeight + 20);
|
||||
}
|
||||
});
|
||||
|
||||
//tasks length and maxEventCount
|
||||
let maxEventCount = 0;
|
||||
@@ -106,7 +106,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
padding: 20,
|
||||
maxHeight: maxTaskHeight,
|
||||
};
|
||||
const taskHeight = await svgDraw.getVirtualNodeHeight(svg, taskNode, conf);
|
||||
const taskHeight = svgDraw.getVirtualNodeHeight(svg, taskNode, conf);
|
||||
log.debug('taskHeight before draw', taskHeight);
|
||||
maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20);
|
||||
|
||||
@@ -123,8 +123,9 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
padding: 20,
|
||||
maxHeight: 50,
|
||||
};
|
||||
maxEventLineLengthTemp += await svgDraw.getVirtualNodeHeight(svg, eventNode, conf);
|
||||
maxEventLineLengthTemp += svgDraw.getVirtualNodeHeight(svg, eventNode, conf);
|
||||
}
|
||||
// Add spacing between events (10px per event except the last one)
|
||||
if (task.events.length > 0) {
|
||||
maxEventLineLengthTemp += (task.events.length - 1) * 10;
|
||||
}
|
||||
@@ -135,7 +136,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
log.debug('maxTaskHeight before draw', maxTaskHeight);
|
||||
|
||||
if (sections && sections.length > 0) {
|
||||
for (const section of sections) {
|
||||
sections.forEach((section) => {
|
||||
//filter task where tasks.section == section
|
||||
const tasksForSection = tasks.filter((task) => task.section === section);
|
||||
|
||||
@@ -149,7 +150,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
};
|
||||
log.debug('sectionNode', sectionNode);
|
||||
const sectionNodeWrapper = svg.append('g');
|
||||
const node = await svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf);
|
||||
const node = svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf);
|
||||
log.debug('sectionNode output', node);
|
||||
|
||||
sectionNodeWrapper.attr('transform', `translate(${masterX}, ${sectionBeginY})`);
|
||||
@@ -158,7 +159,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
|
||||
//draw tasks for this section
|
||||
if (tasksForSection.length > 0) {
|
||||
await drawTasks(
|
||||
drawTasks(
|
||||
svg,
|
||||
tasksForSection,
|
||||
sectionNumber,
|
||||
@@ -177,11 +178,11 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
|
||||
masterY = sectionBeginY;
|
||||
sectionNumber++;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//draw tasks
|
||||
hasSections = false;
|
||||
await drawTasks(
|
||||
drawTasks(
|
||||
svg,
|
||||
tasks,
|
||||
sectionNumber,
|
||||
@@ -235,7 +236,7 @@ export const draw = async function (text: string, id: string, version: string, d
|
||||
// addSVGAccessibilityFields(diagObj.db, diagram, id);
|
||||
};
|
||||
|
||||
export const drawTasks = async function (
|
||||
export const drawTasks = function (
|
||||
diagram: Selection<SVGElement, unknown, null, undefined>,
|
||||
tasks: TimelineTask[],
|
||||
sectionColor: number,
|
||||
@@ -264,7 +265,7 @@ export const drawTasks = async function (
|
||||
// create task wrapper
|
||||
|
||||
const taskWrapper = diagram.append('g').attr('class', 'taskWrapper');
|
||||
const node = await svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf);
|
||||
const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf);
|
||||
const taskHeight = node.height;
|
||||
//log task height
|
||||
log.debug('taskHeight after draw', taskHeight);
|
||||
@@ -281,7 +282,7 @@ export const drawTasks = async function (
|
||||
//add margin to task
|
||||
masterY += 100;
|
||||
lineLength =
|
||||
lineLength + (await drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf));
|
||||
lineLength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf);
|
||||
masterY -= 100;
|
||||
|
||||
lineWrapper
|
||||
@@ -306,7 +307,7 @@ export const drawTasks = async function (
|
||||
masterY = masterY - 10;
|
||||
};
|
||||
|
||||
export const drawEvents = async function (
|
||||
export const drawEvents = function (
|
||||
diagram: Selection<SVGElement, unknown, null, undefined>,
|
||||
events: string[],
|
||||
sectionColor: number,
|
||||
@@ -333,7 +334,7 @@ export const drawEvents = async function (
|
||||
log.debug('eventNode', eventNode);
|
||||
// create event wrapper
|
||||
const eventWrapper = diagram.append('g').attr('class', 'eventWrapper');
|
||||
const node = await svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf);
|
||||
const node = svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf);
|
||||
const eventHeight = node.height;
|
||||
maxEventHeight = maxEventHeight + eventHeight;
|
||||
eventWrapper.attr('transform', `translate(${masterX}, ${masterY})`);
|
||||
|
124
packages/mermaid/src/diagrams/useCase/styles.js
Normal file
124
packages/mermaid/src/diagrams/useCase/styles.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const getStyles = (options) =>
|
||||
`
|
||||
.usecase-diagram {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: ${options.fontSize};
|
||||
}
|
||||
|
||||
/* Actor styles */
|
||||
.usecase-actor-man {
|
||||
stroke: ${options.actorBorder};
|
||||
fill: ${options.actorBkg};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.usecase-actor-man circle {
|
||||
fill: ${options.useCaseActorBkg};
|
||||
stroke: ${options.useCaseActorBorder};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.usecase-actor-man line {
|
||||
stroke: ${options.useCaseActorBorder};
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.usecase-actor-man text {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
fill: ${options.useCaseActorTextColor};
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
/* Use case styles */
|
||||
.usecase-usecase {
|
||||
fill: ${options.useCaseUseCaseBkg};
|
||||
stroke: ${options.useCaseUseCaseBorder};
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.usecase-usecase text {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: 12px;
|
||||
fill: ${options.useCaseUseCaseTextColor};
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
/* System boundary styles */
|
||||
.usecase-system-boundary {
|
||||
fill: ${options.useCaseSystemBoundaryBkg};
|
||||
stroke: ${options.useCaseSystemBoundaryBorder};
|
||||
stroke-width: 2px;
|
||||
stroke-dasharray: 5,5;
|
||||
}
|
||||
|
||||
.usecase-system-boundary text {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
fill: ${options.useCaseSystemBoundaryTextColor};
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
/* Arrow and relationship styles */
|
||||
.usecase-arrow {
|
||||
stroke: ${'red'};
|
||||
stroke-width: 2px;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.usecase-arrow-label {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: 12px;
|
||||
fill: ${options.useCaseArrowTextColor};
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
/* Node styles for standalone nodes */
|
||||
.usecase-node {
|
||||
fill: ${options.useCaseUseCaseBkg};
|
||||
stroke: ${options.useCaseUseCaseBorder};
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.usecase-node text {
|
||||
font-family: ${options.fontFamily};
|
||||
font-size: 12px;
|
||||
fill: ${options.useCaseUseCaseTextColor};
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
|
||||
/* Hover effects */
|
||||
.usecase-actor-man:hover circle {
|
||||
fill: ${options.useCaseActorBkg};
|
||||
stroke: ${options.useCaseArrowColor};
|
||||
}
|
||||
|
||||
.usecase-actor-man:hover line {
|
||||
stroke: ${options.useCaseArrowColor};
|
||||
}
|
||||
|
||||
.usecase-actor-man:hover text {
|
||||
fill: ${options.useCaseArrowColor};
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.usecase-usecase:hover {
|
||||
fill: ${options.useCaseSystemBoundaryBkg};
|
||||
stroke: ${options.useCaseArrowColor};
|
||||
}
|
||||
|
||||
.usecase-usecase:hover text {
|
||||
fill: ${options.useCaseArrowColor};
|
||||
font-weight: bold;
|
||||
}
|
||||
`;
|
||||
|
||||
export default getStyles;
|
586
packages/mermaid/src/diagrams/useCase/useCaseDb.ts
Normal file
586
packages/mermaid/src/diagrams/useCase/useCaseDb.ts
Normal file
@@ -0,0 +1,586 @@
|
||||
// Simple actor type for useCase diagrams
|
||||
interface Actor {
|
||||
type: 'actor';
|
||||
name: string;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
// Simple use case type
|
||||
interface UseCase {
|
||||
type: 'useCase';
|
||||
name: string;
|
||||
}
|
||||
|
||||
// System boundary type
|
||||
interface SystemBoundary {
|
||||
type: 'systemBoundary';
|
||||
name: string;
|
||||
useCases: UseCase[];
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
// System boundary metadata type
|
||||
interface SystemBoundaryMetadata {
|
||||
type: 'systemBoundaryMetadata';
|
||||
name: string; // boundary name
|
||||
metadata: Record<string, string>;
|
||||
}
|
||||
|
||||
// Actor-UseCase relationship type
|
||||
interface ActorUseCaseRelationship {
|
||||
type: 'actorUseCaseRelationship';
|
||||
from: string; // actor name
|
||||
to: string; // use case name
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
// Node type
|
||||
interface Node {
|
||||
type: 'node';
|
||||
id: string; // node ID (e.g., 'a', 'b', 'c')
|
||||
label: string; // node label (e.g., 'Go through code')
|
||||
}
|
||||
|
||||
// Actor-Node relationship type
|
||||
interface ActorNodeRelationship {
|
||||
type: 'actorNodeRelationship';
|
||||
from: string; // actor name
|
||||
to: string; // node ID
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
// Inline Actor-Node relationship type
|
||||
interface InlineActorNodeRelationship {
|
||||
type: 'inlineActorNodeRelationship';
|
||||
actor: string; // actor name
|
||||
node: Node; // node definition
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
export class UseCaseDB {
|
||||
private actors: Actor[] = [];
|
||||
private systemBoundaries: SystemBoundary[] = [];
|
||||
private systemBoundaryMetadata: SystemBoundaryMetadata[] = [];
|
||||
private useCases: UseCase[] = [];
|
||||
private relationships: ActorUseCaseRelationship[] = [];
|
||||
private nodes: Node[] = [];
|
||||
private nodeRelationships: ActorNodeRelationship[] = [];
|
||||
private inlineRelationships: InlineActorNodeRelationship[] = [];
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.actors = [];
|
||||
this.systemBoundaries = [];
|
||||
this.systemBoundaryMetadata = [];
|
||||
this.useCases = [];
|
||||
this.relationships = [];
|
||||
this.nodes = [];
|
||||
this.nodeRelationships = [];
|
||||
this.inlineRelationships = [];
|
||||
}
|
||||
|
||||
addActor(actor: Actor): void {
|
||||
this.actors.push(actor);
|
||||
}
|
||||
|
||||
addSystemBoundary(boundary: SystemBoundary): void {
|
||||
this.systemBoundaries.push(boundary);
|
||||
}
|
||||
|
||||
addSystemBoundaryMetadata(metadata: SystemBoundaryMetadata): void {
|
||||
this.systemBoundaryMetadata.push(metadata);
|
||||
// Apply metadata to existing system boundary
|
||||
const boundary = this.systemBoundaries.find(b => b.name === metadata.name);
|
||||
if (boundary) {
|
||||
boundary.metadata = metadata.metadata;
|
||||
}
|
||||
}
|
||||
|
||||
addUseCase(useCase: UseCase): void {
|
||||
this.useCases.push(useCase);
|
||||
}
|
||||
|
||||
addRelationship(relationship: ActorUseCaseRelationship): void {
|
||||
this.relationships.push(relationship);
|
||||
}
|
||||
|
||||
addNode(node: Node): void {
|
||||
this.nodes.push(node);
|
||||
}
|
||||
|
||||
addNodeRelationship(relationship: ActorNodeRelationship): void {
|
||||
this.nodeRelationships.push(relationship);
|
||||
}
|
||||
|
||||
addInlineRelationship(relationship: InlineActorNodeRelationship): void {
|
||||
this.inlineRelationships.push(relationship);
|
||||
// Also add the node and actor separately
|
||||
this.addNode(relationship.node);
|
||||
// Add actor if not already exists
|
||||
const actorExists = this.actors.some(actor => actor.name === relationship.actor);
|
||||
if (!actorExists) {
|
||||
this.addActor({
|
||||
type: 'actor',
|
||||
name: relationship.actor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getActors(): Actor[] {
|
||||
return this.actors;
|
||||
}
|
||||
|
||||
getSystemBoundaries(): SystemBoundary[] {
|
||||
return this.systemBoundaries;
|
||||
}
|
||||
|
||||
getSystemBoundaryMetadata(): SystemBoundaryMetadata[] {
|
||||
return this.systemBoundaryMetadata;
|
||||
}
|
||||
|
||||
getUseCases(): UseCase[] {
|
||||
return this.useCases;
|
||||
}
|
||||
|
||||
getRelationships(): ActorUseCaseRelationship[] {
|
||||
return this.relationships;
|
||||
}
|
||||
|
||||
getNodes(): Node[] {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
getNodeRelationships(): ActorNodeRelationship[] {
|
||||
return this.nodeRelationships;
|
||||
}
|
||||
|
||||
getInlineRelationships(): InlineActorNodeRelationship[] {
|
||||
return this.inlineRelationships;
|
||||
}
|
||||
|
||||
parse(text: string): void {
|
||||
this.clear();
|
||||
|
||||
// For now, use the simple parser with enhanced metadata support
|
||||
// TODO: Integrate ANTLR parser in the future
|
||||
|
||||
// Simple parser for usecase diagrams (fallback)
|
||||
const lines = text.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('%'));
|
||||
|
||||
let foundUsecase = false;
|
||||
let inSystemBoundary = false;
|
||||
let currentBoundary: SystemBoundary | null = null;
|
||||
let inMetadataBlock = false;
|
||||
let currentMetadataName = '';
|
||||
let currentMetadataContent = '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line === 'usecase') {
|
||||
foundUsecase = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!foundUsecase) {
|
||||
continue
|
||||
};
|
||||
|
||||
if (line.startsWith('actor ')) {
|
||||
const actorPart = line.substring(6).trim();
|
||||
if (actorPart) {
|
||||
// Check if this is an inline actor-node relationship
|
||||
if (this.isInlineActorNodeRelationshipLine(actorPart)) {
|
||||
const relationship = this.parseInlineActorNodeRelationshipLine(actorPart);
|
||||
if (relationship) {
|
||||
this.addInlineRelationship(relationship);
|
||||
}
|
||||
} else {
|
||||
const actors = this.parseActorList(actorPart);
|
||||
actors.forEach((actor: Actor) => this.addActor(actor));
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith('systemBoundary ')) {
|
||||
const boundaryPart = line.substring(15).trim();
|
||||
if (boundaryPart.endsWith(' {')) {
|
||||
// New curly brace syntax: systemBoundary Name {
|
||||
const boundaryName = boundaryPart.substring(0, boundaryPart.length - 2).trim();
|
||||
currentBoundary = {
|
||||
type: 'systemBoundary',
|
||||
name: boundaryName,
|
||||
useCases: []
|
||||
};
|
||||
inSystemBoundary = true;
|
||||
} else if (boundaryPart) {
|
||||
// Old syntax: systemBoundary Name (followed by 'end')
|
||||
currentBoundary = {
|
||||
type: 'systemBoundary',
|
||||
name: boundaryPart,
|
||||
useCases: []
|
||||
};
|
||||
inSystemBoundary = true;
|
||||
}
|
||||
} else if (line === 'end' || (line === '}' && !inMetadataBlock)) {
|
||||
if (inSystemBoundary && currentBoundary) {
|
||||
this.addSystemBoundary(currentBoundary);
|
||||
currentBoundary = null;
|
||||
inSystemBoundary = false;
|
||||
}
|
||||
} else if (inSystemBoundary && currentBoundary && line) {
|
||||
// This is a use case inside the system boundary
|
||||
const useCase: UseCase = {
|
||||
type: 'useCase',
|
||||
name: line
|
||||
};
|
||||
currentBoundary.useCases.push(useCase);
|
||||
} else if (line && !inSystemBoundary) {
|
||||
// Handle multi-line metadata blocks
|
||||
if (inMetadataBlock) {
|
||||
if (line.includes('}')) {
|
||||
// End of metadata block
|
||||
currentMetadataContent += line.replace('}', '').trim();
|
||||
const metadata = this.parseMetadataContent(currentMetadataName, currentMetadataContent);
|
||||
if (metadata) {
|
||||
this.addSystemBoundaryMetadata(metadata);
|
||||
}
|
||||
inMetadataBlock = false;
|
||||
currentMetadataName = '';
|
||||
currentMetadataContent = '';
|
||||
} else {
|
||||
// Continue collecting metadata content
|
||||
currentMetadataContent += line.trim() + ' ';
|
||||
}
|
||||
} else if (line.includes('@{')) {
|
||||
// Start of metadata block
|
||||
const match = line.match(/^(\w+)@\{(.*)$/);
|
||||
if (match) {
|
||||
currentMetadataName = match[1];
|
||||
const content = match[2].trim();
|
||||
if (content.includes('}')) {
|
||||
// Single line metadata
|
||||
const metadata = this.parseMetadataContent(currentMetadataName, content.replace('}', ''));
|
||||
if (metadata) {
|
||||
this.addSystemBoundaryMetadata(metadata);
|
||||
}
|
||||
} else {
|
||||
// Multi-line metadata
|
||||
inMetadataBlock = true;
|
||||
currentMetadataContent = content + ' ';
|
||||
}
|
||||
}
|
||||
} else if (this.isRelationshipLine(line)) {
|
||||
// Check if this is a relationship (actor --> usecase or actor --> node)
|
||||
const relationship = this.parseRelationshipLine(line);
|
||||
if (relationship) {
|
||||
if (relationship.type === 'actorUseCaseRelationship') {
|
||||
this.addRelationship(relationship);
|
||||
} else if (relationship.type === 'actorNodeRelationship') {
|
||||
this.addNodeRelationship(relationship);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a standalone use case
|
||||
const useCase: UseCase = {
|
||||
type: 'useCase',
|
||||
name: line
|
||||
};
|
||||
this.addUseCase(useCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private parseActorList(actorPart: string): Actor[] {
|
||||
// Smart split by comma that respects metadata braces
|
||||
const actorNames = this.smartSplitActors(actorPart);
|
||||
|
||||
return actorNames.map(actorName => this.parseActorWithMetadata(actorName));
|
||||
}
|
||||
|
||||
private smartSplitActors(input: string): string[] {
|
||||
const actors: string[] = [];
|
||||
let current = '';
|
||||
let braceDepth = 0;
|
||||
let inQuotes = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (const char of input) {
|
||||
|
||||
if (!inQuotes && (char === '"' || char === "'")) {
|
||||
inQuotes = true;
|
||||
quoteChar = char;
|
||||
current += char;
|
||||
} else if (inQuotes && char === quoteChar) {
|
||||
inQuotes = false;
|
||||
quoteChar = '';
|
||||
current += char;
|
||||
} else if (!inQuotes && char === '{') {
|
||||
braceDepth++;
|
||||
current += char;
|
||||
} else if (!inQuotes && char === '}') {
|
||||
braceDepth--;
|
||||
current += char;
|
||||
} else if (!inQuotes && char === ',' && braceDepth === 0) {
|
||||
// This is a real separator, not inside metadata
|
||||
if (current.trim()) {
|
||||
actors.push(current.trim());
|
||||
}
|
||||
current = '';
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last actor
|
||||
if (current.trim()) {
|
||||
actors.push(current.trim());
|
||||
}
|
||||
|
||||
return actors;
|
||||
}
|
||||
|
||||
private parseActorWithMetadata(actorPart: string): Actor {
|
||||
// Check if there's metadata (contains @{...})
|
||||
const metadataRegex = /^([^@]+)@{([^}]*)}$/;
|
||||
const metadataMatch = metadataRegex.exec(actorPart);
|
||||
|
||||
if (metadataMatch) {
|
||||
const name = metadataMatch[1].trim();
|
||||
const metadataStr = metadataMatch[2].trim();
|
||||
const metadata = this.parseMetadataString(metadataStr);
|
||||
|
||||
return {
|
||||
type: 'actor',
|
||||
name,
|
||||
metadata
|
||||
};
|
||||
} else {
|
||||
// No metadata, just return the name
|
||||
return {
|
||||
type: 'actor',
|
||||
name: actorPart
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private parseMetadataString(metadataStr: string): Record<string, string> {
|
||||
const metadata: Record<string, string> = {};
|
||||
|
||||
if (!metadataStr.trim()) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Split by comma and parse key-value pairs
|
||||
const pairs = metadataStr.split(',');
|
||||
|
||||
for (const pair of pairs) {
|
||||
const colonIndex = pair.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = pair.substring(0, colonIndex).trim();
|
||||
let value = pair.substring(colonIndex + 1).trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
metadata[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private isRelationshipLine(line: string): boolean {
|
||||
return line.includes('-->') || line.includes('->');
|
||||
}
|
||||
|
||||
private parseRelationshipLine(line: string): ActorUseCaseRelationship | ActorNodeRelationship | null {
|
||||
let arrow = '';
|
||||
let label: string | undefined;
|
||||
let parts: string[] = [];
|
||||
|
||||
// Check for labeled arrows first (--label--> or --label->)
|
||||
const labeledArrowMatch = line.match(/^(.+?)\s*(--\w+--?>)\s*(.+)$/);
|
||||
if (labeledArrowMatch) {
|
||||
parts = [labeledArrowMatch[1].trim(), labeledArrowMatch[3].trim()];
|
||||
arrow = labeledArrowMatch[2];
|
||||
// Extract label from arrow
|
||||
const labelMatch = arrow.match(/^--(\w+)--?>$/);
|
||||
if (labelMatch) {
|
||||
label = labelMatch[1];
|
||||
}
|
||||
} else if (line.includes('-->')) {
|
||||
arrow = '-->';
|
||||
parts = line.split('-->').map(part => part.trim());
|
||||
} else if (line.includes('->')) {
|
||||
arrow = '->';
|
||||
parts = line.split('->').map(part => part.trim());
|
||||
}
|
||||
|
||||
if (parts.length === 2 && parts[0] && parts[1]) {
|
||||
// Check if target is a node definition (contains parentheses)
|
||||
if (this.isNodeDefinitionString(parts[1])) {
|
||||
const node = this.parseNodeDefinitionString(parts[1]);
|
||||
if (node) {
|
||||
this.addNode(node);
|
||||
return {
|
||||
type: 'actorNodeRelationship',
|
||||
from: parts[0],
|
||||
to: node.id,
|
||||
arrow,
|
||||
label
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: 'actorUseCaseRelationship',
|
||||
from: parts[0],
|
||||
to: parts[1],
|
||||
arrow,
|
||||
label
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private isInlineActorNodeRelationshipLine(line: string): boolean {
|
||||
// Check for pattern: ActorName --> nodeId(label) or ActorName --label--> nodeId(label)
|
||||
const hasArrow = line.includes('-->') || line.includes('->') || !!line.match(/--\w+-->/);
|
||||
const hasNodeDefinition = line.includes('(') && line.includes(')');
|
||||
return hasArrow && hasNodeDefinition;
|
||||
}
|
||||
|
||||
private parseInlineActorNodeRelationshipLine(line: string): InlineActorNodeRelationship | null {
|
||||
let arrow = '';
|
||||
let label: string | undefined;
|
||||
let parts: string[] = [];
|
||||
|
||||
// Check for labeled arrows first (--label--> or --label->)
|
||||
const labeledArrowMatch = line.match(/^(.+?)\s*(--\w+--?>)\s*(.+)$/);
|
||||
if (labeledArrowMatch) {
|
||||
parts = [labeledArrowMatch[1].trim(), labeledArrowMatch[3].trim()];
|
||||
arrow = labeledArrowMatch[2];
|
||||
// Extract label from arrow
|
||||
const labelMatch = arrow.match(/^--(\w+)--?>$/);
|
||||
if (labelMatch) {
|
||||
label = labelMatch[1];
|
||||
}
|
||||
} else if (line.includes('-->')) {
|
||||
arrow = '-->';
|
||||
parts = line.split('-->').map(part => part.trim());
|
||||
} else if (line.includes('->')) {
|
||||
arrow = '->';
|
||||
parts = line.split('->').map(part => part.trim());
|
||||
}
|
||||
|
||||
if (parts.length === 2 && parts[0] && parts[1]) {
|
||||
const node = this.parseNodeDefinitionString(parts[1]);
|
||||
if (node) {
|
||||
return {
|
||||
type: 'inlineActorNodeRelationship',
|
||||
actor: parts[0],
|
||||
node,
|
||||
arrow,
|
||||
label
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private isNodeDefinitionString(str: string): boolean {
|
||||
return str.includes('(') && str.includes(')');
|
||||
}
|
||||
|
||||
private parseNodeDefinitionString(str: string): Node | null {
|
||||
const match = str.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.+)\)$/);
|
||||
if (match) {
|
||||
return {
|
||||
type: 'node',
|
||||
id: match[1],
|
||||
label: match[2]
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private isSystemBoundaryMetadataLine(line: string): boolean {
|
||||
// Check for pattern: boundaryName@{...}
|
||||
return line.includes('@{') && line.includes('}');
|
||||
}
|
||||
|
||||
private parseSystemBoundaryMetadataLine(line: string): SystemBoundaryMetadata | null {
|
||||
// Parse pattern: boundaryName@{key: value, key2: value2}
|
||||
const match = line.match(/^(\w+)@\{(.+)\}$/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = match[1];
|
||||
const metadataContent = match[2];
|
||||
const metadata: Record<string, string> = {};
|
||||
|
||||
// Parse key-value pairs
|
||||
const pairs = metadataContent.split(',').map(pair => pair.trim());
|
||||
for (const pair of pairs) {
|
||||
const colonIndex = pair.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = pair.substring(0, colonIndex).trim();
|
||||
let value = pair.substring(colonIndex + 1).trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
metadata[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'systemBoundaryMetadata',
|
||||
name,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
|
||||
private parseMetadataContent(name: string, content: string): SystemBoundaryMetadata | null {
|
||||
const metadata: Record<string, string> = {};
|
||||
|
||||
// Parse key-value pairs from content
|
||||
const pairs = content.split(',').map(pair => pair.trim()).filter(pair => pair);
|
||||
for (const pair of pairs) {
|
||||
const colonIndex = pair.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = pair.substring(0, colonIndex).trim();
|
||||
let value = pair.substring(colonIndex + 1).trim();
|
||||
|
||||
// Remove quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
metadata[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'systemBoundaryMetadata',
|
||||
name,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
}
|
24
packages/mermaid/src/diagrams/useCase/useCaseDetector.ts
Normal file
24
packages/mermaid/src/diagrams/useCase/useCaseDetector.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type {
|
||||
DiagramDetector,
|
||||
DiagramLoader,
|
||||
ExternalDiagramDefinition,
|
||||
} from '../../diagram-api/types.js';
|
||||
|
||||
const id = 'usecase';
|
||||
|
||||
const detector: DiagramDetector = (txt) => {
|
||||
return /^\s*usecase/.test(txt);
|
||||
};
|
||||
|
||||
const loader: DiagramLoader = async () => {
|
||||
const { diagram } = await import('./useCaseDiagram.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
1421
packages/mermaid/src/diagrams/useCase/useCaseDiagram.spec.js
Normal file
1421
packages/mermaid/src/diagrams/useCase/useCaseDiagram.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
33
packages/mermaid/src/diagrams/useCase/useCaseDiagram.ts
Normal file
33
packages/mermaid/src/diagrams/useCase/useCaseDiagram.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import { UseCaseDB } from './useCaseDb.js';
|
||||
import styles from './styles.js';
|
||||
import renderer from './useCaseRenderer.js';
|
||||
|
||||
// Shared database instance
|
||||
let db: UseCaseDB;
|
||||
|
||||
// Create a simple parser that integrates with our custom parser
|
||||
const parser = {
|
||||
parse: (text: string) => {
|
||||
// Use the shared database instance
|
||||
db.parse(text);
|
||||
},
|
||||
};
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
get db() {
|
||||
if (!db) {
|
||||
db = new UseCaseDB();
|
||||
}
|
||||
return db;
|
||||
},
|
||||
renderer,
|
||||
styles,
|
||||
init: (cnf) => {
|
||||
// Initialize configuration if needed
|
||||
if (!db) {
|
||||
db = new UseCaseDB();
|
||||
}
|
||||
},
|
||||
};
|
619
packages/mermaid/src/diagrams/useCase/useCaseRenderer.ts
Normal file
619
packages/mermaid/src/diagrams/useCase/useCaseRenderer.ts
Normal file
@@ -0,0 +1,619 @@
|
||||
import { select } from 'd3';
|
||||
import type { Diagram } from '../../Diagram.js';
|
||||
import type { UseCaseDB } from './useCaseDb.js';
|
||||
import { log } from '../../logger.js';
|
||||
|
||||
// Position interfaces
|
||||
interface NodePosition {
|
||||
name: string; // node ID (for relationship matching)
|
||||
label: string; // node label (for display)
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
// Constants for actor rendering
|
||||
const ACTOR_TYPE_WIDTH = 36; // 18 * 2 from sequence diagram
|
||||
const ACTOR_MAN_FIGURE_CLASS = 'usecase-actor-man';
|
||||
const ACTOR_SPACING = 120; // Horizontal spacing between actors
|
||||
const ACTOR_HEIGHT = 80; // Height of actor figure
|
||||
const MARGIN = 50; // Margin around the diagram
|
||||
|
||||
// Simple actor interface for positioning
|
||||
interface ActorPosition {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
// System boundary interface for positioning
|
||||
interface SystemBoundaryPosition {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
useCases: UseCasePosition[];
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
// Use case interface for positioning
|
||||
interface UseCasePosition {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a stick figure actor similar to sequence diagrams but optimized for useCase
|
||||
*/
|
||||
const drawActorTypeActor = (elem: any, actor: ActorPosition, conf: any): number => {
|
||||
const center = actor.x + actor.width / 2;
|
||||
const actorY = actor.y;
|
||||
|
||||
// Create actor group
|
||||
const actElem = elem.append('g');
|
||||
actElem.attr('class', ACTOR_MAN_FIGURE_CLASS);
|
||||
actElem.attr('name', actor.name);
|
||||
|
||||
// Draw stick figure
|
||||
// Head (circle)
|
||||
actElem
|
||||
.append('circle')
|
||||
.attr('cx', center)
|
||||
.attr('cy', actorY + 15)
|
||||
.attr('r', 10);
|
||||
|
||||
// Body (torso line)
|
||||
actElem
|
||||
.append('line')
|
||||
.attr('x1', center)
|
||||
.attr('y1', actorY + 25)
|
||||
.attr('x2', center)
|
||||
.attr('y2', actorY + 50)
|
||||
.style('stroke', 'black');
|
||||
|
||||
// Arms (horizontal line)
|
||||
actElem
|
||||
.append('line')
|
||||
.attr('x1', center - ACTOR_TYPE_WIDTH / 2)
|
||||
.attr('y1', actorY + 35)
|
||||
.attr('x2', center + ACTOR_TYPE_WIDTH / 2)
|
||||
.style('stroke', 'black')
|
||||
.attr('y2', actorY + 35);
|
||||
|
||||
// Left leg
|
||||
actElem
|
||||
.append('line')
|
||||
.attr('x1', center)
|
||||
.attr('y1', actorY + 50)
|
||||
.attr('x2', center - ACTOR_TYPE_WIDTH / 2)
|
||||
.style('stroke', 'black')
|
||||
.attr('y2', actorY + 70);
|
||||
|
||||
// Right leg
|
||||
actElem
|
||||
.append('line')
|
||||
.attr('x1', center)
|
||||
.attr('y1', actorY + 50)
|
||||
.attr('x2', center + ACTOR_TYPE_WIDTH / 2)
|
||||
.attr('y2', actorY + 70)
|
||||
.style('stroke', 'black');
|
||||
|
||||
// Actor name text
|
||||
const textY = actorY + ACTOR_HEIGHT + 15;
|
||||
drawActorText(actor.name, actElem, actor.x, textY, actor.width, 20);
|
||||
|
||||
return ACTOR_HEIGHT; // Total height including text and metadata
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws text for actor name - simplified version of sequence diagram text drawing
|
||||
*/
|
||||
const drawActorText = (content: string, g: any, x: number, y: number, width: number, height: number): void => {
|
||||
g.append('text')
|
||||
.attr('x', x + width / 2)
|
||||
.attr('y', y + height / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'central')
|
||||
.text(content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a system boundary box with use cases inside
|
||||
*/
|
||||
const drawSystemBoundary = (g: any, boundary: SystemBoundaryPosition, conf: any): void => {
|
||||
// Determine boundary type from metadata (default to 'rect')
|
||||
const boundaryType = boundary.metadata?.type || 'rect';
|
||||
|
||||
if (boundaryType === 'package') {
|
||||
// Draw package-style boundary with title box
|
||||
const titleHeight = 25;
|
||||
const titleWidth = Math.max(100, boundary.name.length * 8 + 20);
|
||||
|
||||
// Draw main boundary rectangle
|
||||
g.append('rect')
|
||||
.attr('x', boundary.x)
|
||||
.attr('y', boundary.y + titleHeight)
|
||||
.attr('width', boundary.width)
|
||||
.attr('height', boundary.height - titleHeight)
|
||||
.attr('class', 'usecase-system-boundary')
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#333')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Draw title box
|
||||
g.append('rect')
|
||||
.attr('x', boundary.x)
|
||||
.attr('y', boundary.y)
|
||||
.attr('width', titleWidth)
|
||||
.attr('height', titleHeight)
|
||||
.attr('class', 'usecase-system-boundary')
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#333')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Draw title text
|
||||
g.append('text')
|
||||
.attr('x', boundary.x + titleWidth / 2)
|
||||
.attr('y', boundary.y + titleHeight / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.style('font-size', '14px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('font-family', 'Arial, sans-serif')
|
||||
.style('fill', '#333')
|
||||
.text(boundary.name);
|
||||
} else {
|
||||
// Draw rect-style boundary (default)
|
||||
g.append('rect')
|
||||
.attr('x', boundary.x)
|
||||
.attr('y', boundary.y)
|
||||
.attr('width', boundary.width)
|
||||
.attr('height', boundary.height)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#333')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('stroke-dasharray', '5,5');
|
||||
|
||||
// Draw boundary title
|
||||
g.append('text')
|
||||
.attr('x', boundary.x + 10)
|
||||
.attr('y', boundary.y + 20)
|
||||
.style('font-size', '16px')
|
||||
.style('font-weight', 'bold')
|
||||
.style('font-family', 'Arial, sans-serif')
|
||||
.style('fill', '#333')
|
||||
.text(boundary.name);
|
||||
}
|
||||
|
||||
// Draw use cases inside the boundary
|
||||
boundary.useCases.forEach((useCase) => {
|
||||
// Draw use case oval
|
||||
g.append('ellipse')
|
||||
.attr('cx', useCase.x + useCase.width / 2)
|
||||
.attr('cy', useCase.y + useCase.height / 2)
|
||||
.attr('rx', useCase.width / 2)
|
||||
.attr('ry', useCase.height / 2)
|
||||
.attr('class', 'usecase-usecase')
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#333');
|
||||
|
||||
// Draw use case text
|
||||
g.append('text')
|
||||
.attr('x', useCase.x + useCase.width / 2)
|
||||
.attr('y', useCase.y + useCase.height / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'central')
|
||||
.text(useCase.name);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a standalone node as an oval
|
||||
*/
|
||||
const drawNode = (g: any, nodePos: NodePosition): void => {
|
||||
const nodeGroup = g.append('g').attr('class', `node-${nodePos.name}`);
|
||||
|
||||
// Draw oval background
|
||||
nodeGroup.append('ellipse')
|
||||
.attr('cx', nodePos.x + nodePos.width / 2)
|
||||
.attr('cy', nodePos.y + nodePos.height / 2)
|
||||
.attr('rx', nodePos.width / 2)
|
||||
.attr('ry', nodePos.height / 2)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#333')
|
||||
.attr('class', 'usecase-node');
|
||||
|
||||
// Add node label
|
||||
nodeGroup.append('text')
|
||||
.attr('x', nodePos.x + nodePos.width / 2)
|
||||
.attr('y', nodePos.y + nodePos.height / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.text(nodePos.label);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an arrow relationship between entities (actor-to-usecase or actor-to-actor)
|
||||
*/
|
||||
const drawRelationship = (g: any, relationship: any, actorPositions: ActorPosition[], boundaryPositions: SystemBoundaryPosition[], conf: any): void => {
|
||||
// Find the source entity (always an actor)
|
||||
const fromEntity = actorPositions.find(a => a.name === relationship.from);
|
||||
if (!fromEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the target entity (could be a use case or another actor)
|
||||
let toEntity: UseCasePosition | ActorPosition | undefined;
|
||||
let isTargetUseCase = false;
|
||||
|
||||
// First check if target is a use case in system boundaries
|
||||
for (const boundary of boundaryPositions) {
|
||||
toEntity = boundary.useCases.find(uc => uc.name === relationship.to);
|
||||
if (toEntity) {
|
||||
isTargetUseCase = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in boundaries, check if target is another actor
|
||||
toEntity ??= actorPositions.find(a => a.name === relationship.to);
|
||||
|
||||
if (!toEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate connection points
|
||||
const fromCenterX = fromEntity.x + fromEntity.width / 2;
|
||||
const fromCenterY = fromEntity.y + fromEntity.height / 2;
|
||||
|
||||
// For use cases, connect to the edge (left side), for actors connect to center
|
||||
const toCenterX = isTargetUseCase ? toEntity.x : toEntity.x + toEntity.width / 2;
|
||||
const toCenterY = isTargetUseCase ? toEntity.y + toEntity.height / 2 : toEntity.y + toEntity.height / 2;
|
||||
|
||||
// Draw arrow line
|
||||
g.append('line')
|
||||
.attr('x1', fromCenterX)
|
||||
.attr('y1', fromCenterY)
|
||||
.attr('x2', toCenterX)
|
||||
.attr('y2', toCenterY)
|
||||
.attr('class', 'usecase-arrow')
|
||||
.attr('stroke', '#333')
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
|
||||
// Add edge label if present
|
||||
if (relationship.label) {
|
||||
const midX = (fromCenterX + toCenterX) / 2;
|
||||
const midY = (fromCenterY + toCenterY) / 2;
|
||||
|
||||
g.append('text')
|
||||
.attr('x', midX)
|
||||
.attr('y', midY - 5)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('class', 'usecase-arrow-label')
|
||||
.attr('stroke', '#333')
|
||||
.attr('font-weight', 200)
|
||||
.text(relationship.label);
|
||||
}
|
||||
|
||||
// Add arrowhead marker definition if not already added
|
||||
const defs = g.select('defs').empty() ? g.append('defs') : g.select('defs');
|
||||
|
||||
if (defs.select('#arrowhead').empty()) {
|
||||
defs.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 3)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,0 L0,6 L9,3 z')
|
||||
.attr('fill', '#333');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an arrow relationship between an actor and a standalone node
|
||||
*/
|
||||
const drawNodeRelationship = (g: any, relationship: any, actorPositions: ActorPosition[], nodePositions: NodePosition[], conf: any): void => {
|
||||
// Find the actor position
|
||||
const actor = actorPositions.find(a => a.name === relationship.from);
|
||||
if (!actor) {return};
|
||||
|
||||
// Find the node position
|
||||
const node = nodePositions.find(n => n.name === relationship.to);
|
||||
if (!node) {return};
|
||||
|
||||
// Calculate connection points
|
||||
const actorCenterX = actor.x + actor.width / 2;
|
||||
const actorCenterY = actor.y + actor.height / 2;
|
||||
|
||||
// For nodes (which are like use cases), connect to the edge (left side)
|
||||
const nodeCenterX = node.x;
|
||||
const nodeCenterY = node.y + node.height / 2;
|
||||
|
||||
// Draw arrow line
|
||||
g.append('line')
|
||||
.attr('x1', actorCenterX)
|
||||
.attr('y1', actorCenterY)
|
||||
.attr('x2', nodeCenterX)
|
||||
.attr('y2', nodeCenterY)
|
||||
.attr('stroke', '#333')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
|
||||
// Add edge label if present
|
||||
if (relationship.label) {
|
||||
const midX = (actorCenterX + nodeCenterX) / 2;
|
||||
const midY = (actorCenterY + nodeCenterY) / 2;
|
||||
|
||||
g.append('text')
|
||||
.attr('x', midX)
|
||||
.attr('y', midY - 5)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('font-size', '12px')
|
||||
.attr('font-family', 'Arial, sans-serif')
|
||||
.attr('fill', '#333')
|
||||
.text(relationship.label);
|
||||
}
|
||||
|
||||
// Add arrowhead marker definition if not already added
|
||||
const defs = g.select('defs').empty() ? g.append('defs') : g.select('defs');
|
||||
|
||||
if (defs.select('#arrowhead').empty()) {
|
||||
defs.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 3)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,0 L0,6 L9,3 z')
|
||||
.attr('fill', '#333');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an arrow relationship from an inline actor-node definition
|
||||
*/
|
||||
const drawInlineRelationship = (g: any, relationship: any, actorPositions: ActorPosition[], nodePositions: NodePosition[], conf: any): void => {
|
||||
// Find the actor position
|
||||
const actor = actorPositions.find(a => a.name === relationship.actor);
|
||||
if (!actor) {return};
|
||||
|
||||
// Find the node position by node ID
|
||||
const node = nodePositions.find(n => n.name === relationship.node.id);
|
||||
if (!node) {return};
|
||||
|
||||
// Calculate connection points
|
||||
const actorCenterX = actor.x + actor.width / 2;
|
||||
const actorCenterY = actor.y + actor.height / 2;
|
||||
|
||||
// For nodes (which are like use cases), connect to the edge (left side)
|
||||
const nodeCenterX = node.x;
|
||||
const nodeCenterY = node.y + node.height / 2;
|
||||
|
||||
// Draw arrow line
|
||||
g.append('line')
|
||||
.attr('x1', actorCenterX)
|
||||
.attr('y1', actorCenterY)
|
||||
.attr('x2', nodeCenterX)
|
||||
.attr('y2', nodeCenterY)
|
||||
.attr('stroke', '#333')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
|
||||
// Add edge label if present
|
||||
if (relationship.label) {
|
||||
const midX = (actorCenterX + nodeCenterX) / 2;
|
||||
const midY = (actorCenterY + nodeCenterY) / 2;
|
||||
|
||||
g.append('text')
|
||||
.attr('x', midX)
|
||||
.attr('y', midY - 5)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('font-size', '12px')
|
||||
.attr('font-family', 'Arial, sans-serif')
|
||||
.attr('fill', '#333')
|
||||
.text(relationship.label);
|
||||
}
|
||||
|
||||
// Add arrowhead marker definition if not already added
|
||||
const defs = g.select('defs').empty() ? g.append('defs') : g.select('defs');
|
||||
|
||||
if (defs.select('#arrowhead').empty()) {
|
||||
defs.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
.attr('refY', 3)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,0 L0,6 L9,3 z')
|
||||
.attr('fill', '#333');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Main draw function for useCase diagrams
|
||||
*/
|
||||
const draw = (text: string, id: string, version: string, diagram: Diagram): void => {
|
||||
const db = diagram.db as UseCaseDB;
|
||||
|
||||
log.debug('Drawing useCase diagram', id);
|
||||
|
||||
const actors = db.getActors();
|
||||
const systemBoundaries = db.getSystemBoundaries();
|
||||
const useCases = db.getUseCases();
|
||||
const relationships = db.getRelationships();
|
||||
const nodes = db.getNodes();
|
||||
const nodeRelationships = db.getNodeRelationships();
|
||||
const inlineRelationships = db.getInlineRelationships();
|
||||
|
||||
// Create SVG container - use the same approach as other diagrams
|
||||
const svg = select(`[id="${id}"]`);
|
||||
svg.selectAll('*').remove();
|
||||
|
||||
if (actors.length === 0 && systemBoundaries.length === 0 && useCases.length === 0 && relationships.length === 0 && nodes.length === 0 && nodeRelationships.length === 0 && inlineRelationships.length === 0) {
|
||||
// Empty diagram
|
||||
svg.attr('width', 200);
|
||||
svg.attr('height', 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate layout
|
||||
let currentX = MARGIN;
|
||||
let currentY = MARGIN;
|
||||
let maxHeight = 0;
|
||||
|
||||
// Position actors
|
||||
const actorPositions: ActorPosition[] = actors.map((actor, index) => ({
|
||||
name: actor.name,
|
||||
x: currentX + index * ACTOR_SPACING,
|
||||
y: currentY,
|
||||
width: ACTOR_TYPE_WIDTH + 20, // Extra width for text
|
||||
height: ACTOR_HEIGHT,
|
||||
metadata: actor.metadata
|
||||
}));
|
||||
|
||||
if (actors.length > 0) {
|
||||
currentX += actors.length * ACTOR_SPACING;
|
||||
maxHeight = Math.max(maxHeight, ACTOR_HEIGHT + 50);
|
||||
}
|
||||
|
||||
// Position system boundaries
|
||||
const boundaryPositions: SystemBoundaryPosition[] = systemBoundaries.map((boundary, index) => {
|
||||
const boundaryWidth = Math.max(200, boundary.useCases.length * 120);
|
||||
const boundaryHeight = 150;
|
||||
|
||||
const position: SystemBoundaryPosition = {
|
||||
name: boundary.name,
|
||||
x: currentX + index * (boundaryWidth + 50),
|
||||
y: currentY,
|
||||
width: boundaryWidth,
|
||||
height: boundaryHeight,
|
||||
metadata: boundary.metadata,
|
||||
useCases: boundary.useCases.map((useCase, ucIndex) => ({
|
||||
name: useCase.name,
|
||||
x: currentX + index * (boundaryWidth + 50) + 20 + ucIndex * 100,
|
||||
y: currentY + 40,
|
||||
width: 80,
|
||||
height: 40
|
||||
}))
|
||||
};
|
||||
|
||||
return position;
|
||||
});
|
||||
|
||||
if (systemBoundaries.length > 0) {
|
||||
const totalBoundaryWidth = systemBoundaries.reduce((sum, boundary, index) => {
|
||||
const boundaryWidth = Math.max(200, boundary.useCases.length * 120);
|
||||
return sum + boundaryWidth + (index > 0 ? 50 : 0);
|
||||
}, 0);
|
||||
currentX += totalBoundaryWidth;
|
||||
maxHeight = Math.max(maxHeight, 150);
|
||||
}
|
||||
|
||||
// Position standalone nodes
|
||||
|
||||
const nodePositions: NodePosition[] = [];
|
||||
if (nodes.length > 0) {
|
||||
currentX += 50; // Add some spacing
|
||||
nodes.forEach((node, index) => {
|
||||
const nodeWidth = Math.max(100, node.label.length * 8);
|
||||
const nodeHeight = 40;
|
||||
|
||||
nodePositions.push({
|
||||
name: node.id,
|
||||
label: node.label,
|
||||
x: currentX,
|
||||
y: MARGIN + 50,
|
||||
width: nodeWidth,
|
||||
height: nodeHeight
|
||||
});
|
||||
|
||||
currentX += nodeWidth + 50;
|
||||
});
|
||||
maxHeight = Math.max(maxHeight, 90);
|
||||
}
|
||||
|
||||
// Create main group
|
||||
const g = svg.append('g').attr('class', 'usecase-diagram');
|
||||
|
||||
// Default configuration
|
||||
const conf = {
|
||||
actorFontSize: '14px',
|
||||
actorFontFamily: 'Arial, sans-serif',
|
||||
actorFontWeight: 'normal'
|
||||
};
|
||||
|
||||
// Draw all actors
|
||||
actorPositions.forEach((actorPos) => {
|
||||
const height = drawActorTypeActor(g, actorPos, conf);
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
});
|
||||
|
||||
// Draw system boundaries
|
||||
boundaryPositions.forEach((boundaryPos) => {
|
||||
drawSystemBoundary(g, boundaryPos, conf);
|
||||
});
|
||||
|
||||
// Draw standalone nodes
|
||||
nodePositions.forEach((nodePos) => {
|
||||
drawNode(g, nodePos);
|
||||
});
|
||||
|
||||
// Draw relationships (arrows)
|
||||
relationships.forEach((relationship) => {
|
||||
drawRelationship(g, relationship, actorPositions, boundaryPositions, conf);
|
||||
});
|
||||
|
||||
// Draw node relationships (arrows to standalone nodes)
|
||||
nodeRelationships.forEach((relationship) => {
|
||||
drawNodeRelationship(g, relationship, actorPositions, nodePositions, conf);
|
||||
});
|
||||
|
||||
// Draw inline relationships (from inline actor-node definitions)
|
||||
inlineRelationships.forEach((relationship) => {
|
||||
drawInlineRelationship(g, relationship, actorPositions, nodePositions, conf);
|
||||
});
|
||||
|
||||
// Calculate total dimensions
|
||||
let totalWidth = MARGIN;
|
||||
if (actors.length > 0) {
|
||||
totalWidth = Math.max(totalWidth, actorPositions[actorPositions.length - 1].x + actorPositions[actorPositions.length - 1].width + MARGIN);
|
||||
}
|
||||
if (systemBoundaries.length > 0) {
|
||||
totalWidth = Math.max(totalWidth, boundaryPositions[boundaryPositions.length - 1].x + boundaryPositions[boundaryPositions.length - 1].width + MARGIN);
|
||||
}
|
||||
if (nodePositions.length > 0) {
|
||||
totalWidth = Math.max(totalWidth, nodePositions[nodePositions.length - 1].x + nodePositions[nodePositions.length - 1].width + MARGIN);
|
||||
}
|
||||
|
||||
const totalHeight = MARGIN + maxHeight + MARGIN;
|
||||
|
||||
// Set SVG dimensions
|
||||
svg.attr('width', totalWidth);
|
||||
svg.attr('height', totalHeight);
|
||||
svg.attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`);
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@@ -31,7 +31,7 @@
|
||||
"fast-glob": "^3.3.3",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^2.0.3",
|
||||
"unocss": "^66.0.0",
|
||||
"unocss": "^66.4.2",
|
||||
"unplugin-vue-components": "^28.4.0",
|
||||
"vite": "^6.1.1",
|
||||
"vite-plugin-pwa": "^1.0.0",
|
||||
|
@@ -41,7 +41,6 @@ import { decodeEntities, encodeEntities } from './utils.js';
|
||||
import { toBase64 } from './utils/base64.js';
|
||||
import { StateDB } from './diagrams/state/stateDb.js';
|
||||
import { ensureNodeFromSelector, jsdomIt } from './tests/util.js';
|
||||
import { select } from 'd3';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
/**
|
||||
@@ -50,7 +49,6 @@ import { JSDOM } from 'jsdom';
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
describe('mermaidAPI', () => {
|
||||
describe('encodeEntities', () => {
|
||||
it('removes the ending ; from style [text1]:[optional word]#[text2]; with ', () => {
|
||||
@@ -913,4 +911,241 @@ graph TD;A--x|text including URL space|B;`)
|
||||
expect(sequenceDiagram1.db.getActors()).not.toEqual(sequenceDiagram2.db.getActors());
|
||||
});
|
||||
});
|
||||
|
||||
describe('mermaidAPI config precedence', () => {
|
||||
const id = 'mermaid-config-test';
|
||||
|
||||
beforeEach(() => {
|
||||
mermaidAPI.globalReset();
|
||||
});
|
||||
|
||||
jsdomIt('renders with YAML config taking precedence over initialize config', async () => {
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
fontFamily: 'Arial',
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
theme: base
|
||||
fontFamily: Courier
|
||||
themeVariables:
|
||||
fontFamily: "Courier New"
|
||||
fontSize: "20px"
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
const { svg } = await mermaidAPI.render('yaml-over-init', diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.theme).toBe('base');
|
||||
expect(config.fontFamily).toBe('Courier');
|
||||
expect(config.themeVariables.fontFamily).toBe('Courier New');
|
||||
expect(config.themeVariables.fontSize).toBe('20px');
|
||||
expect(config.flowchart?.htmlLabels).toBe(true);
|
||||
|
||||
const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document);
|
||||
expect(svgNode).not.toBeNull();
|
||||
});
|
||||
|
||||
jsdomIt(
|
||||
'renders with YAML themeVariables fully overriding initialize themeVariables',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
themeVariables:
|
||||
fontFamily: "Courier New"
|
||||
fontSize: "20px"
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
const { svg } = await mermaidAPI.render(id, diagramText);
|
||||
const config = mermaidAPI.getConfig();
|
||||
|
||||
expect(config.themeVariables.fontFamily).toBe('Courier New');
|
||||
expect(config.themeVariables.fontSize).toBe('20px');
|
||||
expect(config.themeVariables.fontFamily).not.toBe('Arial');
|
||||
expect(config.themeVariables.fontSize).not.toBe('16px');
|
||||
|
||||
const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document);
|
||||
expect(svgNode).not.toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
jsdomIt(
|
||||
'renders with YAML themeVariables overriding only provided keys and keeping others from initialize',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
fontFamily: 'Arial',
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px', colorPrimary: '#ff0000' },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
themeVariables:
|
||||
fontFamily: "Courier New"
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
const { svg } = await mermaidAPI.render(id, diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.themeVariables.fontFamily).toBe('Courier New');
|
||||
expect(config.themeVariables.fontSize).toBe('16px');
|
||||
expect(config.themeVariables.colorPrimary).toBe('#ff0000');
|
||||
|
||||
const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document);
|
||||
expect(svgNode).not.toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
jsdomIt(
|
||||
'renders with YAML config (no themeVariables) and falls back to initialize themeVariables',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
theme: base
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
const { svg } = await mermaidAPI.render(id, diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.themeVariables.fontFamily).toBe('Arial');
|
||||
expect(config.themeVariables.fontSize).toBe('16px');
|
||||
expect(config.theme).toBe('base');
|
||||
|
||||
const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document);
|
||||
expect(svgNode).not.toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
jsdomIt(
|
||||
'renders with full YAML config block taking full precedence over initialize config',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
fontFamily: 'Arial',
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
theme: base
|
||||
fontFamily: Courier
|
||||
themeVariables:
|
||||
fontFamily: "Courier New"
|
||||
fontSize: "20px"
|
||||
flowchart:
|
||||
htmlLabels: true
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
const { svg } = await mermaidAPI.render('yaml-over-init', diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.theme).toBe('base');
|
||||
expect(config.fontFamily).toBe('Courier');
|
||||
expect(config.themeVariables.fontFamily).toBe('Courier New');
|
||||
expect(config.themeVariables.fontSize).toBe('20px');
|
||||
expect(config.flowchart?.htmlLabels).toBe(true);
|
||||
|
||||
const svgNode = ensureNodeFromSelector('svg', new JSDOM(svg).window.document);
|
||||
expect(svgNode).not.toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
jsdomIt(
|
||||
'renders with YAML config (no themeVariables) and falls back to initialize themeVariables (duplicate scenario)',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config:
|
||||
theme: base
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
await mermaidAPI.render(id, diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.themeVariables.fontFamily).toBe('Arial');
|
||||
expect(config.themeVariables.fontSize).toBe('16px');
|
||||
expect(config.theme).toBe('base');
|
||||
}
|
||||
);
|
||||
|
||||
jsdomIt('renders with no YAML config so initialize config is fully applied', async () => {
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
fontFamily: 'Arial',
|
||||
themeVariables: { fontFamily: 'Arial', fontSize: '16px' },
|
||||
});
|
||||
|
||||
const diagramText = `
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
await mermaidAPI.render(id, diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.theme).toBe('forest');
|
||||
expect(config.fontFamily).toBe('Arial');
|
||||
expect(config.themeVariables.fontFamily).toBe('Arial');
|
||||
expect(config.themeVariables.fontSize).toBe('16px');
|
||||
});
|
||||
|
||||
jsdomIt(
|
||||
'renders with empty YAML config block and falls back to initialize config',
|
||||
async () => {
|
||||
mermaid.initialize({
|
||||
theme: 'dark',
|
||||
themeVariables: { fontFamily: 'Times', fontSize: '14px' },
|
||||
});
|
||||
|
||||
const diagramText = `---
|
||||
config: {}
|
||||
---
|
||||
flowchart TD
|
||||
A --> B
|
||||
`;
|
||||
|
||||
await mermaidAPI.render(id, diagramText);
|
||||
|
||||
const config = mermaidAPI.getConfig();
|
||||
expect(config.theme).toBe('dark');
|
||||
expect(config.themeVariables.fontFamily).toBe('Times');
|
||||
expect(config.themeVariables.fontSize).toBe('14px');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -19,7 +19,9 @@
|
||||
"scripts": {
|
||||
"clean": "rimraf dist src/language/generated",
|
||||
"langium:generate": "langium generate",
|
||||
"langium:watch": "langium generate --watch"
|
||||
"langium:watch": "langium generate --watch",
|
||||
"antlr:generate": "antlr4ts -visitor -listener -o src/language/useCase/generated src/language/useCase/Usecase.g4",
|
||||
"generate": "npm run langium:generate && npm run antlr:generate"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,6 +35,8 @@
|
||||
"ast"
|
||||
],
|
||||
"dependencies": {
|
||||
"antlr4ts": "0.5.0-alpha.4",
|
||||
"antlr4ts-cli": "0.5.0-alpha.4",
|
||||
"langium": "3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -45,3 +45,4 @@ export * from './pie/index.js';
|
||||
export * from './architecture/index.js';
|
||||
export * from './radar/index.js';
|
||||
export * from './treemap/index.js';
|
||||
export * from './useCase/index.js';
|
||||
|
70
packages/parser/src/language/useCase/.antlr/Usecase.interp
Normal file
70
packages/parser/src/language/useCase/.antlr/Usecase.interp
Normal file
File diff suppressed because one or more lines are too long
29
packages/parser/src/language/useCase/.antlr/Usecase.tokens
Normal file
29
packages/parser/src/language/useCase/.antlr/Usecase.tokens
Normal file
@@ -0,0 +1,29 @@
|
||||
USECASE_START=1
|
||||
ACTOR=2
|
||||
SYSTEM_BOUNDARY=3
|
||||
END=4
|
||||
ARROW=5
|
||||
LABELED_ARROW=6
|
||||
AT=7
|
||||
LBRACE=8
|
||||
RBRACE=9
|
||||
LPAREN=10
|
||||
RPAREN=11
|
||||
COMMA=12
|
||||
COLON=13
|
||||
STRING=14
|
||||
IDENTIFIER=15
|
||||
NEWLINE=16
|
||||
WS=17
|
||||
COMMENT=18
|
||||
'usecase'=1
|
||||
'actor'=2
|
||||
'systemBoundary'=3
|
||||
'end'=4
|
||||
'@'=7
|
||||
'{'=8
|
||||
'}'=9
|
||||
'('=10
|
||||
')'=11
|
||||
','=12
|
||||
':'=13
|
@@ -0,0 +1,71 @@
|
||||
token literal names:
|
||||
null
|
||||
'usecase'
|
||||
'actor'
|
||||
'systemBoundary'
|
||||
'end'
|
||||
null
|
||||
null
|
||||
'@'
|
||||
'{'
|
||||
'}'
|
||||
'('
|
||||
')'
|
||||
','
|
||||
':'
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
USECASE_START
|
||||
ACTOR
|
||||
SYSTEM_BOUNDARY
|
||||
END
|
||||
ARROW
|
||||
LABELED_ARROW
|
||||
AT
|
||||
LBRACE
|
||||
RBRACE
|
||||
LPAREN
|
||||
RPAREN
|
||||
COMMA
|
||||
COLON
|
||||
STRING
|
||||
IDENTIFIER
|
||||
NEWLINE
|
||||
WS
|
||||
COMMENT
|
||||
|
||||
rule names:
|
||||
USECASE_START
|
||||
ACTOR
|
||||
SYSTEM_BOUNDARY
|
||||
END
|
||||
ARROW
|
||||
LABELED_ARROW
|
||||
AT
|
||||
LBRACE
|
||||
RBRACE
|
||||
LPAREN
|
||||
RPAREN
|
||||
COMMA
|
||||
COLON
|
||||
STRING
|
||||
IDENTIFIER
|
||||
NEWLINE
|
||||
WS
|
||||
COMMENT
|
||||
|
||||
channel names:
|
||||
DEFAULT_TOKEN_CHANNEL
|
||||
HIDDEN
|
||||
|
||||
mode names:
|
||||
DEFAULT_MODE
|
||||
|
||||
atn:
|
||||
[4, 0, 18, 154, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 76, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 93, 8, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 111, 8, 13, 10, 13, 12, 13, 114, 9, 13, 1, 13, 1, 13, 1, 13, 5, 13, 119, 8, 13, 10, 13, 12, 13, 122, 9, 13, 1, 13, 3, 13, 125, 8, 13, 1, 14, 1, 14, 5, 14, 129, 8, 14, 10, 14, 12, 14, 132, 9, 14, 1, 15, 4, 15, 135, 8, 15, 11, 15, 12, 15, 136, 1, 16, 4, 16, 140, 8, 16, 11, 16, 12, 16, 141, 1, 16, 1, 16, 1, 17, 1, 17, 5, 17, 148, 8, 17, 10, 17, 12, 17, 151, 9, 17, 1, 17, 1, 17, 0, 0, 18, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 1, 0, 6, 3, 0, 10, 10, 13, 13, 34, 34, 3, 0, 10, 10, 13, 13, 39, 39, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 162, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 1, 37, 1, 0, 0, 0, 3, 45, 1, 0, 0, 0, 5, 51, 1, 0, 0, 0, 7, 66, 1, 0, 0, 0, 9, 75, 1, 0, 0, 0, 11, 92, 1, 0, 0, 0, 13, 94, 1, 0, 0, 0, 15, 96, 1, 0, 0, 0, 17, 98, 1, 0, 0, 0, 19, 100, 1, 0, 0, 0, 21, 102, 1, 0, 0, 0, 23, 104, 1, 0, 0, 0, 25, 106, 1, 0, 0, 0, 27, 124, 1, 0, 0, 0, 29, 126, 1, 0, 0, 0, 31, 134, 1, 0, 0, 0, 33, 139, 1, 0, 0, 0, 35, 145, 1, 0, 0, 0, 37, 38, 5, 117, 0, 0, 38, 39, 5, 115, 0, 0, 39, 40, 5, 101, 0, 0, 40, 41, 5, 99, 0, 0, 41, 42, 5, 97, 0, 0, 42, 43, 5, 115, 0, 0, 43, 44, 5, 101, 0, 0, 44, 2, 1, 0, 0, 0, 45, 46, 5, 97, 0, 0, 46, 47, 5, 99, 0, 0, 47, 48, 5, 116, 0, 0, 48, 49, 5, 111, 0, 0, 49, 50, 5, 114, 0, 0, 50, 4, 1, 0, 0, 0, 51, 52, 5, 115, 0, 0, 52, 53, 5, 121, 0, 0, 53, 54, 5, 115, 0, 0, 54, 55, 5, 116, 0, 0, 55, 56, 5, 101, 0, 0, 56, 57, 5, 109, 0, 0, 57, 58, 5, 66, 0, 0, 58, 59, 5, 111, 0, 0, 59, 60, 5, 117, 0, 0, 60, 61, 5, 110, 0, 0, 61, 62, 5, 100, 0, 0, 62, 63, 5, 97, 0, 0, 63, 64, 5, 114, 0, 0, 64, 65, 5, 121, 0, 0, 65, 6, 1, 0, 0, 0, 66, 67, 5, 101, 0, 0, 67, 68, 5, 110, 0, 0, 68, 69, 5, 100, 0, 0, 69, 8, 1, 0, 0, 0, 70, 71, 5, 45, 0, 0, 71, 72, 5, 45, 0, 0, 72, 76, 5, 62, 0, 0, 73, 74, 5, 45, 0, 0, 74, 76, 5, 62, 0, 0, 75, 70, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 76, 10, 1, 0, 0, 0, 77, 78, 5, 45, 0, 0, 78, 79, 5, 45, 0, 0, 79, 80, 1, 0, 0, 0, 80, 81, 3, 29, 14, 0, 81, 82, 5, 45, 0, 0, 82, 83, 5, 45, 0, 0, 83, 84, 5, 62, 0, 0, 84, 93, 1, 0, 0, 0, 85, 86, 5, 45, 0, 0, 86, 87, 5, 45, 0, 0, 87, 88, 1, 0, 0, 0, 88, 89, 3, 29, 14, 0, 89, 90, 5, 45, 0, 0, 90, 91, 5, 62, 0, 0, 91, 93, 1, 0, 0, 0, 92, 77, 1, 0, 0, 0, 92, 85, 1, 0, 0, 0, 93, 12, 1, 0, 0, 0, 94, 95, 5, 64, 0, 0, 95, 14, 1, 0, 0, 0, 96, 97, 5, 123, 0, 0, 97, 16, 1, 0, 0, 0, 98, 99, 5, 125, 0, 0, 99, 18, 1, 0, 0, 0, 100, 101, 5, 40, 0, 0, 101, 20, 1, 0, 0, 0, 102, 103, 5, 41, 0, 0, 103, 22, 1, 0, 0, 0, 104, 105, 5, 44, 0, 0, 105, 24, 1, 0, 0, 0, 106, 107, 5, 58, 0, 0, 107, 26, 1, 0, 0, 0, 108, 112, 5, 34, 0, 0, 109, 111, 8, 0, 0, 0, 110, 109, 1, 0, 0, 0, 111, 114, 1, 0, 0, 0, 112, 110, 1, 0, 0, 0, 112, 113, 1, 0, 0, 0, 113, 115, 1, 0, 0, 0, 114, 112, 1, 0, 0, 0, 115, 125, 5, 34, 0, 0, 116, 120, 5, 39, 0, 0, 117, 119, 8, 1, 0, 0, 118, 117, 1, 0, 0, 0, 119, 122, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 120, 121, 1, 0, 0, 0, 121, 123, 1, 0, 0, 0, 122, 120, 1, 0, 0, 0, 123, 125, 5, 39, 0, 0, 124, 108, 1, 0, 0, 0, 124, 116, 1, 0, 0, 0, 125, 28, 1, 0, 0, 0, 126, 130, 7, 2, 0, 0, 127, 129, 7, 3, 0, 0, 128, 127, 1, 0, 0, 0, 129, 132, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 130, 131, 1, 0, 0, 0, 131, 30, 1, 0, 0, 0, 132, 130, 1, 0, 0, 0, 133, 135, 7, 4, 0, 0, 134, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 134, 1, 0, 0, 0, 136, 137, 1, 0, 0, 0, 137, 32, 1, 0, 0, 0, 138, 140, 7, 5, 0, 0, 139, 138, 1, 0, 0, 0, 140, 141, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 141, 142, 1, 0, 0, 0, 142, 143, 1, 0, 0, 0, 143, 144, 6, 16, 0, 0, 144, 34, 1, 0, 0, 0, 145, 149, 5, 37, 0, 0, 146, 148, 8, 4, 0, 0, 147, 146, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, 149, 150, 1, 0, 0, 0, 150, 152, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 152, 153, 6, 17, 0, 0, 153, 36, 1, 0, 0, 0, 10, 0, 75, 92, 112, 120, 124, 130, 136, 141, 149, 1, 6, 0, 0]
|
213
packages/parser/src/language/useCase/.antlr/UsecaseLexer.java
Normal file
213
packages/parser/src/language/useCase/.antlr/UsecaseLexer.java
Normal file
@@ -0,0 +1,213 @@
|
||||
// Generated from /home/omkar-kadam/Public/mermaid/mermaid/packages/parser/src/language/useCase/Usecase.g4 by ANTLR 4.13.1
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
|
||||
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "this-escape"})
|
||||
public class UsecaseLexer extends Lexer {
|
||||
static { RuntimeMetaData.checkVersion("4.13.1", RuntimeMetaData.VERSION); }
|
||||
|
||||
protected static final DFA[] _decisionToDFA;
|
||||
protected static final PredictionContextCache _sharedContextCache =
|
||||
new PredictionContextCache();
|
||||
public static final int
|
||||
USECASE_START=1, ACTOR=2, SYSTEM_BOUNDARY=3, END=4, ARROW=5, LABELED_ARROW=6,
|
||||
AT=7, LBRACE=8, RBRACE=9, LPAREN=10, RPAREN=11, COMMA=12, COLON=13, STRING=14,
|
||||
IDENTIFIER=15, NEWLINE=16, WS=17, COMMENT=18;
|
||||
public static String[] channelNames = {
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
};
|
||||
|
||||
public static String[] modeNames = {
|
||||
"DEFAULT_MODE"
|
||||
};
|
||||
|
||||
private static String[] makeRuleNames() {
|
||||
return new String[] {
|
||||
"USECASE_START", "ACTOR", "SYSTEM_BOUNDARY", "END", "ARROW", "LABELED_ARROW",
|
||||
"AT", "LBRACE", "RBRACE", "LPAREN", "RPAREN", "COMMA", "COLON", "STRING",
|
||||
"IDENTIFIER", "NEWLINE", "WS", "COMMENT"
|
||||
};
|
||||
}
|
||||
public static final String[] ruleNames = makeRuleNames();
|
||||
|
||||
private static String[] makeLiteralNames() {
|
||||
return new String[] {
|
||||
null, "'usecase'", "'actor'", "'systemBoundary'", "'end'", null, null,
|
||||
"'@'", "'{'", "'}'", "'('", "')'", "','", "':'"
|
||||
};
|
||||
}
|
||||
private static final String[] _LITERAL_NAMES = makeLiteralNames();
|
||||
private static String[] makeSymbolicNames() {
|
||||
return new String[] {
|
||||
null, "USECASE_START", "ACTOR", "SYSTEM_BOUNDARY", "END", "ARROW", "LABELED_ARROW",
|
||||
"AT", "LBRACE", "RBRACE", "LPAREN", "RPAREN", "COMMA", "COLON", "STRING",
|
||||
"IDENTIFIER", "NEWLINE", "WS", "COMMENT"
|
||||
};
|
||||
}
|
||||
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
|
||||
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #VOCABULARY} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String[] tokenNames;
|
||||
static {
|
||||
tokenNames = new String[_SYMBOLIC_NAMES.length];
|
||||
for (int i = 0; i < tokenNames.length; i++) {
|
||||
tokenNames[i] = VOCABULARY.getLiteralName(i);
|
||||
if (tokenNames[i] == null) {
|
||||
tokenNames[i] = VOCABULARY.getSymbolicName(i);
|
||||
}
|
||||
|
||||
if (tokenNames[i] == null) {
|
||||
tokenNames[i] = "<INVALID>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public String[] getTokenNames() {
|
||||
return tokenNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Vocabulary getVocabulary() {
|
||||
return VOCABULARY;
|
||||
}
|
||||
|
||||
|
||||
public UsecaseLexer(CharStream input) {
|
||||
super(input);
|
||||
_interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGrammarFileName() { return "Usecase.g4"; }
|
||||
|
||||
@Override
|
||||
public String[] getRuleNames() { return ruleNames; }
|
||||
|
||||
@Override
|
||||
public String getSerializedATN() { return _serializedATN; }
|
||||
|
||||
@Override
|
||||
public String[] getChannelNames() { return channelNames; }
|
||||
|
||||
@Override
|
||||
public String[] getModeNames() { return modeNames; }
|
||||
|
||||
@Override
|
||||
public ATN getATN() { return _ATN; }
|
||||
|
||||
public static final String _serializedATN =
|
||||
"\u0004\u0000\u0012\u009a\u0006\uffff\uffff\u0002\u0000\u0007\u0000\u0002"+
|
||||
"\u0001\u0007\u0001\u0002\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002"+
|
||||
"\u0004\u0007\u0004\u0002\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002"+
|
||||
"\u0007\u0007\u0007\u0002\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002"+
|
||||
"\u000b\u0007\u000b\u0002\f\u0007\f\u0002\r\u0007\r\u0002\u000e\u0007\u000e"+
|
||||
"\u0002\u000f\u0007\u000f\u0002\u0010\u0007\u0010\u0002\u0011\u0007\u0011"+
|
||||
"\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000"+
|
||||
"\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+
|
||||
"\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002"+
|
||||
"\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002"+
|
||||
"\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003"+
|
||||
"\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001\u0004\u0001\u0004"+
|
||||
"\u0001\u0004\u0001\u0004\u0003\u0004L\b\u0004\u0001\u0005\u0001\u0005"+
|
||||
"\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+
|
||||
"\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+
|
||||
"\u0001\u0005\u0003\u0005]\b\u0005\u0001\u0006\u0001\u0006\u0001\u0007"+
|
||||
"\u0001\u0007\u0001\b\u0001\b\u0001\t\u0001\t\u0001\n\u0001\n\u0001\u000b"+
|
||||
"\u0001\u000b\u0001\f\u0001\f\u0001\r\u0001\r\u0005\ro\b\r\n\r\f\rr\t\r"+
|
||||
"\u0001\r\u0001\r\u0001\r\u0005\rw\b\r\n\r\f\rz\t\r\u0001\r\u0003\r}\b"+
|
||||
"\r\u0001\u000e\u0001\u000e\u0005\u000e\u0081\b\u000e\n\u000e\f\u000e\u0084"+
|
||||
"\t\u000e\u0001\u000f\u0004\u000f\u0087\b\u000f\u000b\u000f\f\u000f\u0088"+
|
||||
"\u0001\u0010\u0004\u0010\u008c\b\u0010\u000b\u0010\f\u0010\u008d\u0001"+
|
||||
"\u0010\u0001\u0010\u0001\u0011\u0001\u0011\u0005\u0011\u0094\b\u0011\n"+
|
||||
"\u0011\f\u0011\u0097\t\u0011\u0001\u0011\u0001\u0011\u0000\u0000\u0012"+
|
||||
"\u0001\u0001\u0003\u0002\u0005\u0003\u0007\u0004\t\u0005\u000b\u0006\r"+
|
||||
"\u0007\u000f\b\u0011\t\u0013\n\u0015\u000b\u0017\f\u0019\r\u001b\u000e"+
|
||||
"\u001d\u000f\u001f\u0010!\u0011#\u0012\u0001\u0000\u0006\u0003\u0000\n"+
|
||||
"\n\r\r\"\"\u0003\u0000\n\n\r\r\'\'\u0003\u0000AZ__az\u0004\u000009AZ_"+
|
||||
"_az\u0002\u0000\n\n\r\r\u0002\u0000\t\t \u00a2\u0000\u0001\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u0003\u0001\u0000\u0000\u0000\u0000\u0005\u0001\u0000"+
|
||||
"\u0000\u0000\u0000\u0007\u0001\u0000\u0000\u0000\u0000\t\u0001\u0000\u0000"+
|
||||
"\u0000\u0000\u000b\u0001\u0000\u0000\u0000\u0000\r\u0001\u0000\u0000\u0000"+
|
||||
"\u0000\u000f\u0001\u0000\u0000\u0000\u0000\u0011\u0001\u0000\u0000\u0000"+
|
||||
"\u0000\u0013\u0001\u0000\u0000\u0000\u0000\u0015\u0001\u0000\u0000\u0000"+
|
||||
"\u0000\u0017\u0001\u0000\u0000\u0000\u0000\u0019\u0001\u0000\u0000\u0000"+
|
||||
"\u0000\u001b\u0001\u0000\u0000\u0000\u0000\u001d\u0001\u0000\u0000\u0000"+
|
||||
"\u0000\u001f\u0001\u0000\u0000\u0000\u0000!\u0001\u0000\u0000\u0000\u0000"+
|
||||
"#\u0001\u0000\u0000\u0000\u0001%\u0001\u0000\u0000\u0000\u0003-\u0001"+
|
||||
"\u0000\u0000\u0000\u00053\u0001\u0000\u0000\u0000\u0007B\u0001\u0000\u0000"+
|
||||
"\u0000\tK\u0001\u0000\u0000\u0000\u000b\\\u0001\u0000\u0000\u0000\r^\u0001"+
|
||||
"\u0000\u0000\u0000\u000f`\u0001\u0000\u0000\u0000\u0011b\u0001\u0000\u0000"+
|
||||
"\u0000\u0013d\u0001\u0000\u0000\u0000\u0015f\u0001\u0000\u0000\u0000\u0017"+
|
||||
"h\u0001\u0000\u0000\u0000\u0019j\u0001\u0000\u0000\u0000\u001b|\u0001"+
|
||||
"\u0000\u0000\u0000\u001d~\u0001\u0000\u0000\u0000\u001f\u0086\u0001\u0000"+
|
||||
"\u0000\u0000!\u008b\u0001\u0000\u0000\u0000#\u0091\u0001\u0000\u0000\u0000"+
|
||||
"%&\u0005u\u0000\u0000&\'\u0005s\u0000\u0000\'(\u0005e\u0000\u0000()\u0005"+
|
||||
"c\u0000\u0000)*\u0005a\u0000\u0000*+\u0005s\u0000\u0000+,\u0005e\u0000"+
|
||||
"\u0000,\u0002\u0001\u0000\u0000\u0000-.\u0005a\u0000\u0000./\u0005c\u0000"+
|
||||
"\u0000/0\u0005t\u0000\u000001\u0005o\u0000\u000012\u0005r\u0000\u0000"+
|
||||
"2\u0004\u0001\u0000\u0000\u000034\u0005s\u0000\u000045\u0005y\u0000\u0000"+
|
||||
"56\u0005s\u0000\u000067\u0005t\u0000\u000078\u0005e\u0000\u000089\u0005"+
|
||||
"m\u0000\u00009:\u0005B\u0000\u0000:;\u0005o\u0000\u0000;<\u0005u\u0000"+
|
||||
"\u0000<=\u0005n\u0000\u0000=>\u0005d\u0000\u0000>?\u0005a\u0000\u0000"+
|
||||
"?@\u0005r\u0000\u0000@A\u0005y\u0000\u0000A\u0006\u0001\u0000\u0000\u0000"+
|
||||
"BC\u0005e\u0000\u0000CD\u0005n\u0000\u0000DE\u0005d\u0000\u0000E\b\u0001"+
|
||||
"\u0000\u0000\u0000FG\u0005-\u0000\u0000GH\u0005-\u0000\u0000HL\u0005>"+
|
||||
"\u0000\u0000IJ\u0005-\u0000\u0000JL\u0005>\u0000\u0000KF\u0001\u0000\u0000"+
|
||||
"\u0000KI\u0001\u0000\u0000\u0000L\n\u0001\u0000\u0000\u0000MN\u0005-\u0000"+
|
||||
"\u0000NO\u0005-\u0000\u0000OP\u0001\u0000\u0000\u0000PQ\u0003\u001d\u000e"+
|
||||
"\u0000QR\u0005-\u0000\u0000RS\u0005-\u0000\u0000ST\u0005>\u0000\u0000"+
|
||||
"T]\u0001\u0000\u0000\u0000UV\u0005-\u0000\u0000VW\u0005-\u0000\u0000W"+
|
||||
"X\u0001\u0000\u0000\u0000XY\u0003\u001d\u000e\u0000YZ\u0005-\u0000\u0000"+
|
||||
"Z[\u0005>\u0000\u0000[]\u0001\u0000\u0000\u0000\\M\u0001\u0000\u0000\u0000"+
|
||||
"\\U\u0001\u0000\u0000\u0000]\f\u0001\u0000\u0000\u0000^_\u0005@\u0000"+
|
||||
"\u0000_\u000e\u0001\u0000\u0000\u0000`a\u0005{\u0000\u0000a\u0010\u0001"+
|
||||
"\u0000\u0000\u0000bc\u0005}\u0000\u0000c\u0012\u0001\u0000\u0000\u0000"+
|
||||
"de\u0005(\u0000\u0000e\u0014\u0001\u0000\u0000\u0000fg\u0005)\u0000\u0000"+
|
||||
"g\u0016\u0001\u0000\u0000\u0000hi\u0005,\u0000\u0000i\u0018\u0001\u0000"+
|
||||
"\u0000\u0000jk\u0005:\u0000\u0000k\u001a\u0001\u0000\u0000\u0000lp\u0005"+
|
||||
"\"\u0000\u0000mo\b\u0000\u0000\u0000nm\u0001\u0000\u0000\u0000or\u0001"+
|
||||
"\u0000\u0000\u0000pn\u0001\u0000\u0000\u0000pq\u0001\u0000\u0000\u0000"+
|
||||
"qs\u0001\u0000\u0000\u0000rp\u0001\u0000\u0000\u0000s}\u0005\"\u0000\u0000"+
|
||||
"tx\u0005\'\u0000\u0000uw\b\u0001\u0000\u0000vu\u0001\u0000\u0000\u0000"+
|
||||
"wz\u0001\u0000\u0000\u0000xv\u0001\u0000\u0000\u0000xy\u0001\u0000\u0000"+
|
||||
"\u0000y{\u0001\u0000\u0000\u0000zx\u0001\u0000\u0000\u0000{}\u0005\'\u0000"+
|
||||
"\u0000|l\u0001\u0000\u0000\u0000|t\u0001\u0000\u0000\u0000}\u001c\u0001"+
|
||||
"\u0000\u0000\u0000~\u0082\u0007\u0002\u0000\u0000\u007f\u0081\u0007\u0003"+
|
||||
"\u0000\u0000\u0080\u007f\u0001\u0000\u0000\u0000\u0081\u0084\u0001\u0000"+
|
||||
"\u0000\u0000\u0082\u0080\u0001\u0000\u0000\u0000\u0082\u0083\u0001\u0000"+
|
||||
"\u0000\u0000\u0083\u001e\u0001\u0000\u0000\u0000\u0084\u0082\u0001\u0000"+
|
||||
"\u0000\u0000\u0085\u0087\u0007\u0004\u0000\u0000\u0086\u0085\u0001\u0000"+
|
||||
"\u0000\u0000\u0087\u0088\u0001\u0000\u0000\u0000\u0088\u0086\u0001\u0000"+
|
||||
"\u0000\u0000\u0088\u0089\u0001\u0000\u0000\u0000\u0089 \u0001\u0000\u0000"+
|
||||
"\u0000\u008a\u008c\u0007\u0005\u0000\u0000\u008b\u008a\u0001\u0000\u0000"+
|
||||
"\u0000\u008c\u008d\u0001\u0000\u0000\u0000\u008d\u008b\u0001\u0000\u0000"+
|
||||
"\u0000\u008d\u008e\u0001\u0000\u0000\u0000\u008e\u008f\u0001\u0000\u0000"+
|
||||
"\u0000\u008f\u0090\u0006\u0010\u0000\u0000\u0090\"\u0001\u0000\u0000\u0000"+
|
||||
"\u0091\u0095\u0005%\u0000\u0000\u0092\u0094\b\u0004\u0000\u0000\u0093"+
|
||||
"\u0092\u0001\u0000\u0000\u0000\u0094\u0097\u0001\u0000\u0000\u0000\u0095"+
|
||||
"\u0093\u0001\u0000\u0000\u0000\u0095\u0096\u0001\u0000\u0000\u0000\u0096"+
|
||||
"\u0098\u0001\u0000\u0000\u0000\u0097\u0095\u0001\u0000\u0000\u0000\u0098"+
|
||||
"\u0099\u0006\u0011\u0000\u0000\u0099$\u0001\u0000\u0000\u0000\n\u0000"+
|
||||
"K\\px|\u0082\u0088\u008d\u0095\u0001\u0006\u0000\u0000";
|
||||
public static final ATN _ATN =
|
||||
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
|
||||
static {
|
||||
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
|
||||
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
|
||||
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
USECASE_START=1
|
||||
ACTOR=2
|
||||
SYSTEM_BOUNDARY=3
|
||||
END=4
|
||||
ARROW=5
|
||||
LABELED_ARROW=6
|
||||
AT=7
|
||||
LBRACE=8
|
||||
RBRACE=9
|
||||
LPAREN=10
|
||||
RPAREN=11
|
||||
COMMA=12
|
||||
COLON=13
|
||||
STRING=14
|
||||
IDENTIFIER=15
|
||||
NEWLINE=16
|
||||
WS=17
|
||||
COMMENT=18
|
||||
'usecase'=1
|
||||
'actor'=2
|
||||
'systemBoundary'=3
|
||||
'end'=4
|
||||
'@'=7
|
||||
'{'=8
|
||||
'}'=9
|
||||
'('=10
|
||||
')'=11
|
||||
','=12
|
||||
':'=13
|
1574
packages/parser/src/language/useCase/.antlr/UsecaseParser.java
Normal file
1574
packages/parser/src/language/useCase/.antlr/UsecaseParser.java
Normal file
File diff suppressed because it is too large
Load Diff
184
packages/parser/src/language/useCase/Usecase.g4
Normal file
184
packages/parser/src/language/useCase/Usecase.g4
Normal file
@@ -0,0 +1,184 @@
|
||||
grammar Usecase;
|
||||
|
||||
// Parser rules
|
||||
usecaseDiagram
|
||||
: USECASE_START NEWLINE* statement* EOF
|
||||
;
|
||||
|
||||
statement
|
||||
: actor NEWLINE*
|
||||
| systemBoundary NEWLINE*
|
||||
| systemBoundaryMetadata NEWLINE*
|
||||
| useCase NEWLINE*
|
||||
| relationship NEWLINE*
|
||||
| actorRelationship NEWLINE*
|
||||
| NEWLINE
|
||||
;
|
||||
|
||||
relationship
|
||||
: actorName ARROW target
|
||||
| actorName LABELED_ARROW target
|
||||
;
|
||||
|
||||
actorRelationship
|
||||
: ACTOR actorName ARROW target
|
||||
| ACTOR actorName LABELED_ARROW target
|
||||
;
|
||||
|
||||
target
|
||||
: useCaseName
|
||||
| nodeDefinition
|
||||
;
|
||||
|
||||
nodeDefinition
|
||||
: nodeId LPAREN nodeLabel RPAREN
|
||||
;
|
||||
|
||||
nodeId
|
||||
: IDENTIFIER
|
||||
;
|
||||
|
||||
nodeLabel
|
||||
: IDENTIFIER (WS IDENTIFIER)*
|
||||
| STRING
|
||||
;
|
||||
|
||||
actorName
|
||||
: IDENTIFIER
|
||||
;
|
||||
|
||||
systemBoundary
|
||||
: SYSTEM_BOUNDARY boundaryName LBRACE NEWLINE* boundaryContent* RBRACE
|
||||
| SYSTEM_BOUNDARY boundaryName NEWLINE* boundaryContent* END
|
||||
;
|
||||
|
||||
systemBoundaryMetadata
|
||||
: boundaryName AT LBRACE metadataContent RBRACE
|
||||
;
|
||||
|
||||
boundaryContent
|
||||
: useCase NEWLINE*
|
||||
| NEWLINE
|
||||
;
|
||||
|
||||
useCase
|
||||
: useCaseName
|
||||
;
|
||||
|
||||
boundaryName
|
||||
: IDENTIFIER
|
||||
;
|
||||
|
||||
useCaseName
|
||||
: IDENTIFIER
|
||||
;
|
||||
|
||||
actor
|
||||
: ACTOR actorList
|
||||
;
|
||||
|
||||
actorList
|
||||
: actorDefinition (COMMA actorDefinition)*
|
||||
;
|
||||
|
||||
actorDefinition
|
||||
: actorName metadata?
|
||||
;
|
||||
|
||||
metadata
|
||||
: AT LBRACE metadataContent RBRACE
|
||||
;
|
||||
|
||||
metadataContent
|
||||
: metadataPair (COMMA metadataPair)*
|
||||
|
|
||||
;
|
||||
|
||||
metadataPair
|
||||
: metadataKey COLON metadataValue
|
||||
;
|
||||
|
||||
metadataKey
|
||||
: IDENTIFIER
|
||||
;
|
||||
|
||||
metadataValue
|
||||
: STRING
|
||||
| IDENTIFIER
|
||||
;
|
||||
|
||||
// Lexer rules
|
||||
USECASE_START
|
||||
: 'usecase'
|
||||
;
|
||||
|
||||
ACTOR
|
||||
: 'actor'
|
||||
;
|
||||
|
||||
SYSTEM_BOUNDARY
|
||||
: 'systemBoundary'
|
||||
;
|
||||
|
||||
END
|
||||
: 'end'
|
||||
;
|
||||
|
||||
ARROW
|
||||
: '-->'
|
||||
| '->'
|
||||
;
|
||||
|
||||
LABELED_ARROW
|
||||
: '--' IDENTIFIER '-->'
|
||||
| '--' IDENTIFIER '->'
|
||||
;
|
||||
|
||||
AT
|
||||
: '@'
|
||||
;
|
||||
|
||||
LBRACE
|
||||
: '{'
|
||||
;
|
||||
|
||||
RBRACE
|
||||
: '}'
|
||||
;
|
||||
|
||||
LPAREN
|
||||
: '('
|
||||
;
|
||||
|
||||
RPAREN
|
||||
: ')'
|
||||
;
|
||||
|
||||
COMMA
|
||||
: ','
|
||||
;
|
||||
|
||||
COLON
|
||||
: ':'
|
||||
;
|
||||
|
||||
STRING
|
||||
: '"' (~["\r\n])* '"'
|
||||
| '\'' (~['\r\n])* '\''
|
||||
;
|
||||
|
||||
IDENTIFIER
|
||||
: [a-zA-Z_][a-zA-Z0-9_]*
|
||||
;
|
||||
|
||||
NEWLINE
|
||||
: [\r\n]+
|
||||
;
|
||||
|
||||
WS
|
||||
: [ \t]+ -> skip
|
||||
;
|
||||
|
||||
COMMENT
|
||||
: '%' ~[\r\n]* -> skip
|
||||
;
|
2
packages/parser/src/language/useCase/index.ts
Normal file
2
packages/parser/src/language/useCase/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { parseUsecase } from './usecaseParser.js';
|
||||
export * from './usecaseTypes.js';
|
387
packages/parser/src/language/useCase/test.ts
Normal file
387
packages/parser/src/language/useCase/test.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
import { parseUsecase } from './usecaseParser.js';
|
||||
|
||||
// Test basic usecase diagram parsing
|
||||
function testBasicUsecaseParsing() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
actor Developer2
|
||||
actor Developer3`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Basic Usecase Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test simple usecase diagram
|
||||
function testSimpleUsecaseParsing() {
|
||||
const input = `usecase
|
||||
actor User
|
||||
actor Admin`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Simple Usecase Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test metadata parsing
|
||||
function testMetadataParsing() {
|
||||
const input = `usecase
|
||||
actor Developer1@{ icon : 'icon_name', place: "sample place" }`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Metadata Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test complex metadata parsing
|
||||
function testComplexMetadataParsing() {
|
||||
const input = `usecase
|
||||
actor Developer1@{ icon : 'icon_name', type : 'hollow', place: "sample place", material:"sample" }`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Complex Metadata Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test mixed actors (with and without metadata)
|
||||
function testMixedActorsParsing() {
|
||||
const input = `usecase
|
||||
actor User
|
||||
actor Developer1@{ icon : 'dev_icon' }
|
||||
actor Admin@{ type: 'admin', place: "office" }`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Mixed Actors Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test multiple actors in single line
|
||||
function testMultipleActorsSingleLine() {
|
||||
const input = `usecase
|
||||
actor Developer1, Developer2, Developer3`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Multiple Actors Single Line:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test multiple actors with metadata
|
||||
function testMultipleActorsWithMetadata() {
|
||||
const input = `usecase
|
||||
actor Developer1@{ icon: 'dev' }, Developer2, Developer3@{ type: 'admin' }`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Multiple Actors With Metadata:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test five actors in single line
|
||||
function testFiveActorsSingleLine() {
|
||||
const input = `usecase
|
||||
actor Developer1, Developer2, Developer3, Developer4, Developer5`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Five Actors Single Line:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test system boundary parsing
|
||||
function testSystemBoundaryParsing() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
systemBoundary Tasks
|
||||
coding
|
||||
testing
|
||||
deploying
|
||||
end`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test System Boundary Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test mixed actors and system boundaries
|
||||
function testMixedActorsAndBoundaries() {
|
||||
const input = `usecase
|
||||
actor Developer1, Developer2
|
||||
systemBoundary Tasks
|
||||
coding
|
||||
testing
|
||||
end
|
||||
actor Admin`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Mixed Actors and Boundaries:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test curly brace system boundary parsing
|
||||
function testCurlyBraceSystemBoundary() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
systemBoundary Tasks {
|
||||
playing
|
||||
reviewing
|
||||
}`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Curly Brace System Boundary:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test relationship parsing
|
||||
function testRelationshipParsing() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
systemBoundary Tasks {
|
||||
playing
|
||||
reviewing
|
||||
}
|
||||
Developer1 --> playing
|
||||
Developer1 --> reviewing`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Relationship Parsing:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test complete example
|
||||
function testCompleteExample() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
systemBoundary Tasks {
|
||||
playing
|
||||
reviewing
|
||||
}
|
||||
Developer1 --> playing
|
||||
Developer1 --> reviewing`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Complete Example:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test node definitions
|
||||
function testNodeDefinitions() {
|
||||
const input = `usecase
|
||||
actor Tester1
|
||||
Tester1 --> c(Go through testing)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Node Definitions:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test inline actor-node relationships
|
||||
function testInlineActorNodeRelationships() {
|
||||
const input = `usecase
|
||||
actor Developer1 --> a(Go through code)
|
||||
actor Developer2 --> b(Go through implementation)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Inline Actor-Node Relationships:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test mixed syntax
|
||||
function testMixedSyntax() {
|
||||
const input = `usecase
|
||||
actor Tester1
|
||||
Tester1 --> c(Go through testing)
|
||||
actor Developer1 --> a(Go through code)
|
||||
actor Developer2 --> b(Go through implementation)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Mixed Syntax:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test edge labels
|
||||
function testEdgeLabels() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
Developer1 --task2--> c(Go through testing)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Edge Labels:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test edge labels with inline syntax
|
||||
function testInlineEdgeLabels() {
|
||||
const input = `usecase
|
||||
actor Developer1 --task1--> a(Go through code)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Inline Edge Labels:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Test mixed edge labels and regular arrows
|
||||
function testMixedEdgeLabels() {
|
||||
const input = `usecase
|
||||
actor Developer1
|
||||
actor Tester1
|
||||
Developer1 --task1--> a(Go through code)
|
||||
Tester1 --> b(Go through testing)`;
|
||||
|
||||
const result = parseUsecase(input);
|
||||
console.log('Test Mixed Edge Labels:');
|
||||
console.log('Success:', result.success);
|
||||
if (result.success && result.ast) {
|
||||
console.log('Statements:', result.ast.statements.length);
|
||||
console.log('AST:', JSON.stringify(result.ast, null, 2));
|
||||
} else {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
console.log('---');
|
||||
}
|
||||
|
||||
// Run tests
|
||||
console.log('Running Usecase Parser Tests...\n');
|
||||
testBasicUsecaseParsing();
|
||||
testSimpleUsecaseParsing();
|
||||
testMetadataParsing();
|
||||
testComplexMetadataParsing();
|
||||
testMixedActorsParsing();
|
||||
testMultipleActorsSingleLine();
|
||||
testMultipleActorsWithMetadata();
|
||||
testFiveActorsSingleLine();
|
||||
testSystemBoundaryParsing();
|
||||
testMixedActorsAndBoundaries();
|
||||
testCurlyBraceSystemBoundary();
|
||||
testRelationshipParsing();
|
||||
testCompleteExample();
|
||||
testNodeDefinitions();
|
||||
testInlineActorNodeRelationships();
|
||||
testMixedSyntax();
|
||||
testEdgeLabels();
|
||||
testInlineEdgeLabels();
|
||||
testMixedEdgeLabels();
|
||||
console.log('Tests completed.');
|
752
packages/parser/src/language/useCase/usecaseParser.ts
Normal file
752
packages/parser/src/language/useCase/usecaseParser.ts
Normal file
@@ -0,0 +1,752 @@
|
||||
// Simple tokenizer and parser for usecase diagrams
|
||||
// This approach is more compatible with the mermaid build system
|
||||
import type {
|
||||
UsecaseDiagram,
|
||||
Statement,
|
||||
Actor,
|
||||
Usecase,
|
||||
SystemBoundary,
|
||||
SystemBoundaryMetadata,
|
||||
ActorUseCaseRelationship,
|
||||
Node,
|
||||
ActorNodeRelationship,
|
||||
InlineActorNodeRelationship,
|
||||
ParseResult
|
||||
} from './usecaseTypes.js';
|
||||
|
||||
// Token types
|
||||
enum TokenType {
|
||||
USECASE_START = 'USECASE_START',
|
||||
ACTOR = 'ACTOR',
|
||||
SYSTEM_BOUNDARY = 'SYSTEM_BOUNDARY',
|
||||
END = 'END',
|
||||
ARROW = 'ARROW',
|
||||
LABELED_ARROW = 'LABELED_ARROW',
|
||||
AT = 'AT',
|
||||
LBRACE = 'LBRACE',
|
||||
RBRACE = 'RBRACE',
|
||||
LPAREN = 'LPAREN',
|
||||
RPAREN = 'RPAREN',
|
||||
COMMA = 'COMMA',
|
||||
COLON = 'COLON',
|
||||
STRING = 'STRING',
|
||||
IDENTIFIER = 'IDENTIFIER',
|
||||
NEWLINE = 'NEWLINE',
|
||||
EOF = 'EOF'
|
||||
}
|
||||
|
||||
interface Token {
|
||||
type: TokenType;
|
||||
value: string;
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
class UsecaseLexer {
|
||||
private input: string;
|
||||
private position: number = 0;
|
||||
private line: number = 1;
|
||||
private column: number = 1;
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
tokenize(): Token[] {
|
||||
const tokens: Token[] = [];
|
||||
|
||||
while (this.position < this.input.length) {
|
||||
this.skipWhitespace();
|
||||
|
||||
if (this.position >= this.input.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
const token = this.nextToken();
|
||||
if (token) {
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push({
|
||||
type: TokenType.EOF,
|
||||
value: '',
|
||||
line: this.line,
|
||||
column: this.column
|
||||
});
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private nextToken(): Token | null {
|
||||
const startLine = this.line;
|
||||
const startColumn = this.column;
|
||||
|
||||
// Skip comments
|
||||
if (this.peek() === '%') {
|
||||
this.skipComment();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Newlines
|
||||
if (this.peek() === '\n' || this.peek() === '\r') {
|
||||
this.advance();
|
||||
if (this.peek() === '\n') {
|
||||
this.advance();
|
||||
}
|
||||
this.line++;
|
||||
this.column = 1;
|
||||
return {
|
||||
type: TokenType.NEWLINE,
|
||||
value: '\n',
|
||||
line: startLine,
|
||||
column: startColumn
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Strings
|
||||
if (this.peek() === '"' || this.peek() === "'") {
|
||||
return this.readString(startLine, startColumn);
|
||||
}
|
||||
|
||||
// Arrow tokens (-->, ->, --label-->, --label->)
|
||||
if (this.peek() === '-') {
|
||||
if (this.peek(1) === '-') {
|
||||
// Check for labeled arrow: --label--> or --label->
|
||||
const labeledArrowMatch = this.tryParseLabeledArrow();
|
||||
if (labeledArrowMatch) {
|
||||
return labeledArrowMatch;
|
||||
}
|
||||
|
||||
// Regular arrow: -->
|
||||
if (this.peek(2) === '>') {
|
||||
this.advance(3);
|
||||
return { type: TokenType.ARROW, value: '-->', line: startLine, column: startColumn };
|
||||
}
|
||||
} else if (this.peek(1) === '>') {
|
||||
// Regular arrow: ->
|
||||
this.advance(2);
|
||||
return { type: TokenType.ARROW, value: '->', line: startLine, column: startColumn };
|
||||
}
|
||||
}
|
||||
|
||||
// Single character tokens
|
||||
switch (this.peek()) {
|
||||
case '@':
|
||||
this.advance();
|
||||
return { type: TokenType.AT, value: '@', line: startLine, column: startColumn };
|
||||
case '{':
|
||||
this.advance();
|
||||
return { type: TokenType.LBRACE, value: '{', line: startLine, column: startColumn };
|
||||
case '}':
|
||||
this.advance();
|
||||
return { type: TokenType.RBRACE, value: '}', line: startLine, column: startColumn };
|
||||
case ',':
|
||||
this.advance();
|
||||
return { type: TokenType.COMMA, value: ',', line: startLine, column: startColumn };
|
||||
case ':':
|
||||
this.advance();
|
||||
return { type: TokenType.COLON, value: ':', line: startLine, column: startColumn };
|
||||
case '(':
|
||||
this.advance();
|
||||
return { type: TokenType.LPAREN, value: '(', line: startLine, column: startColumn };
|
||||
case ')':
|
||||
this.advance();
|
||||
return { type: TokenType.RPAREN, value: ')', line: startLine, column: startColumn };
|
||||
}
|
||||
|
||||
// Keywords and identifiers
|
||||
if (this.isAlpha(this.peek())) {
|
||||
return this.readIdentifierOrKeyword(startLine, startColumn);
|
||||
}
|
||||
|
||||
// Skip unknown characters
|
||||
this.advance();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private readIdentifierOrKeyword(line: number, column: number): Token {
|
||||
let value = '';
|
||||
|
||||
while (this.position < this.input.length &&
|
||||
(this.isAlphaNumeric(this.peek()) || this.peek() === '_')) {
|
||||
value += this.peek();
|
||||
this.advance();
|
||||
}
|
||||
|
||||
// Check for keywords
|
||||
const type = this.getKeywordType(value);
|
||||
|
||||
return {
|
||||
type,
|
||||
value,
|
||||
line,
|
||||
column
|
||||
};
|
||||
}
|
||||
|
||||
private readString(line: number, column: number): Token {
|
||||
const quote = this.peek();
|
||||
this.advance(); // Skip opening quote
|
||||
|
||||
let value = '';
|
||||
while (this.position < this.input.length && this.peek() !== quote) {
|
||||
value += this.peek();
|
||||
this.advance();
|
||||
}
|
||||
|
||||
if (this.peek() === quote) {
|
||||
this.advance(); // Skip closing quote
|
||||
}
|
||||
|
||||
return {
|
||||
type: TokenType.STRING,
|
||||
value: value, // Return the content without quotes
|
||||
line,
|
||||
column
|
||||
};
|
||||
}
|
||||
|
||||
private getKeywordType(value: string): TokenType {
|
||||
switch (value.toLowerCase()) {
|
||||
case 'usecase': return TokenType.USECASE_START;
|
||||
case 'actor': return TokenType.ACTOR;
|
||||
case 'systemboundary': return TokenType.SYSTEM_BOUNDARY;
|
||||
case 'end': return TokenType.END;
|
||||
default: return TokenType.IDENTIFIER;
|
||||
}
|
||||
}
|
||||
|
||||
private skipWhitespace(): void {
|
||||
while (this.position < this.input.length &&
|
||||
(this.peek() === ' ' || this.peek() === '\t')) {
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
private skipComment(): void {
|
||||
while (this.position < this.input.length &&
|
||||
this.peek() !== '\n' && this.peek() !== '\r') {
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
private peek(offset: number = 0): string {
|
||||
const pos = this.position + offset;
|
||||
return pos < this.input.length ? this.input[pos] : '';
|
||||
}
|
||||
|
||||
private tryParseLabeledArrow(): Token | null {
|
||||
// Try to parse --label--> or --label->
|
||||
const startPos = this.position;
|
||||
const startLine = this.line;
|
||||
const startColumn = this.column;
|
||||
|
||||
// Skip initial '--'
|
||||
if (this.peek() !== '-' || this.peek(1) !== '-') {
|
||||
return null;
|
||||
}
|
||||
|
||||
let pos = 2;
|
||||
let label = '';
|
||||
|
||||
// Read the label
|
||||
while (pos < this.input.length - this.position) {
|
||||
const char = this.peek(pos);
|
||||
if (char === '-') {
|
||||
// Check if this is the end pattern
|
||||
if (this.peek(pos + 1) === '-' && this.peek(pos + 2) === '>') {
|
||||
// Found --label-->
|
||||
this.advance(pos + 3);
|
||||
return {
|
||||
type: TokenType.LABELED_ARROW,
|
||||
value: `--${label}-->`,
|
||||
line: startLine,
|
||||
column: startColumn
|
||||
};
|
||||
} else if (this.peek(pos + 1) === '>') {
|
||||
// Found --label->
|
||||
this.advance(pos + 2);
|
||||
return {
|
||||
type: TokenType.LABELED_ARROW,
|
||||
value: `--${label}->`,
|
||||
line: startLine,
|
||||
column: startColumn
|
||||
};
|
||||
} else {
|
||||
label += char;
|
||||
pos++;
|
||||
}
|
||||
} else if (char.match(/[a-zA-Z0-9_]/)) {
|
||||
label += char;
|
||||
pos++;
|
||||
} else {
|
||||
// Invalid character in label
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private advance(count: number = 1): void {
|
||||
for (let i = 0; i < count && this.position < this.input.length; i++) {
|
||||
this.position++;
|
||||
this.column++;
|
||||
}
|
||||
}
|
||||
|
||||
private isAlpha(char: string): boolean {
|
||||
return /[a-zA-Z]/.test(char);
|
||||
}
|
||||
|
||||
private isAlphaNumeric(char: string): boolean {
|
||||
return /[a-zA-Z0-9]/.test(char);
|
||||
}
|
||||
}
|
||||
|
||||
class UsecaseParser {
|
||||
private tokens: Token[];
|
||||
private position: number = 0;
|
||||
|
||||
constructor(tokens: Token[]) {
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
parse(): UsecaseDiagram {
|
||||
const statements: Statement[] = [];
|
||||
|
||||
// Expect 'usecase' keyword at the start
|
||||
this.consume(TokenType.USECASE_START);
|
||||
this.skipNewlines();
|
||||
|
||||
while (!this.isAtEnd()) {
|
||||
this.skipNewlines();
|
||||
|
||||
if (this.isAtEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const parsedStatements = this.parseStatement();
|
||||
if (parsedStatements) {
|
||||
if (Array.isArray(parsedStatements)) {
|
||||
statements.push(...parsedStatements);
|
||||
} else {
|
||||
statements.push(parsedStatements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'usecaseDiagram',
|
||||
statements
|
||||
};
|
||||
}
|
||||
|
||||
private parseStatement(): Statement | Statement[] | null {
|
||||
const token = this.peek();
|
||||
|
||||
switch (token.type) {
|
||||
case TokenType.ACTOR:
|
||||
return this.parseActorStatement();
|
||||
case TokenType.SYSTEM_BOUNDARY:
|
||||
return this.parseSystemBoundary();
|
||||
case TokenType.IDENTIFIER:
|
||||
// Look ahead to see if this is a systemBoundaryMetadata, relationship, or use case
|
||||
if (this.isSystemBoundaryMetadata()) {
|
||||
return this.parseSystemBoundaryMetadata();
|
||||
} else if (this.isRelationship()) {
|
||||
return this.parseRelationship();
|
||||
} else {
|
||||
return this.parseUseCase();
|
||||
}
|
||||
default:
|
||||
this.advance(); // Skip unknown tokens
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private parseActorStatement(): Statement | Statement[] {
|
||||
this.consume(TokenType.ACTOR);
|
||||
|
||||
// Check if this is an inline actor-node relationship
|
||||
// Look ahead: IDENTIFIER ARROW IDENTIFIER LPAREN
|
||||
if (this.isInlineActorNodeRelationship()) {
|
||||
return this.parseInlineActorNodeRelationship();
|
||||
}
|
||||
|
||||
const actors: Actor[] = [];
|
||||
|
||||
// Parse first actor
|
||||
actors.push(this.parseActorDefinition());
|
||||
|
||||
// Parse additional actors separated by commas
|
||||
while (this.check(TokenType.COMMA)) {
|
||||
this.consume(TokenType.COMMA);
|
||||
actors.push(this.parseActorDefinition());
|
||||
}
|
||||
|
||||
return actors;
|
||||
}
|
||||
|
||||
private parseActorDefinition(): Actor {
|
||||
const name = this.consume(TokenType.IDENTIFIER).value;
|
||||
|
||||
let metadata: Record<string, string> | undefined;
|
||||
|
||||
// Check for optional metadata
|
||||
if (this.check(TokenType.AT)) {
|
||||
metadata = this.parseMetadata();
|
||||
}
|
||||
|
||||
const actor: Actor = { type: 'actor', name };
|
||||
if (metadata) {
|
||||
actor.metadata = metadata;
|
||||
}
|
||||
|
||||
return actor;
|
||||
}
|
||||
|
||||
private parseSystemBoundary(): SystemBoundary {
|
||||
this.consume(TokenType.SYSTEM_BOUNDARY);
|
||||
const name = this.consume(TokenType.IDENTIFIER).value;
|
||||
this.consume(TokenType.LBRACE);
|
||||
|
||||
// Skip newlines after opening brace
|
||||
this.skipNewlines();
|
||||
|
||||
const useCases: Usecase[] = [];
|
||||
|
||||
// Parse use cases until we hit closing brace
|
||||
while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
|
||||
this.skipNewlines();
|
||||
|
||||
if (this.check(TokenType.RBRACE) || this.isAtEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.check(TokenType.IDENTIFIER)) {
|
||||
const useCase = this.parseUseCase();
|
||||
if (useCase) {
|
||||
useCases.push(useCase as Usecase);
|
||||
}
|
||||
} else {
|
||||
this.advance(); // Skip unknown tokens
|
||||
}
|
||||
}
|
||||
|
||||
this.consume(TokenType.RBRACE);
|
||||
|
||||
return {
|
||||
type: 'systemBoundary',
|
||||
name,
|
||||
useCases
|
||||
};
|
||||
}
|
||||
|
||||
private parseSystemBoundaryMetadata(): SystemBoundaryMetadata {
|
||||
const name = this.consume(TokenType.IDENTIFIER).value;
|
||||
this.consume(TokenType.AT);
|
||||
this.consume(TokenType.LBRACE);
|
||||
|
||||
const metadata: Record<string, string> = {};
|
||||
|
||||
// Parse metadata content
|
||||
while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
|
||||
if (this.check(TokenType.IDENTIFIER)) {
|
||||
const key = this.consume(TokenType.IDENTIFIER).value;
|
||||
this.consume(TokenType.COLON);
|
||||
|
||||
let value = '';
|
||||
if (this.check(TokenType.STRING)) {
|
||||
value = this.consume(TokenType.STRING).value;
|
||||
// Remove quotes from string value
|
||||
value = value.slice(1, -1);
|
||||
} else if (this.check(TokenType.IDENTIFIER)) {
|
||||
value = this.consume(TokenType.IDENTIFIER).value;
|
||||
}
|
||||
|
||||
metadata[key] = value;
|
||||
|
||||
// Optional comma
|
||||
if (this.check(TokenType.COMMA)) {
|
||||
this.advance();
|
||||
}
|
||||
} else {
|
||||
this.advance(); // Skip unknown tokens
|
||||
}
|
||||
}
|
||||
|
||||
this.consume(TokenType.RBRACE);
|
||||
|
||||
return {
|
||||
type: 'systemBoundaryMetadata',
|
||||
name,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
|
||||
private parseUseCase(): Usecase {
|
||||
const name = this.consume(TokenType.IDENTIFIER).value;
|
||||
|
||||
return {
|
||||
type: 'usecase',
|
||||
name
|
||||
};
|
||||
}
|
||||
|
||||
private isRelationship(): boolean {
|
||||
// Look ahead to see if there's an arrow after the identifier
|
||||
const currentPos = this.position;
|
||||
this.advance(); // Skip the identifier
|
||||
const hasArrow = this.check(TokenType.ARROW) || this.check(TokenType.LABELED_ARROW);
|
||||
this.position = currentPos; // Reset position
|
||||
return hasArrow;
|
||||
}
|
||||
|
||||
private isSystemBoundaryMetadata(): boolean {
|
||||
// Look ahead to see if there's an @ after the identifier
|
||||
const currentPos = this.position;
|
||||
this.advance(); // Skip the identifier
|
||||
const hasAt = this.check(TokenType.AT);
|
||||
this.position = currentPos; // Reset position
|
||||
return hasAt;
|
||||
}
|
||||
|
||||
private parseRelationship(): ActorUseCaseRelationship | ActorNodeRelationship {
|
||||
const from = this.consume(TokenType.IDENTIFIER).value;
|
||||
|
||||
let arrowToken: Token;
|
||||
let label: string | undefined;
|
||||
|
||||
if (this.check(TokenType.LABELED_ARROW)) {
|
||||
arrowToken = this.consume(TokenType.LABELED_ARROW);
|
||||
// Extract label from --label--> or --label->
|
||||
const arrowValue = arrowToken.value;
|
||||
const match = arrowValue.match(/^--(.+?)-+>$/);
|
||||
if (match) {
|
||||
label = match[1];
|
||||
}
|
||||
} else {
|
||||
arrowToken = this.consume(TokenType.ARROW);
|
||||
}
|
||||
|
||||
// Check if target is a node definition (ID followed by parentheses)
|
||||
if (this.isNodeDefinition()) {
|
||||
const node = this.parseNodeDefinition();
|
||||
return {
|
||||
type: 'actorNodeRelationship',
|
||||
from,
|
||||
to: node.id,
|
||||
arrow: arrowToken.value,
|
||||
label
|
||||
};
|
||||
} else {
|
||||
const to = this.consume(TokenType.IDENTIFIER).value;
|
||||
return {
|
||||
type: 'actorUseCaseRelationship',
|
||||
from,
|
||||
to,
|
||||
arrow: arrowToken.value,
|
||||
label
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private isInlineActorNodeRelationship(): boolean {
|
||||
// Look ahead: IDENTIFIER (ARROW|LABELED_ARROW) IDENTIFIER LPAREN
|
||||
const currentPos = this.position;
|
||||
|
||||
if (!this.check(TokenType.IDENTIFIER)) {
|
||||
this.position = currentPos;
|
||||
return false;
|
||||
}
|
||||
this.advance(); // Skip actor name
|
||||
|
||||
if (!this.check(TokenType.ARROW) && !this.check(TokenType.LABELED_ARROW)) {
|
||||
this.position = currentPos;
|
||||
return false;
|
||||
}
|
||||
this.advance(); // Skip arrow
|
||||
|
||||
if (!this.check(TokenType.IDENTIFIER)) {
|
||||
this.position = currentPos;
|
||||
return false;
|
||||
}
|
||||
this.advance(); // Skip node ID
|
||||
|
||||
const hasLParen = this.check(TokenType.LPAREN);
|
||||
this.position = currentPos; // Reset position
|
||||
return hasLParen;
|
||||
}
|
||||
|
||||
private parseInlineActorNodeRelationship(): InlineActorNodeRelationship {
|
||||
const actor = this.consume(TokenType.IDENTIFIER).value;
|
||||
|
||||
let arrowToken: Token;
|
||||
let label: string | undefined;
|
||||
|
||||
if (this.check(TokenType.LABELED_ARROW)) {
|
||||
arrowToken = this.consume(TokenType.LABELED_ARROW);
|
||||
// Extract label from --label--> or --label->
|
||||
const arrowValue = arrowToken.value;
|
||||
const match = arrowValue.match(/^--(.+?)-+>$/);
|
||||
if (match) {
|
||||
label = match[1];
|
||||
}
|
||||
} else {
|
||||
arrowToken = this.consume(TokenType.ARROW);
|
||||
}
|
||||
|
||||
const node = this.parseNodeDefinition();
|
||||
|
||||
return {
|
||||
type: 'inlineActorNodeRelationship',
|
||||
actor,
|
||||
node,
|
||||
arrow: arrowToken.value,
|
||||
label
|
||||
};
|
||||
}
|
||||
|
||||
private isNodeDefinition(): boolean {
|
||||
// Look ahead: IDENTIFIER LPAREN
|
||||
const currentPos = this.position;
|
||||
|
||||
if (!this.check(TokenType.IDENTIFIER)) {
|
||||
this.position = currentPos;
|
||||
return false;
|
||||
}
|
||||
this.advance(); // Skip node ID
|
||||
|
||||
const hasLParen = this.check(TokenType.LPAREN);
|
||||
this.position = currentPos; // Reset position
|
||||
return hasLParen;
|
||||
}
|
||||
|
||||
private parseNodeDefinition(): Node {
|
||||
const id = this.consume(TokenType.IDENTIFIER).value;
|
||||
this.consume(TokenType.LPAREN);
|
||||
|
||||
// Parse node label (can be multiple words or a string)
|
||||
let label = '';
|
||||
if (this.check(TokenType.STRING)) {
|
||||
label = this.consume(TokenType.STRING).value;
|
||||
// Remove quotes
|
||||
label = label.slice(1, -1);
|
||||
} else {
|
||||
// Parse multiple identifiers as label
|
||||
const labelParts: string[] = [];
|
||||
while (this.check(TokenType.IDENTIFIER) && !this.check(TokenType.RPAREN)) {
|
||||
labelParts.push(this.consume(TokenType.IDENTIFIER).value);
|
||||
}
|
||||
label = labelParts.join(' ');
|
||||
}
|
||||
|
||||
this.consume(TokenType.RPAREN);
|
||||
|
||||
return {
|
||||
type: 'node',
|
||||
id,
|
||||
label
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
private parseMetadata(): Record<string, string> {
|
||||
this.consume(TokenType.AT);
|
||||
this.consume(TokenType.LBRACE);
|
||||
|
||||
const metadata: Record<string, string> = {};
|
||||
|
||||
// Handle empty metadata
|
||||
if (this.check(TokenType.RBRACE)) {
|
||||
this.consume(TokenType.RBRACE);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Parse key-value pairs
|
||||
do {
|
||||
const key = this.consume(TokenType.IDENTIFIER).value;
|
||||
this.consume(TokenType.COLON);
|
||||
|
||||
let value: string;
|
||||
if (this.check(TokenType.STRING)) {
|
||||
value = this.consume(TokenType.STRING).value;
|
||||
} else {
|
||||
value = this.consume(TokenType.IDENTIFIER).value;
|
||||
}
|
||||
|
||||
metadata[key] = value;
|
||||
|
||||
// Check for comma (more pairs) or closing brace
|
||||
if (this.check(TokenType.COMMA)) {
|
||||
this.consume(TokenType.COMMA);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (!this.check(TokenType.RBRACE) && !this.isAtEnd());
|
||||
|
||||
this.consume(TokenType.RBRACE);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private skipNewlines(): void {
|
||||
while (this.check(TokenType.NEWLINE)) {
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
private peek(): Token {
|
||||
return this.tokens[this.position];
|
||||
}
|
||||
|
||||
private advance(): Token {
|
||||
if (!this.isAtEnd()) {
|
||||
this.position++;
|
||||
}
|
||||
return this.tokens[this.position - 1];
|
||||
}
|
||||
|
||||
private check(type: TokenType): boolean {
|
||||
if (this.isAtEnd()) return false;
|
||||
return this.peek().type === type;
|
||||
}
|
||||
|
||||
private consume(type: TokenType): Token {
|
||||
if (this.check(type)) {
|
||||
return this.advance();
|
||||
}
|
||||
|
||||
const current = this.peek();
|
||||
throw new Error(`Expected ${type}, got ${current.type} at line ${current.line}`);
|
||||
}
|
||||
|
||||
private isAtEnd(): boolean {
|
||||
return this.position >= this.tokens.length || this.peek().type === TokenType.EOF;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseUsecase(input: string): ParseResult {
|
||||
try {
|
||||
const lexer = new UsecaseLexer(input);
|
||||
const tokens = lexer.tokenize();
|
||||
const parser = new UsecaseParser(tokens);
|
||||
const ast = parser.parse();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
ast
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
errors: [error instanceof Error ? error.message : String(error)]
|
||||
};
|
||||
}
|
||||
}
|
113
packages/parser/src/language/useCase/usecaseTypes.ts
Normal file
113
packages/parser/src/language/useCase/usecaseTypes.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
// AST types for usecase diagrams
|
||||
|
||||
export interface UsecaseDiagram {
|
||||
type: 'usecaseDiagram';
|
||||
statements: Statement[];
|
||||
}
|
||||
|
||||
export type Statement = Actor | SystemBoundary | SystemBoundaryMetadata | Usecase | Relationship | ActorUseCaseRelationship | Node | ActorNodeRelationship | InlineActorNodeRelationship;
|
||||
|
||||
export interface Title {
|
||||
type: 'title';
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface AccDescr {
|
||||
type: 'accDescr';
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface AccTitle {
|
||||
type: 'accTitle';
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface Actor {
|
||||
type: 'actor';
|
||||
name: string;
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Usecase {
|
||||
type: 'usecase';
|
||||
name: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
export interface SystemBoundary {
|
||||
type: 'systemBoundary';
|
||||
name: string;
|
||||
useCases: Usecase[];
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SystemBoundaryMetadata {
|
||||
type: 'systemBoundaryMetadata';
|
||||
name: string; // boundary name
|
||||
metadata: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ActorUseCaseRelationship {
|
||||
type: 'actorUseCaseRelationship';
|
||||
from: string; // actor name
|
||||
to: string; // use case name
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
type: 'node';
|
||||
id: string; // node ID (e.g., 'a', 'b', 'c')
|
||||
label: string; // node label (e.g., 'Go through code')
|
||||
}
|
||||
|
||||
export interface ActorNodeRelationship {
|
||||
type: 'actorNodeRelationship';
|
||||
from: string; // actor name
|
||||
to: string; // node ID
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
export interface InlineActorNodeRelationship {
|
||||
type: 'inlineActorNodeRelationship';
|
||||
actor: string; // actor name
|
||||
node: Node; // node definition
|
||||
arrow: string; // '-->' or '->'
|
||||
label?: string; // edge label (optional)
|
||||
}
|
||||
|
||||
export interface Relationship {
|
||||
type: 'relationship';
|
||||
from: string;
|
||||
to: string;
|
||||
relationshipType: RelationshipType;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
type: 'note';
|
||||
position: NotePosition;
|
||||
target: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type RelationshipType =
|
||||
| 'arrow-left'
|
||||
| 'arrow-right'
|
||||
| 'arrow-both'
|
||||
| 'extends'
|
||||
| 'includes';
|
||||
|
||||
export type NotePosition =
|
||||
| 'left'
|
||||
| 'right'
|
||||
| 'top'
|
||||
| 'bottom';
|
||||
|
||||
// Parser result type
|
||||
export interface ParseResult {
|
||||
success: boolean;
|
||||
ast?: UsecaseDiagram;
|
||||
errors?: string[];
|
||||
}
|
440
pnpm-lock.yaml
generated
440
pnpm-lock.yaml
generated
@@ -227,8 +227,8 @@ importers:
|
||||
specifier: ^7.0.4
|
||||
version: 7.1.0
|
||||
'@iconify/utils':
|
||||
specifier: ^2.1.33
|
||||
version: 2.3.0
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
'@mermaid-js/parser':
|
||||
specifier: workspace:^
|
||||
version: link:../parser
|
||||
@@ -499,8 +499,8 @@ importers:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
unocss:
|
||||
specifier: ^66.0.0
|
||||
version: 66.0.0(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))
|
||||
specifier: ^66.4.2
|
||||
version: 66.4.2(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))
|
||||
unplugin-vue-components:
|
||||
specifier: ^28.4.0
|
||||
version: 28.4.0(@babel/parser@7.28.0)(vue@3.5.13(typescript@5.7.3))
|
||||
@@ -647,11 +647,11 @@ packages:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@antfu/install-pkg@1.0.0':
|
||||
resolution: {integrity: sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==}
|
||||
'@antfu/install-pkg@1.1.0':
|
||||
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||
|
||||
'@antfu/utils@8.1.1':
|
||||
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
|
||||
'@antfu/utils@9.2.0':
|
||||
resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==}
|
||||
|
||||
'@apideck/better-ajv-errors@0.3.6':
|
||||
resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==}
|
||||
@@ -2460,8 +2460,8 @@ packages:
|
||||
'@iconify/types@2.0.0':
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
|
||||
'@iconify/utils@2.3.0':
|
||||
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
|
||||
'@iconify/utils@3.0.1':
|
||||
resolution: {integrity: sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
@@ -2740,6 +2740,9 @@ packages:
|
||||
'@polka/url@1.0.0-next.28':
|
||||
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
|
||||
|
||||
'@quansync/fs@0.1.4':
|
||||
resolution: {integrity: sha512-vy/41FCdnIalPTQCb2Wl0ic1caMdzGus4ktDp+gpZesQNydXcx8nhh8qB3qMPbGkictOTaXgXEUUfQEm8DQYoA==}
|
||||
|
||||
'@react-aria/focus@3.21.0':
|
||||
resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==}
|
||||
peerDependencies:
|
||||
@@ -3538,88 +3541,94 @@ packages:
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
'@unocss/astro@66.0.0':
|
||||
resolution: {integrity: sha512-GBhXT6JPqXjDXoJZTXhySk83NgOt0UigChqrUUdG4x7Z+DVYkDBION8vZUJjw0OdIaxNQ4euGWu4GDsMF6gQQg==}
|
||||
'@unocss/astro@66.4.2':
|
||||
resolution: {integrity: sha512-En3AKHwkiPxtZT95vkVrNiRYrB+DFVCikew6/dMMCWDWVKK0+5tEVUTzR1ak3+YnzAXl0NpWj8D4zHb0PxOs/A==}
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@unocss/cli@66.0.0':
|
||||
resolution: {integrity: sha512-KVQiskoOjVkLVpNaG6WpLa4grPplrZROYZJVIUYSTqZyZRFNSvjttHcsCwpoWUEUdEombPtVZl8FrXePjY5IiQ==}
|
||||
'@unocss/cli@66.4.2':
|
||||
resolution: {integrity: sha512-WsXzrB0SHbSt2nOHtD5QM91VN8j38+wObqyGcoIhtBSugqzsc+t7AdPkxV/ZaYgtPAz87bR0WFEVKcbiBRnmJw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
'@unocss/config@66.0.0':
|
||||
resolution: {integrity: sha512-nFRGop/guBa4jLkrgXjaRDm5JPz4x3YpP10m5IQkHpHwlnHUVn1L9smyPl04ohYWhYn9ZcAHgR28Ih2jwta8hw==}
|
||||
'@unocss/config@66.4.2':
|
||||
resolution: {integrity: sha512-plji1gNGSzlWjuV2Uh0q6Dt5ZlNkOKCHpgxekW9J458WghGAMBeXgB9uNpWg6flilqP1g0GJQv+XvJcSkYRGpQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@unocss/core@66.0.0':
|
||||
resolution: {integrity: sha512-PdVbSMHNDDkr++9nkqzsZRAkaU84gxMTEgYbqI7dt2p1DXp/5tomVtmMsr2/whXGYKRiUc0xZ3p4Pzraz8TcXA==}
|
||||
'@unocss/core@66.4.2':
|
||||
resolution: {integrity: sha512-cYgMQrLhB9nRekv5c+yPDDa+5dzlMkA2UMQRil0s5D9Lb5n7NsCMcr6+nfxkcSYVLy92SbwDV45c6T7vIxFTOA==}
|
||||
|
||||
'@unocss/extractor-arbitrary-variants@66.0.0':
|
||||
resolution: {integrity: sha512-vlkOIOuwBfaFBJcN6o7+obXjigjOlzVFN/jT6pG1WXbQDTRZ021jeF3i9INdb9D/0cQHSeDvNgi1TJ5oUxfiow==}
|
||||
'@unocss/extractor-arbitrary-variants@66.4.2':
|
||||
resolution: {integrity: sha512-T/eSeodfAp7HaWnQGqVLOsW4PbKUAvuybNRyvFWThMneM2qo+dOo3kFnA5my9ULAmRSFsAlyB1DnupD3qv5Klg==}
|
||||
|
||||
'@unocss/inspector@66.0.0':
|
||||
resolution: {integrity: sha512-mkIxieVm0kMOKw+E4ABpIerihYMdjgq9A92RD5h2+W/ebpxTEw5lTTK1xcMLiAlmOrVYMQKjpgPeu3vQmDyGZQ==}
|
||||
'@unocss/inspector@66.4.2':
|
||||
resolution: {integrity: sha512-ugcJK8r2ypM4eIdgetVn8RhfKrbA3AF3OQ/RohK5PPk2UPDAScqabzYpfdNW4eYQsBOZOgoiqWtnfc8weqo8LQ==}
|
||||
|
||||
'@unocss/postcss@66.0.0':
|
||||
resolution: {integrity: sha512-6bi+ujzh8I1PJwtmHX71LH8z/H9+vPxeYD4XgFihyU1k4Y6MVhjr7giGjLX4yP27IP+NsVyotD22V7by/dBVEA==}
|
||||
'@unocss/postcss@66.4.2':
|
||||
resolution: {integrity: sha512-tu4lnh6K27pIAuaQHlFlhXin8korwC0r1kQl00YMmF3THiX7orXkTP6xWGcQwnkbx4uQz1dw+tBimYxeaAMrhA==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
postcss: ^8.4.21
|
||||
|
||||
'@unocss/preset-attributify@66.0.0':
|
||||
resolution: {integrity: sha512-eYsOgmcDoiIgGAepIwRX+DKGYxc/wm0r4JnDuZdz29AB+A6oY/FGHS1BVt4rq9ny4B5PofP4p6Rty+vwD9rigw==}
|
||||
'@unocss/preset-attributify@66.4.2':
|
||||
resolution: {integrity: sha512-DwFJJkkawmHpjo3pGQE8FyoPsvhbxh+QMvvaAdYpo+iZ5HRkeDml9SOj7u6SGTcmbNyI+QR61s0KM8fxx6HcVQ==}
|
||||
|
||||
'@unocss/preset-icons@66.0.0':
|
||||
resolution: {integrity: sha512-6ObwTvEGuPBbKWRoMMiDioHtwwQTFI5oojFLJ32Y8tW6TdXvBLkO88d7qpgQxEjgVt4nJrqF1WEfR4niRgBm0Q==}
|
||||
'@unocss/preset-icons@66.4.2':
|
||||
resolution: {integrity: sha512-qJx9gmesrvrmoTe9Mqoidihad8hm2MSD4QAezhfDSAyllioJOgyT0Bev/IEWAbehe9jtqYIh8v1oCerBPbGn6Q==}
|
||||
|
||||
'@unocss/preset-mini@66.0.0':
|
||||
resolution: {integrity: sha512-d62eACnuKtR0dwCFOQXgvw5VLh5YSyK56xCzpHkh0j0GstgfDLfKTys0T/XVAAvdSvAy/8A8vhSNJ4PlIc9V2A==}
|
||||
'@unocss/preset-mini@66.4.2':
|
||||
resolution: {integrity: sha512-Ry+5hM+XLmT8HrEb182mUfcZuyrZ8xR+TBe72DBcliJ1DhOV3K67TCxwQucfb0zHbGV71HNWdPmHsLKxPDgweQ==}
|
||||
|
||||
'@unocss/preset-tagify@66.0.0':
|
||||
resolution: {integrity: sha512-GGYGyWxaevh0jN0NoATVO1Qe7DFXM3ykLxchlXmG6/zy963pZxItg/njrKnxE9la4seCdxpFH7wQBa68imwwdA==}
|
||||
'@unocss/preset-tagify@66.4.2':
|
||||
resolution: {integrity: sha512-dECS09LqWJY4sYpgPUH2OAUftWU/tiZPR2XDRoTngeGU37GxSN+1sWtSmB7vwDm3C7opsdVUN20he8F1LUNubw==}
|
||||
|
||||
'@unocss/preset-typography@66.0.0':
|
||||
resolution: {integrity: sha512-apjckP5nPU5mtaHTCzz5u/dK9KJWwJ2kOFCVk0+a/KhUWmnqnzmjRYZlEuWxxr5QxTdCW+9cIoRDSA0lYZS5tg==}
|
||||
'@unocss/preset-typography@66.4.2':
|
||||
resolution: {integrity: sha512-ZOKRuR5+V0r30QTVq04/6ZoIw75me3V25v2dU2YWJXIzwpMKmQ9TUN/M1yeiEUFfXjOaruWX6Ad6CvAw2MlCew==}
|
||||
|
||||
'@unocss/preset-uno@66.0.0':
|
||||
resolution: {integrity: sha512-qgoZ/hzTI32bQvcyjcwvv1X/dbPlmQNehzgjUaL7QFT0q0/CN/SRpysfzoQ8DLl2se9T+YCOS9POx3KrpIiYSQ==}
|
||||
'@unocss/preset-uno@66.4.2':
|
||||
resolution: {integrity: sha512-1MFtPivGcpqRQFWdjtP40Enop1y3XDb3tlZXoMQUX0IGLG8HJOT+lfQx/Xl9t73ShJ8aAJ/l6qTxC43ZGNACzA==}
|
||||
|
||||
'@unocss/preset-web-fonts@66.0.0':
|
||||
resolution: {integrity: sha512-9MzfDc6AJILN4Kq7Z91FfFbizBOYgw3lJd2UwqIs3PDYWG5iH5Zv5zhx6jelZVqEW5uWcIARYEEg2m4stZO1ZA==}
|
||||
'@unocss/preset-web-fonts@66.4.2':
|
||||
resolution: {integrity: sha512-4FYmleeRoM8r2DqGl6dfIjnX57tepcfZCvVfeCqYnk7475Yddmv1OYkoMjkWMnkK9MzdSxsFwHMU6CIUTmFTzQ==}
|
||||
|
||||
'@unocss/preset-wind3@66.0.0':
|
||||
resolution: {integrity: sha512-WAGRmpi1sb2skvYn9DBQUvhfqrJ+VmQmn5ZGsT2ewvsk7HFCvVLAMzZeKrrTQepeNBRhg6HzFDDi8yg6yB5c9g==}
|
||||
'@unocss/preset-wind3@66.4.2':
|
||||
resolution: {integrity: sha512-0Aye/PaT08M/cQhPnGKn93iEVoRJbym0/1eomMvXoL+8oc7DVry35ws06r5CLu5h1sXI6UmS6sejoePFlSkLJQ==}
|
||||
|
||||
'@unocss/preset-wind@66.0.0':
|
||||
resolution: {integrity: sha512-FtvGpHnGC7FiyKJavPnn5y9lsaoWRhXlujCqlT5Bw63kKhMNr0ogKySBpenUhJOhWhVM0OQXn2nZ3GZRxW2qpw==}
|
||||
'@unocss/preset-wind4@66.4.2':
|
||||
resolution: {integrity: sha512-F4RZsDqIpnSevD9hY353+Tw5gxpJuHA5HwdKjLnC/TnT9VKKVmV7qUEZ6M0jEuAk1kz2x3/ngnQ9Ftw+C2L84A==}
|
||||
|
||||
'@unocss/preset-wind@66.4.2':
|
||||
resolution: {integrity: sha512-z/rFYFINNqmBtl3Dh+7UCKpPnPkxM7IIUGszMnvdntky9uhLauJ11dt/Puir73sM2cAfywfgvnHyZ00m0pg7rA==}
|
||||
|
||||
'@unocss/reset@66.0.0':
|
||||
resolution: {integrity: sha512-YLFz/5yT7mFJC8JSmIUA5+bS3CBCJbtztOw+8rWzjQr/BEVSGuihWUUpI2Df6VVxXIXxKanZR6mIl59yvf+GEA==}
|
||||
|
||||
'@unocss/rule-utils@66.0.0':
|
||||
resolution: {integrity: sha512-UJ51YHbwxYTGyj35ugsPlOT4gaa7tCbXdywZ3m5Nn0JgywwIqGmBFyiN9ZjHBHfJuDxmmPd6lxojoBscih/WMQ==}
|
||||
'@unocss/reset@66.4.2':
|
||||
resolution: {integrity: sha512-s3Kq4Q6a/d3/jYe6HTCfXUx7zYAYufetId5n66DZHzQxpeu6CoBS83+b37STTKsw27SOgV28cPJlJtZ6/D6Bhw==}
|
||||
|
||||
'@unocss/rule-utils@66.4.2':
|
||||
resolution: {integrity: sha512-7z3IuajwXhy2cx3E0IGOFXIiuKC79/jzm4Tt56TC68nXLh/etlH0fKhxVwkZ/HbcQRpVwWyDRNcbh29pmA3DwQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@unocss/transformer-attributify-jsx@66.0.0':
|
||||
resolution: {integrity: sha512-jS7szFXXC6RjTv9wo0NACskf618w981bkbyQ5izRO7Ha47sNpHhHDpaltnG7SR9qV4cCtGalOw4onVMHsRKwRg==}
|
||||
'@unocss/transformer-attributify-jsx@66.4.2':
|
||||
resolution: {integrity: sha512-de6LzoyW1tkdOftlCrj6z8wEb4j6l1sqmOU1nYKkYHw7luLFGxRUELC7iujlI9KmylbM02bcKfLETAfJy/je2w==}
|
||||
|
||||
'@unocss/transformer-compile-class@66.0.0':
|
||||
resolution: {integrity: sha512-ytUIE0nAcHRMACuTXkHp8auZ483DXrOZw99jk3FJ+aFjpD/pVSFmX14AWJ7bqPFObxb4SLFs6KhQma30ESC22A==}
|
||||
'@unocss/transformer-compile-class@66.4.2':
|
||||
resolution: {integrity: sha512-+oiIrV8c3T7qiJdICr6YsEWik5sjbWirXF0mlpcBvZu2HyV559hvHjzuWKr/fl7xYYZKDL9FvddbqWo3DOXh3Q==}
|
||||
|
||||
'@unocss/transformer-directives@66.0.0':
|
||||
resolution: {integrity: sha512-utcg7m2Foi7uHrU5WHadNuJ0a3qWG8tZNkQMi+m0DQpX6KWfuDtDn0zDZ1X+z5lmiB3WGSJERRrsvZbj1q50Mw==}
|
||||
'@unocss/transformer-directives@66.4.2':
|
||||
resolution: {integrity: sha512-7m/dTrCUkBkZeSRKPxPEo65Rav239orQSLq6sztwZhoA4x/6H8r58xCkAK0qC9VEalyerpCpyarU3sKN4+ehNg==}
|
||||
|
||||
'@unocss/transformer-variant-group@66.0.0':
|
||||
resolution: {integrity: sha512-1BLjNWtAnR1JAcQGw0TS+nGrVoB9aznzvVZRoTx23dtRr3btvgKPHb8LrD48eD/p8Dtw9j3WfuxMDKXKegKDLg==}
|
||||
'@unocss/transformer-variant-group@66.4.2':
|
||||
resolution: {integrity: sha512-SbPDbZUrhQyL4CpvnpvUfrr1DFq8AKf8ofPGbMJDm5S2TInQ34vFaIrhNroGR0szntMZRH5Zlkq6LtVUKDRs5g==}
|
||||
|
||||
'@unocss/vite@66.0.0':
|
||||
resolution: {integrity: sha512-IVcPX8xL+2edyXKt4tp9yu5A6gcbPVCsspfcL0XgziCr01kS+4qSoZ90F3IUs3hXc/AyO5eCpRtGFMPLpOjXQg==}
|
||||
'@unocss/vite@66.4.2':
|
||||
resolution: {integrity: sha512-7eON9iPF3qWzuI+M6u0kq7K3y9nEbimZlLj01nGoqrgSGxEsyJpP01QQQsmT7FPRiZzRMJv7BiKMEyDQSuRRCA==}
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
|
||||
@@ -4779,12 +4788,15 @@ packages:
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
confbox@0.2.2:
|
||||
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
|
||||
|
||||
connect-history-api-fallback@2.0.0:
|
||||
resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
consola@3.4.0:
|
||||
resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==}
|
||||
consola@3.4.2:
|
||||
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
|
||||
console.table@0.10.0:
|
||||
@@ -5852,6 +5864,9 @@ packages:
|
||||
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
exsolve@1.0.7:
|
||||
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
|
||||
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
@@ -7276,6 +7291,10 @@ packages:
|
||||
resolution: {integrity: sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
local-pkg@1.1.1:
|
||||
resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
locate-path@3.0.0:
|
||||
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -8040,6 +8059,9 @@ packages:
|
||||
package-manager-detector@0.2.9:
|
||||
resolution: {integrity: sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==}
|
||||
|
||||
package-manager-detector@1.3.0:
|
||||
resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
@@ -8223,6 +8245,9 @@ packages:
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
pkg-types@2.2.0:
|
||||
resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==}
|
||||
|
||||
plist@3.1.0:
|
||||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||
engines: {node: '>=10.4.0'}
|
||||
@@ -8405,6 +8430,9 @@ packages:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
quansync@0.2.10:
|
||||
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
@@ -8996,6 +9024,7 @@ packages:
|
||||
source-map@0.8.0-beta.0:
|
||||
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
||||
engines: {node: '>= 8'}
|
||||
deprecated: The work that was done in this beta branch won't be included in future versions
|
||||
|
||||
sourcemap-codec@1.4.8:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
@@ -9327,6 +9356,9 @@ packages:
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyexec@1.0.1:
|
||||
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
|
||||
|
||||
tinyglobby@0.2.12:
|
||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -9547,8 +9579,8 @@ packages:
|
||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
unconfig@7.0.0:
|
||||
resolution: {integrity: sha512-G5CJSoG6ZTxgzCJblEfgpdRK2tos9+UdD2WtecDUVfImzQ0hFjwpH5RVvGMhP4pRpC9ML7NrC4qBsBl0Ttj35A==}
|
||||
unconfig@7.3.2:
|
||||
resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==}
|
||||
|
||||
underscore@1.1.7:
|
||||
resolution: {integrity: sha512-w4QtCHoLBXw1mjofIDoMyexaEdWGMedWNDhlWTtT1V1lCRqi65Pnoygkh6+WRdr+Bm8ldkBNkNeCsXGMlQS9HQ==}
|
||||
@@ -9625,12 +9657,12 @@ packages:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
unocss@66.0.0:
|
||||
resolution: {integrity: sha512-SHstiv1s7zGPSjzOsADzlwRhQM+6817+OqQE3Fv+N/nn2QLNx1bi3WXybFfz5tWkzBtyTZlwdPmeecsIs1yOCA==}
|
||||
unocss@66.4.2:
|
||||
resolution: {integrity: sha512-PsZ+4XF/ekiParR7PZEM7AchvHJ78EIfOXlqTPflTOXCYgZ77kG9NaIaIf4lHRevY+rRTyrHrjxdg1Ern2j8qw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@unocss/webpack': 66.0.0
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
|
||||
'@unocss/webpack': 66.4.2
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
|
||||
peerDependenciesMeta:
|
||||
'@unocss/webpack':
|
||||
optional: true
|
||||
@@ -9974,10 +10006,8 @@ packages:
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
vue-flow-layout@0.1.1:
|
||||
resolution: {integrity: sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==}
|
||||
peerDependencies:
|
||||
vue: ^3.4.37
|
||||
vue-flow-layout@0.2.0:
|
||||
resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==}
|
||||
|
||||
vue@3.5.13:
|
||||
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
|
||||
@@ -10498,12 +10528,12 @@ snapshots:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@antfu/install-pkg@1.0.0':
|
||||
'@antfu/install-pkg@1.1.0':
|
||||
dependencies:
|
||||
package-manager-detector: 0.2.9
|
||||
tinyexec: 0.3.2
|
||||
package-manager-detector: 1.3.0
|
||||
tinyexec: 1.0.1
|
||||
|
||||
'@antfu/utils@8.1.1': {}
|
||||
'@antfu/utils@9.2.0': {}
|
||||
|
||||
'@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)':
|
||||
dependencies:
|
||||
@@ -10856,7 +10886,7 @@ snapshots:
|
||||
|
||||
'@babel/generator@7.27.1':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/types': 7.27.1
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
@@ -10955,7 +10985,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-module-imports@7.27.1':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.27.1
|
||||
'@babel/traverse': 7.28.0
|
||||
'@babel/types': 7.27.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -10965,7 +10995,7 @@ snapshots:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
'@babel/traverse': 7.27.1
|
||||
'@babel/traverse': 7.28.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -12080,14 +12110,14 @@ snapshots:
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@babel/traverse@7.27.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
@@ -12673,7 +12703,7 @@ snapshots:
|
||||
'@babel/preset-env': 7.27.2(@babel/core@7.27.1)
|
||||
babel-loader: 9.2.1(@babel/core@7.27.1)(webpack@5.95.0(esbuild@0.25.0))
|
||||
bluebird: 3.7.1
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
lodash: 4.17.21
|
||||
webpack: 5.95.0(esbuild@0.25.0)
|
||||
transitivePeerDependencies:
|
||||
@@ -13111,15 +13141,15 @@ snapshots:
|
||||
|
||||
'@iconify/types@2.0.0': {}
|
||||
|
||||
'@iconify/utils@2.3.0':
|
||||
'@iconify/utils@3.0.1':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.0.0
|
||||
'@antfu/utils': 8.1.1
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@antfu/utils': 9.2.0
|
||||
'@iconify/types': 2.0.0
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
globals: 15.15.0
|
||||
kolorist: 1.8.0
|
||||
local-pkg: 1.0.0
|
||||
local-pkg: 1.1.1
|
||||
mlly: 1.7.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -13501,6 +13531,10 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.28': {}
|
||||
|
||||
'@quansync/fs@0.1.4':
|
||||
dependencies:
|
||||
quansync: 0.2.10
|
||||
|
||||
'@react-aria/focus@3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -13840,7 +13874,7 @@ snapshots:
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/types': 7.27.1
|
||||
'@types/babel__generator': 7.6.8
|
||||
'@types/babel__template': 7.4.4
|
||||
@@ -13852,7 +13886,7 @@ snapshots:
|
||||
|
||||
'@types/babel__template@7.4.4':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@types/babel__traverse@7.20.6':
|
||||
@@ -14358,150 +14392,157 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@unocss/astro@66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))':
|
||||
'@unocss/astro@66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/reset': 66.0.0
|
||||
'@unocss/vite': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/reset': 66.4.2
|
||||
'@unocss/vite': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))
|
||||
optionalDependencies:
|
||||
vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@unocss/cli@66.0.0':
|
||||
'@unocss/cli@66.4.2':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@unocss/config': 66.0.0
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/preset-uno': 66.0.0
|
||||
'@unocss/config': 66.4.2
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/preset-uno': 66.4.2
|
||||
cac: 6.7.14
|
||||
chokidar: 3.6.0
|
||||
colorette: 2.0.20
|
||||
consola: 3.4.0
|
||||
consola: 3.4.2
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
perfect-debounce: 1.0.0
|
||||
tinyglobby: 0.2.12
|
||||
tinyglobby: 0.2.14
|
||||
unplugin-utils: 0.2.4
|
||||
|
||||
'@unocss/config@66.0.0':
|
||||
'@unocss/config@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
unconfig: 7.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
unconfig: 7.3.2
|
||||
|
||||
'@unocss/core@66.0.0': {}
|
||||
'@unocss/core@66.4.2': {}
|
||||
|
||||
'@unocss/extractor-arbitrary-variants@66.0.0':
|
||||
'@unocss/extractor-arbitrary-variants@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
|
||||
'@unocss/inspector@66.0.0(vue@3.5.13(typescript@5.7.3))':
|
||||
'@unocss/inspector@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
colorette: 2.0.20
|
||||
gzip-size: 6.0.0
|
||||
sirv: 3.0.1
|
||||
vue-flow-layout: 0.1.1(vue@3.5.13(typescript@5.7.3))
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
vue-flow-layout: 0.2.0
|
||||
|
||||
'@unocss/postcss@66.0.0(postcss@8.5.6)':
|
||||
'@unocss/postcss@66.4.2(postcss@8.5.6)':
|
||||
dependencies:
|
||||
'@unocss/config': 66.0.0
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/config': 66.4.2
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
css-tree: 3.1.0
|
||||
postcss: 8.5.6
|
||||
tinyglobby: 0.2.12
|
||||
tinyglobby: 0.2.14
|
||||
|
||||
'@unocss/preset-attributify@66.0.0':
|
||||
'@unocss/preset-attributify@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
|
||||
'@unocss/preset-icons@66.0.0':
|
||||
'@unocss/preset-icons@66.4.2':
|
||||
dependencies:
|
||||
'@iconify/utils': 2.3.0
|
||||
'@unocss/core': 66.0.0
|
||||
'@iconify/utils': 3.0.1
|
||||
'@unocss/core': 66.4.2
|
||||
ofetch: 1.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@unocss/preset-mini@66.0.0':
|
||||
'@unocss/preset-mini@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/extractor-arbitrary-variants': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/extractor-arbitrary-variants': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
|
||||
'@unocss/preset-tagify@66.0.0':
|
||||
'@unocss/preset-tagify@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
|
||||
'@unocss/preset-typography@66.0.0':
|
||||
'@unocss/preset-typography@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/preset-mini': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/preset-mini': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
|
||||
'@unocss/preset-uno@66.0.0':
|
||||
'@unocss/preset-uno@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/preset-wind3': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/preset-wind3': 66.4.2
|
||||
|
||||
'@unocss/preset-web-fonts@66.0.0':
|
||||
'@unocss/preset-web-fonts@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
ofetch: 1.4.1
|
||||
|
||||
'@unocss/preset-wind3@66.0.0':
|
||||
'@unocss/preset-wind3@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/preset-mini': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/preset-mini': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
|
||||
'@unocss/preset-wind@66.0.0':
|
||||
'@unocss/preset-wind4@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/preset-wind3': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/extractor-arbitrary-variants': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
|
||||
'@unocss/preset-wind@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/preset-wind3': 66.4.2
|
||||
|
||||
'@unocss/reset@66.0.0': {}
|
||||
|
||||
'@unocss/rule-utils@66.0.0':
|
||||
'@unocss/reset@66.4.2': {}
|
||||
|
||||
'@unocss/rule-utils@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
magic-string: 0.30.17
|
||||
|
||||
'@unocss/transformer-attributify-jsx@66.0.0':
|
||||
'@unocss/transformer-attributify-jsx@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@babel/parser': 7.28.0
|
||||
'@babel/traverse': 7.28.0
|
||||
'@unocss/core': 66.4.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@unocss/transformer-compile-class@66.0.0':
|
||||
'@unocss/transformer-compile-class@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
|
||||
'@unocss/transformer-directives@66.0.0':
|
||||
'@unocss/transformer-directives@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/rule-utils': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/rule-utils': 66.4.2
|
||||
css-tree: 3.1.0
|
||||
|
||||
'@unocss/transformer-variant-group@66.0.0':
|
||||
'@unocss/transformer-variant-group@66.4.2':
|
||||
dependencies:
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/core': 66.4.2
|
||||
|
||||
'@unocss/vite@66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))':
|
||||
'@unocss/vite@66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@unocss/config': 66.0.0
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/inspector': 66.0.0(vue@3.5.13(typescript@5.7.3))
|
||||
'@unocss/config': 66.4.2
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/inspector': 66.4.2
|
||||
chokidar: 3.6.0
|
||||
magic-string: 0.30.17
|
||||
tinyglobby: 0.2.12
|
||||
pathe: 2.0.3
|
||||
tinyglobby: 0.2.14
|
||||
unplugin-utils: 0.2.4
|
||||
vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
optional: true
|
||||
@@ -15771,9 +15812,11 @@ snapshots:
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
confbox@0.2.2: {}
|
||||
|
||||
connect-history-api-fallback@2.0.0: {}
|
||||
|
||||
consola@3.4.0: {}
|
||||
consola@3.4.2: {}
|
||||
|
||||
console.table@0.10.0:
|
||||
dependencies:
|
||||
@@ -17197,6 +17240,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
exsolve@1.0.7: {}
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
extendable-error@0.1.7: {}
|
||||
@@ -17402,7 +17447,7 @@ snapshots:
|
||||
'@actions/core': 1.11.1
|
||||
arg: 5.0.2
|
||||
console.table: 0.10.0
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
find-test-names: 1.29.5(@babel/core@7.27.1)
|
||||
globby: 11.1.0
|
||||
minimatch: 3.1.2
|
||||
@@ -18303,7 +18348,7 @@ snapshots:
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -18936,6 +18981,12 @@ snapshots:
|
||||
mlly: 1.7.4
|
||||
pkg-types: 1.3.1
|
||||
|
||||
local-pkg@1.1.1:
|
||||
dependencies:
|
||||
mlly: 1.7.4
|
||||
pkg-types: 2.2.0
|
||||
quansync: 0.2.10
|
||||
|
||||
locate-path@3.0.0:
|
||||
dependencies:
|
||||
p-locate: 3.0.0
|
||||
@@ -19615,7 +19666,7 @@ snapshots:
|
||||
|
||||
node-source-walk@7.0.0:
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/parser': 7.28.0
|
||||
|
||||
nomnom@1.5.2:
|
||||
dependencies:
|
||||
@@ -19873,6 +19924,8 @@ snapshots:
|
||||
|
||||
package-manager-detector@0.2.9: {}
|
||||
|
||||
package-manager-detector@1.3.0: {}
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
pako@2.1.0: {}
|
||||
@@ -20047,6 +20100,12 @@ snapshots:
|
||||
mlly: 1.7.4
|
||||
pathe: 2.0.3
|
||||
|
||||
pkg-types@2.2.0:
|
||||
dependencies:
|
||||
confbox: 0.2.2
|
||||
exsolve: 1.0.7
|
||||
pathe: 2.0.3
|
||||
|
||||
plist@3.1.0:
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.10
|
||||
@@ -20228,6 +20287,8 @@ snapshots:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
quansync@0.2.10: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
@@ -21017,7 +21078,7 @@ snapshots:
|
||||
|
||||
spdy@4.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
handle-thing: 2.0.1
|
||||
http-deceiver: 1.2.7
|
||||
select-hose: 2.0.0
|
||||
@@ -21397,6 +21458,8 @@ snapshots:
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyexec@1.0.1: {}
|
||||
|
||||
tinyglobby@0.2.12:
|
||||
dependencies:
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
@@ -21599,11 +21662,12 @@ snapshots:
|
||||
has-symbols: 1.1.0
|
||||
which-boxed-primitive: 1.1.1
|
||||
|
||||
unconfig@7.0.0:
|
||||
unconfig@7.3.2:
|
||||
dependencies:
|
||||
'@antfu/utils': 8.1.1
|
||||
'@quansync/fs': 0.1.4
|
||||
defu: 6.1.4
|
||||
jiti: 2.4.2
|
||||
quansync: 0.2.10
|
||||
|
||||
underscore@1.1.7: {}
|
||||
|
||||
@@ -21689,32 +21753,32 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unocss@66.0.0(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3)):
|
||||
unocss@66.4.2(postcss@8.5.6)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@unocss/astro': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))
|
||||
'@unocss/cli': 66.0.0
|
||||
'@unocss/core': 66.0.0
|
||||
'@unocss/postcss': 66.0.0(postcss@8.5.6)
|
||||
'@unocss/preset-attributify': 66.0.0
|
||||
'@unocss/preset-icons': 66.0.0
|
||||
'@unocss/preset-mini': 66.0.0
|
||||
'@unocss/preset-tagify': 66.0.0
|
||||
'@unocss/preset-typography': 66.0.0
|
||||
'@unocss/preset-uno': 66.0.0
|
||||
'@unocss/preset-web-fonts': 66.0.0
|
||||
'@unocss/preset-wind': 66.0.0
|
||||
'@unocss/preset-wind3': 66.0.0
|
||||
'@unocss/transformer-attributify-jsx': 66.0.0
|
||||
'@unocss/transformer-compile-class': 66.0.0
|
||||
'@unocss/transformer-directives': 66.0.0
|
||||
'@unocss/transformer-variant-group': 66.0.0
|
||||
'@unocss/vite': 66.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))(vue@3.5.13(typescript@5.7.3))
|
||||
'@unocss/astro': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))
|
||||
'@unocss/cli': 66.4.2
|
||||
'@unocss/core': 66.4.2
|
||||
'@unocss/postcss': 66.4.2(postcss@8.5.6)
|
||||
'@unocss/preset-attributify': 66.4.2
|
||||
'@unocss/preset-icons': 66.4.2
|
||||
'@unocss/preset-mini': 66.4.2
|
||||
'@unocss/preset-tagify': 66.4.2
|
||||
'@unocss/preset-typography': 66.4.2
|
||||
'@unocss/preset-uno': 66.4.2
|
||||
'@unocss/preset-web-fonts': 66.4.2
|
||||
'@unocss/preset-wind': 66.4.2
|
||||
'@unocss/preset-wind3': 66.4.2
|
||||
'@unocss/preset-wind4': 66.4.2
|
||||
'@unocss/transformer-attributify-jsx': 66.4.2
|
||||
'@unocss/transformer-compile-class': 66.4.2
|
||||
'@unocss/transformer-directives': 66.4.2
|
||||
'@unocss/transformer-variant-group': 66.4.2
|
||||
'@unocss/vite': 66.4.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0))
|
||||
optionalDependencies:
|
||||
vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- postcss
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
@@ -22051,9 +22115,7 @@ snapshots:
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-flow-layout@0.1.1(vue@3.5.13(typescript@5.7.3)):
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.7.3)
|
||||
vue-flow-layout@0.2.0: {}
|
||||
|
||||
vue@3.5.13(typescript@5.7.3):
|
||||
dependencies:
|
||||
|
Reference in New Issue
Block a user