5432 WIP, parsing works

This commit is contained in:
Knut Sveidqvist
2024-10-06 16:55:57 +02:00
parent 2d7686eb65
commit fabdfd9ae8
11 changed files with 481 additions and 549 deletions

View File

@@ -83,349 +83,16 @@
</head> </head>
<body> <body>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
layout: elk
elk:
<!-- nodePlacementStrategy: INTERACTIVE -->
<!-- mergeEdges: true -->
---
stateDiagram-v2
direction LR
accTitle: An idealized Open Source supply-chain graph
%%
state "🟦 Importer" as author_importer
state "🟥 Supplier, Owner" as author_owner
state "🟨🟥 Maintainer, Author\n🟨 Custodian" as author
state "🟩 Distributor" as repository_distributor
state "🟦 Importer" as language_importer
state "🟦🟨 Packager" as language_packager
state "🟦🟨 OSS Steward" as language_steward
state "🟨 Curator" as language_curator
state "🟩 Distributor" as language_distributor
state "🟦 Contributor" as contributor
state "🟦 Importer" as package_importer
state "🟨 Patcher" as package_patcher
state "🟨🟦 Builder\n🟨🟦 Packager\n🟨🟦 Containerizer" as package_packager
state "🟨 Curator" as package_curator
state "🟩 Distributor" as package_distributor
state "🟦 Importer" as integrator_importer
state "🟥 Supplier, Manufacturer, Owner" as integrator_owner
state "🟦🟨🟥 Integrator, Developer" as integrator_developer
state "🟩🟨 SBOM Redactor\n🟩 Publisher" as integrator_publisher
state "🟦🟨 Builder" as integrator_builder
state "🟨 Deployer" as deployer
state "🟦 Vuln. Checker" as integrator_checker
state "🟩🟨 SBOM Redactor" as redactor
state "🟦 Consumer\n🟦 User" as consumer
state "🟦 Auditor" as auditor_internal
state "🟦 Auditor" as auditor_external
%%
classDef createsSBOM stroke:red,stroke-width:3px;
classDef updatesSBOM stroke:yellow,stroke-width:3px;
classDef assemblesSBOM stroke:yellow,stroke-width:3px;
classDef distributesSBOM stroke:green,stroke-width:3px;
classDef verifiesSBOM stroke:#07f,stroke-width:3px;
%%
class author_importer verifiesSBOM
class author_owner createsSBOM
class manufacturer_owner createsSBOM
class author assemblesSBOM
class package_importer verifiesSBOM
class package_patcher updatesSBOM
class package_packager assemblesSBOM
class package_curator distributesSBOM
class package_distributor distributesSBOM
class language_importer verifiesSBOM
class language_packager assemblesSBOM
class language_steward updatesSBOM
class language_curator distributesSBOM
class language_distributor distributesSBOM
class repository_distributor distributesSBOM
class integrator_importer verifiesSBOM
class integrator_owner createsSBOM
class integrator_developer assemblesSBOM
class integrator_publisher distributesSBOM
class integrator_builder assemblesSBOM
class integrator_checker verifiesSBOM
class deployer assemblesSBOM
class redactor distributesSBOM
class auditor_internal verifiesSBOM
class auditor_external verifiesSBOM
state "Maintainer Environment" as environment_maintainer {
[*] --> author_importer
[*] --> author
author_importer --> author
author_owner --> author
author --> language_packager
}
[*] --> environment_maintainer
state "Language Ecosystem" as ecosystem_lang {
[*] --> language_importer
[*] --> language_steward
[*] --> language_curator
[*] --> language_distributor
language_importer --> language_distributor
language_importer --> language_curator
language_steward --> language_curator
language_curator --> language_distributor
}
language_packager --> ecosystem_lang
ecosystem_lang --> ecosystem_lang
state "Public Collaboration Ecosystem" as ecosystem_repo {
[*] --> repository_distributor
}
author --> ecosystem_repo
ecosystem_repo --> author
repository_distributor --> contributor
contributor --> repository_distributor
state "Package Ecosystem" as ecosystem_package {
[*] --> package_importer
[*] --> package_packager
[*] --> package_patcher
package_importer --> package_patcher
package_importer --> package_packager
package_patcher --> package_packager
package_packager --> package_curator
package_packager --> package_distributor
package_curator --> package_distributor
}
repository_distributor --> ecosystem_package
language_distributor --> ecosystem_package
ecosystem_package --> ecosystem_package
state "Integrator Environment" as environment_integrator {
[*] --> integrator_developer
[*] --> integrator_importer
integrator_importer --> integrator_developer
integrator_owner --> integrator_developer
integrator_builder --> integrator_publisher
integrator_developer --> integrator_checker
integrator_checker --> integrator_developer
auditor_internal --> integrator_developer
integrator_developer --> integrator_builder
integrator_developer --> auditor_internal
}
repository_distributor --> environment_integrator
language_distributor --> environment_integrator
package_distributor --> environment_integrator
state "Production Environment" as environment_prod {
[*] --> deployer
deployer --> redactor
}
integrator_publisher --> [*]
integrator_developer --> environment_prod
integrator_builder --> environment_prod
integrator_publisher --> environment_prod
deployer --> auditor_external
deployer --> consumer
redactor --> consumer
</pre>
<pre id="diagram" class="mermaid2">
---
config:
look: neo
---
flowchart RL
subgraph " "
A5@{ shape: manual-file, label: "a label"}
B5@{ shape: manual-input, label: "a label" }
C5@{ shape: mul-doc, label: "a label" }
D5@{ shape: mul-proc, label: "a label" }
E5@{ shape: paper-tape, label: "a label" }
B3@{ shape: das, label: "a label" }
C3@{ shape: disk, label: "a label" }
D4@{ shape: lin-doc, label: "a label" }
E4@{ shape: loop-limit, label: "a label" }
end
subgraph " "
B6@{ shape: summary, label: "a label" }
C6@{ shape: tag-we-rect, label: "a label" }
D6@{ shape: tag-rect, label: "a label" }
A2@{ shape: fork}
B2@{ shape: hourglass }
C2@{ shape: comment, label: "I am a comment" }
D2@{ shape: bolt }
D3@{ shape: disp, label: "a label" }
C4@{ shape: junction, label: "a label" }
A4@{ shape: extract, label: "a label"}
B52[a fr]@{ shape: fr }
end
subgraph " "
A1@{ shape: text, label: This is a textblock}
B1@{ shape: card, label: "a label" }
C1@{ shape: lined-proc, label: "a label" }
D1@{ shape: start, label: "a label" }
E1@{ shape: stop, label: "a label" }
E2@{ shape: doc, label: "a label" }
A6@{ shape: stored-data, label: "a label"}
A3@{ shape: delay, label: "a label" }
E3@{ shape: div-proc, label: "a label" }
B4[a label]@{ shape: win-pane }
end
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
elk:
<!-- nodePlacementStrategy: SIMPLE -->
---
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TD
A([Start]) -->|go to booking page| B("select
ISBS booking no")
A --> QQ{cancel booking}
A --> RR{no show}
A --> SS{change booking}
B -->C(wmpay_request_payment.request_type= 'partial',
wmpay_request_payment.status= 'paid',
pos_booking.booking_status= partial and 'full_deposit')
style C text-align:left
C -->D{manage booking}
D -->|cancel|E[ระบบแสดงช่องให้กรอกเหตุผล]
E -->F{กดปุ่ม 'cancel' หรือไม่}
F -->|Yes|G[ระบบบันทึกค่าใหม่
และไม่สามารถแก้ไขข้อมูลได้]
F -->|No|H[กดปุ่ม 'close']
H -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
G -->|ระบบส่งข้อมูล|I[(POS_database)]
I -->|pos_booking.booking_status='cancel'|Z([End])
D -->|no show|J[ระบบแสดงช่องให้กรอกเหตุผล]
J -->K{กดปุ่ม 'noshow' หรือไม่}
K -->|Yes|L[ระบบสร้างใบเสร็จอัตโนมัติ
Product_id: 439,
ItemName: no show]
style L text-align:left
K -->|No|O[กดปุ่ม 'close']
O -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
L -->M[ระบบบันทึกค่าใหม่]
M -->|ระบบส่งข้อมูล|N[(POS_database)]
N -->|pos_booking.booking_status=noshow|Z
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
layout: dagre
elk:
nodePlacementStrategy: BRANDES_KOEPF
---
flowchart
A --> A
subgraph A
B --> B
subgraph B
C
end
end
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
look: handdrawn
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? handdrawn - true]
</pre
>
</div>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: false
---
flowchart
A[I am a long text, where do I go??? classic - false]
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? classic - true]
</pre
>
</div>
<pre id="diagram2" class="mermaid2">
flowchart LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
</pre>
<pre id="diagram3" class="mermaid2">
flowchart LR
A:::foo & B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef ash color:red
class C ash
style C stroke:#00f, fill:black
</pre>
<pre id="diagram4" class="mermaid2">
stateDiagram
A:::foo
B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
style C stroke:#00f, fill:black, color:white
</pre>
<pre id="diagram4" class="mermaid"> <pre id="diagram4" class="mermaid">
flowchart TB kanban
A@{ id1[Todo]
label: "aksljhf kasjdh" id2[Create JISON]
} id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
</pre> </pre>
<script type="module"> <script type="module">
import mermaid from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
@@ -456,7 +123,7 @@ flowchart TB
messageFontFamily: 'courier', messageFontFamily: 'courier',
}, },
fontSize: 12, fontSize: 12,
logLevel: 3, logLevel: 0,
securityLevel: 'loose', securityLevel: 'loose',
}); });
function callback() { function callback() {

View File

@@ -19,6 +19,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js';
import flowchartElk from '../diagrams/flowchart/elk/detector.js'; import flowchartElk from '../diagrams/flowchart/elk/detector.js';
import timeline from '../diagrams/timeline/detector.js'; import timeline from '../diagrams/timeline/detector.js';
import mindmap from '../diagrams/mindmap/detector.js'; import mindmap from '../diagrams/mindmap/detector.js';
import kanban from '../diagrams/kanban/detector.js';
import sankey from '../diagrams/sankey/sankeyDetector.js'; import sankey from '../diagrams/sankey/sankeyDetector.js';
import { packet } from '../diagrams/packet/detector.js'; import { packet } from '../diagrams/packet/detector.js';
import block from '../diagrams/block/blockDetector.js'; import block from '../diagrams/block/blockDetector.js';
@@ -70,6 +71,7 @@ export const addDiagrams = () => {
// Ordering of detectors is important. The first one to return true will be used. // Ordering of detectors is important. The first one to return true will be used.
registerLazyLoadedDiagrams( registerLazyLoadedDiagrams(
c4, c4,
kanban,
classDiagramV2, classDiagramV2,
classDiagram, classDiagram,
er, er,

View File

@@ -1,5 +1,5 @@
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/mindmap.jison'; import parser from './parser/kanban.jison';
import db from './kanbanDb.js'; import db from './kanbanDb.js';
import renderer from './kanbanRenderer.js'; import renderer from './kanbanRenderer.js';
import styles from './styles.js'; import styles from './styles.js';

View File

@@ -16,8 +16,9 @@ describe('when parsing a kanban ', function () {
root`; root`;
kanban.parse(str); kanban.parse(str);
// console.log('Time for checks', kanban.yy.getMindmap().descr); const sections = kanban.yy.getSections();
expect(kanban.yy.getMindmap().descr).toEqual('root'); expect(sections.length).toEqual(1);
expect(sections[0].descr).toEqual('root');
}); });
it('KNBN-2 should handle a hierachial kanban definition', function () { it('KNBN-2 should handle a hierachial kanban definition', function () {
const str = `kanban const str = `kanban
@@ -27,45 +28,59 @@ describe('when parsing a kanban ', function () {
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('root'); expect(sections.length).toEqual(1);
expect(mm.children.length).toEqual(2); expect(sections[0].descr).toEqual('root');
expect(mm.children[0].descr).toEqual('child1'); expect(sections[0].children.length).toEqual(2);
expect(mm.children[1].descr).toEqual('child2'); expect(sections[0].children[0].descr).toEqual('child1');
expect(sections[0].children[1].descr).toEqual('child2');
}); });
/** CATCH case when a lower level comes later, should throw
* a
* b
* c
*/
it('3 should handle a simple root definition with a shape and without an id abc123', function () { it('3 should handle a simple root definition with a shape and without an id abc123', function () {
const str = `kanban const str = `kanban
(root)`; (root)`;
kanban.parse(str); kanban.parse(str);
// console.log('Time for checks', kanban.yy.getMindmap().descr); const sections = kanban.yy.getSections();
expect(kanban.yy.getMindmap().descr).toEqual('root'); expect(sections[0].descr).toEqual('root');
}); });
it('KNBN-4 should handle a deeper hierachial kanban definition', function () { it('KNBN-4 should not dsitinguis between deeper hierachial levels in thr kanban definition', function () {
const str = `kanban const str = `kanban
root root
child1 child1
leaf1 leaf1
child2`; child2`;
kanban.parse(str); // less picky is better
const mm = kanban.yy.getMindmap(); // expect(() => kanban.parse(str)).toThrow(
expect(mm.descr).toEqual('root'); // 'There can be only one root. No parent could be found for ("fakeRoot")'
expect(mm.children.length).toEqual(2); // );
expect(mm.children[0].descr).toEqual('child1');
expect(mm.children[0].children[0].descr).toEqual('leaf1');
expect(mm.children[1].descr).toEqual('child2');
});
it('5 Multiple roots are illegal', function () {
const str = `kanban
root
fakeRoot`;
expect(() => kanban.parse(str)).toThrow( kanban.parse(str);
'There can be only one root. No parent could be found for ("fakeRoot")' const sections = kanban.yy.getSections();
); expect(sections.length).toBe(1);
expect(sections[0].children.length).toBe(3);
});
it('5 Multiple sections are ok', function () {
const str = `kanban
section1
section2`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections.length).toBe(2);
expect(sections[0].descr).toBe('section1');
expect(sections[1].descr).toBe('section2');
// expect(() => kanban.parse(str)).toThrow(
// 'There can be only one root. No parent could be found for ("fakeRoot")'
// );
}); });
it('KNBN-6 real root in wrong place', function () { it('KNBN-6 real root in wrong place', function () {
const str = `kanban const str = `kanban
@@ -73,7 +88,7 @@ describe('when parsing a kanban ', function () {
fakeRoot fakeRoot
realRootWrongPlace`; realRootWrongPlace`;
expect(() => kanban.parse(str)).toThrow( expect(() => kanban.parse(str)).toThrow(
'There can be only one root. No parent could be found for ("fakeRoot")' 'Items without section detected, found section ("fakeRoot")'
); );
}); });
}); });
@@ -84,10 +99,10 @@ describe('when parsing a kanban ', function () {
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('The root'); expect(sections[0].descr).toEqual('The root');
expect(mm.type).toEqual(kanban.yy.nodeType.RECT); expect(sections[0].type).toEqual(kanban.yy.nodeType.RECT);
}); });
it('KNBN-8 should handle an id and type for a node definition', function () { it('KNBN-8 should handle an id and type for a node definition', function () {
const str = `kanban const str = `kanban
@@ -95,10 +110,10 @@ describe('when parsing a kanban ', function () {
theId(child1)`; theId(child1)`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('root'); expect(sections[0].descr).toEqual('root');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(1);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.descr).toEqual('child1'); expect(child.descr).toEqual('child1');
expect(child.nodeId).toEqual('theId'); expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(kanban.yy.nodeType.ROUNDED_RECT); expect(child.type).toEqual(kanban.yy.nodeType.ROUNDED_RECT);
@@ -109,10 +124,10 @@ root
theId(child1)`; theId(child1)`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('root'); expect(sections[0].descr).toEqual('root');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(1);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.descr).toEqual('child1'); expect(child.descr).toEqual('child1');
expect(child.nodeId).toEqual('theId'); expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(kanban.yy.nodeType.ROUNDED_RECT); expect(child.type).toEqual(kanban.yy.nodeType.ROUNDED_RECT);
@@ -123,10 +138,10 @@ root
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('the root'); expect(sections[0].descr).toEqual('the root');
expect(mm.children.length).toEqual(0); expect(sections[0].children.length).toEqual(0);
expect(mm.type).toEqual(kanban.yy.nodeType.CIRCLE); expect(sections[0].type).toEqual(kanban.yy.nodeType.CIRCLE);
}); });
it('KNBN-11 multiple types (cloud)', function () { it('KNBN-11 multiple types (cloud)', function () {
@@ -135,10 +150,10 @@ root
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('the root'); expect(sections[0].descr).toEqual('the root');
expect(mm.children.length).toEqual(0); expect(sections[0].children.length).toEqual(0);
expect(mm.type).toEqual(kanban.yy.nodeType.CLOUD); expect(sections[0].type).toEqual(kanban.yy.nodeType.CLOUD);
}); });
it('KNBN-12 multiple types (bang)', function () { it('KNBN-12 multiple types (bang)', function () {
const str = `kanban const str = `kanban
@@ -146,10 +161,10 @@ root
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.descr).toEqual('the root'); expect(sections[0].descr).toEqual('the root');
expect(mm.children.length).toEqual(0); expect(sections[0].children.length).toEqual(0);
expect(mm.type).toEqual(kanban.yy.nodeType.BANG); expect(sections[0].type).toEqual(kanban.yy.nodeType.BANG);
}); });
it('KNBN-12-a multiple types (hexagon)', function () { it('KNBN-12-a multiple types (hexagon)', function () {
@@ -158,10 +173,10 @@ root
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.type).toEqual(kanban.yy.nodeType.HEXAGON); expect(sections[0].type).toEqual(kanban.yy.nodeType.HEXAGON);
expect(mm.descr).toEqual('the root'); expect(sections[0].descr).toEqual('the root');
expect(mm.children.length).toEqual(0); expect(sections[0].children.length).toEqual(0);
}); });
}); });
describe('decorations', function () { describe('decorations', function () {
@@ -173,11 +188,11 @@ root
// ::class1 class2 // ::class1 class2
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('The root'); expect(sections[0].descr).toEqual('The root');
expect(mm.type).toEqual(kanban.yy.nodeType.RECT); expect(sections[0].type).toEqual(kanban.yy.nodeType.RECT);
expect(mm.icon).toEqual('bomb'); expect(sections[0].icon).toEqual('bomb');
}); });
it('KNBN-14 should be possible to set classes for the node', function () { it('KNBN-14 should be possible to set classes for the node', function () {
const str = `kanban const str = `kanban
@@ -187,11 +202,11 @@ root
// ::class1 class2 // ::class1 class2
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('The root'); expect(sections[0].descr).toEqual('The root');
expect(mm.type).toEqual(kanban.yy.nodeType.RECT); expect(sections[0].type).toEqual(kanban.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8'); expect(sections[0].class).toEqual('m-4 p-8');
}); });
it('KNBN-15 should be possible to set both classes and icon for the node', function () { it('KNBN-15 should be possible to set both classes and icon for the node', function () {
const str = `kanban const str = `kanban
@@ -202,12 +217,12 @@ root
// ::class1 class2 // ::class1 class2
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('The root'); expect(sections[0].descr).toEqual('The root');
expect(mm.type).toEqual(kanban.yy.nodeType.RECT); expect(sections[0].type).toEqual(kanban.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8'); expect(sections[0].class).toEqual('m-4 p-8');
expect(mm.icon).toEqual('bomb'); expect(sections[0].icon).toEqual('bomb');
}); });
it('KNBN-16 should be possible to set both classes and icon for the node', function () { it('KNBN-16 should be possible to set both classes and icon for the node', function () {
const str = `kanban const str = `kanban
@@ -218,12 +233,12 @@ root
// ::class1 class2 // ::class1 class2
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('The root'); expect(sections[0].descr).toEqual('The root');
expect(mm.type).toEqual(kanban.yy.nodeType.RECT); expect(sections[0].type).toEqual(kanban.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8'); expect(sections[0].class).toEqual('m-4 p-8');
expect(mm.icon).toEqual('bomb'); expect(sections[0].icon).toEqual('bomb');
}); });
}); });
describe('descriptions', function () { describe('descriptions', function () {
@@ -232,9 +247,9 @@ root
root["String containing []"] root["String containing []"]
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []'); expect(sections[0].descr).toEqual('String containing []');
}); });
it('KNBN-18 should be possible to use node syntax in the descriptions in children', function () { it('KNBN-18 should be possible to use node syntax in the descriptions in children', function () {
const str = `kanban const str = `kanban
@@ -242,11 +257,11 @@ root
child1["String containing ()"] child1["String containing ()"]
`; `;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []'); expect(sections[0].descr).toEqual('String containing []');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(1);
expect(mm.children[0].descr).toEqual('String containing ()'); expect(sections[0].children[0].descr).toEqual('String containing ()');
}); });
it('KNBN-19 should be possible to have a child after a class assignment', function () { it('KNBN-19 should be possible to have a child after a class assignment', function () {
const str = `kanban const str = `kanban
@@ -256,16 +271,17 @@ root
a(a) a(a)
b[New Stuff]`; b[New Stuff]`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('Root'); expect(sections[0].descr).toEqual('Root');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(3);
const child = mm.children[0]; const item1 = sections[0].children[0];
expect(child.nodeId).toEqual('Child'); const item2 = sections[0].children[1];
expect(child.children[0].nodeId).toEqual('a'); const item3 = sections[0].children[2];
expect(child.children.length).toEqual(2); expect(item1.nodeId).toEqual('Child');
expect(child.children[1].nodeId).toEqual('b'); expect(item2.nodeId).toEqual('a');
expect(item3.nodeId).toEqual('b');
}); });
}); });
it('KNBN-20 should be possible to have meaningless empty rows in a kanban abc124', function () { it('KNBN-20 should be possible to have meaningless empty rows in a kanban abc124', function () {
@@ -276,16 +292,17 @@ root
b[New Stuff]`; b[New Stuff]`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('Root'); expect(sections[0].descr).toEqual('Root');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(3);
const child = mm.children[0]; const item1 = sections[0].children[0];
expect(child.nodeId).toEqual('Child'); const item2 = sections[0].children[1];
expect(child.children[0].nodeId).toEqual('a'); const item3 = sections[0].children[2];
expect(child.children.length).toEqual(2); expect(item1.nodeId).toEqual('Child');
expect(child.children[1].nodeId).toEqual('b'); expect(item2.nodeId).toEqual('a');
expect(item3.nodeId).toEqual('b');
}); });
it('KNBN-21 should be possible to have comments in a kanban', function () { it('KNBN-21 should be possible to have comments in a kanban', function () {
const str = `kanban const str = `kanban
@@ -296,16 +313,15 @@ root
%% This is a comment %% This is a comment
b[New Stuff]`; b[New Stuff]`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('Root'); expect(sections[0].descr).toEqual('Root');
expect(mm.children.length).toEqual(1);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.nodeId).toEqual('Child'); expect(child.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a'); expect(sections[0].children[1].nodeId).toEqual('a');
expect(child.children.length).toEqual(2); expect(sections[0].children[2].nodeId).toEqual('b');
expect(child.children[1].nodeId).toEqual('b'); expect(sections[0].children.length).toEqual(3);
}); });
it('KNBN-22 should be possible to have comments at the end of a line', function () { it('KNBN-22 should be possible to have comments at the end of a line', function () {
@@ -315,51 +331,52 @@ root
a(a) %% This is a comment a(a) %% This is a comment
b[New Stuff]`; b[New Stuff]`;
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.descr).toEqual('Root'); expect(sections[0].descr).toEqual('Root');
expect(mm.children.length).toEqual(1); expect(sections[0].children.length).toEqual(3);
const child = mm.children[0]; const child1 = sections[0].children[0];
expect(child.nodeId).toEqual('Child'); expect(child1.nodeId).toEqual('Child');
expect(child.children[0].nodeId).toEqual('a'); const child2 = sections[0].children[1];
expect(child.children.length).toEqual(2); expect(child2.nodeId).toEqual('a');
expect(child.children[1].nodeId).toEqual('b'); const child3 = sections[0].children[2];
expect(child3.nodeId).toEqual('b');
}); });
it('KNBN-23 Rows with only spaces should not interfere', function () { it('KNBN-23 Rows with only spaces should not interfere', function () {
const str = 'kanban\nroot\n A\n \n\n B'; const str = 'kanban\nroot\n A\n \n\n B';
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.children.length).toEqual(2); expect(sections[0].children.length).toEqual(2);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.nodeId).toEqual('A'); expect(child.nodeId).toEqual('A');
const child2 = mm.children[1]; const child2 = sections[0].children[1];
expect(child2.nodeId).toEqual('B'); expect(child2.nodeId).toEqual('B');
}); });
it('KNBN-24 Handle rows above the kanban declarations', function () { it('KNBN-24 Handle rows above the kanban declarations', function () {
const str = '\n \nkanban\nroot\n A\n \n\n B'; const str = '\n \nkanban\nroot\n A\n \n\n B';
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.children.length).toEqual(2); expect(sections[0].children.length).toEqual(2);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.nodeId).toEqual('A'); expect(child.nodeId).toEqual('A');
const child2 = mm.children[1]; const child2 = sections[0].children[1];
expect(child2.nodeId).toEqual('B'); expect(child2.nodeId).toEqual('B');
}); });
it('KNBN-25 Handle rows above the kanban declarations, no space', function () { it('KNBN-25 Handle rows above the kanban declarations, no space', function () {
const str = '\n\n\nkanban\nroot\n A\n \n\n B'; const str = '\n\n\nkanban\nroot\n A\n \n\n B';
kanban.parse(str); kanban.parse(str);
const mm = kanban.yy.getMindmap(); const sections = kanban.yy.getSections();
expect(mm.nodeId).toEqual('root'); expect(sections[0].nodeId).toEqual('root');
expect(mm.children.length).toEqual(2); expect(sections[0].children.length).toEqual(2);
const child = mm.children[0]; const child = sections[0].children[0];
expect(child.nodeId).toEqual('A'); expect(child.nodeId).toEqual('A');
const child2 = mm.children[1]; const child2 = sections[0].children[1];
expect(child2.nodeId).toEqual('B'); expect(child2.nodeId).toEqual('B');
}); });
}); });

View File

@@ -2,35 +2,75 @@ import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { D3Element } from '../../types.js'; import type { D3Element } from '../../types.js';
import { sanitizeText } from '../../diagrams/common/common.js'; import { sanitizeText } from '../../diagrams/common/common.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import type { MindmapNode } from './kanbanTypes.js'; import type { KanbanNode } from './kanbanTypes.js';
import type { Node, Edge } from '../../rendering-util/types.js';
import defaultConfig from '../../defaultConfig.js'; import defaultConfig from '../../defaultConfig.js';
let nodes: MindmapNode[] = []; let nodes: KanbanNode[] = [];
let sections: KanbanNode[] = [];
let cnt = 0; let cnt = 0;
let elements: Record<number, D3Element> = {}; let elements: Record<number, D3Element> = {};
const clear = () => { const clear = () => {
nodes = []; nodes = [];
sections = [];
cnt = 0; cnt = 0;
elements = {}; elements = {};
}; };
/*
const getParent = function (level: number) { * if your level is the section level return null - then you do not belong to a level
* otherwise return the current section
*/
const getSection = function (level: number) {
if (nodes.length === 0) {
// console.log('No nodes');
return null;
}
const sectionLevel = nodes[0].level;
let lastSection = null;
for (let i = nodes.length - 1; i >= 0; i--) { for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level < level) { if (nodes[i].level === sectionLevel && !lastSection) {
return nodes[i]; lastSection = nodes[i];
// console.log('lastSection found', lastSection);
}
// console.log('HERE', nodes[i].id, level, nodes[i].level, sectionLevel);
if (nodes[i].level < sectionLevel) {
throw new Error('Items without section detected, found section ("' + nodes[i].descr + '")');
} }
} }
// No parent found // if (!lastSection) {
return null; // // console.log('No last section');
// }
if (level === lastSection?.level) {
return null;
}
// No found
return lastSection;
}; };
const getMindmap = () => { const getSections = function () {
return nodes.length > 0 ? nodes[0] : null; console.log('sections', sections);
return sections;
};
const getData = function () {
const edges = [] as Edge[];
const nodes: Node[] = [];
// const id: string = sanitizeText(id, conf) || 'identifier' + cnt++;
// const node = {
// id,
// label: sanitizeText(descr, conf),
// isGroup,
// } satisfies Node;
return { nodes, edges, other: {}, config: getConfig() };
}; };
const addNode = (level: number, id: string, descr: string, type: number) => { const addNode = (level: number, id: string, descr: string, type: number) => {
log.info('addNode', level, id, descr, type); // log.info('addNode level=', level, 'id=', id, 'descr=', descr, 'type=', type);
const conf = getConfig(); const conf = getConfig();
let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding; let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding;
switch (type) { switch (type) {
@@ -49,24 +89,17 @@ const addNode = (level: number, id: string, descr: string, type: number) => {
children: [], children: [],
width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth, width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth,
padding, padding,
} satisfies MindmapNode; } satisfies KanbanNode;
const section = getSection(level);
const parent = getParent(level); console.log('Node ', node.descr, ' section', section?.descr);
if (parent) { if (section) {
parent.children.push(node); section.children.push(node);
// Keep all nodes in the list // Keep all nodes in the list
nodes.push(node); nodes.push(node);
} else { } else {
if (nodes.length === 0) { sections.push(node);
// First node, the root
nodes.push(node);
} else {
// Syntax error ... there can only bee one root
throw new Error(
'There can be only one root. No parent could be found for ("' + node.descr + '")'
);
}
} }
nodes.push(node);
}; };
const nodeType = { const nodeType = {
@@ -146,7 +179,8 @@ const getElementById = (id: number) => elements[id];
const db = { const db = {
clear, clear,
addNode, addNode,
getMindmap, getSections,
getData,
nodeType, nodeType,
getType, getType,
setElementForId, setElementForId,

View File

@@ -9,29 +9,13 @@ import { log } from '../../logger.js';
import type { D3Element } from '../../types.js'; import type { D3Element } from '../../types.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { FilledMindMapNode, MindmapDB, MindmapNode } from './kanbanTypes.js'; import type { FilledKanbanNode, KanbanDB, KanbanNode } from './kanbanTypes.js';
import { drawNode, positionNode } from './svgDraw.js'; import { drawNode, positionNode } from './svgDraw.js';
import defaultConfig from '../../defaultConfig.js'; import defaultConfig from '../../defaultConfig.js';
// Inject the layout algorithm into cytoscape // Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
async function drawNodes( async function drawSection(section: FilledKanbanNode, svg: D3Element, conf: MermaidConfig) {}
db: MindmapDB,
svg: D3Element,
mindmap: FilledMindMapNode,
section: number,
conf: MermaidConfig
) {
await drawNode(db, svg, mindmap, section, conf);
if (mindmap.children) {
await Promise.all(
mindmap.children.map((child, index) =>
drawNodes(db, svg, child, section < 0 ? index : section, conf)
)
);
}
}
declare module 'cytoscape' { declare module 'cytoscape' {
interface EdgeSingular { interface EdgeSingular {
@@ -66,7 +50,7 @@ function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
}); });
} }
function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) { function addNodes(mindmap: KanbanNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
cy.add({ cy.add({
group: 'nodes', group: 'nodes',
data: { data: {
@@ -101,7 +85,7 @@ function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig,
} }
} }
function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> { function layoutMindmap(node: KanbanNode, conf: MermaidConfig): Promise<cytoscape.Core> {
return new Promise((resolve) => { return new Promise((resolve) => {
// Add temporary render element // Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@@ -142,7 +126,7 @@ function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscap
}); });
} }
function positionNodes(db: MindmapDB, cy: cytoscape.Core) { function positionNodes(db: KanbanDB, cy: cytoscape.Core) {
cy.nodes().map((node, id) => { cy.nodes().map((node, id) => {
const data = node.data(); const data = node.data();
data.x = node.position().x; data.x = node.position().x;
@@ -161,9 +145,10 @@ function positionNodes(db: MindmapDB, cy: cytoscape.Core) {
export const draw: DrawDefinition = async (text, id, _version, diagObj) => { export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering mindmap diagram\n' + text); log.debug('Rendering mindmap diagram\n' + text);
const db = diagObj.db as MindmapDB; const db = diagObj.db as KanbanDB;
const mm = db.getMindmap(); const sections = db.getSections();
if (!mm) { // const sections = db.getData();
if (!sections) {
return; return;
} }
@@ -176,14 +161,14 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
// this gives us the size of the nodes and we can set the positions later // this gives us the size of the nodes and we can set the positions later
const edgesElem = svg.append('g'); const edgesElem = svg.append('g');
edgesElem.attr('class', 'mindmap-edges'); edgesElem.attr('class', 'sections');
const nodesElem = svg.append('g'); const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes'); nodesElem.attr('class', 'items');
await drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf); await drawSections(db, nodesElem, sections as FilledKanbanNode, -1, conf);
// Next step is to layout the mindmap, giving each node a position // Next step is to layout the mindmap, giving each node a position
const cy = await layoutMindmap(mm, conf); const cy = await layoutMindmap(sections, conf);
// After this we can draw, first the edges and the then nodes with the correct position // After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy); drawEdges(edgesElem, cy);

View File

@@ -1,13 +1,13 @@
import type { RequiredDeep } from 'type-fest'; import type { RequiredDeep } from 'type-fest';
import type mindmapDb from './kanbanDb.js'; import type kanbanDb from './kanbanDb.js';
export interface MindmapNode { export interface KanbanNode {
id: number; id: number;
nodeId: string; nodeId: string;
level: number; level: number;
descr: string; descr: string;
type: number; type: number;
children: MindmapNode[]; children: KanbanNode[];
width: number; width: number;
padding: number; padding: number;
section?: number; section?: number;
@@ -18,5 +18,5 @@ export interface MindmapNode {
y?: number; y?: number;
} }
export type FilledMindMapNode = RequiredDeep<MindmapNode>; export type FilledKanbanNode = RequiredDeep<KanbanNode>;
export type MindmapDB = typeof mindmapDb; export type KanbanDB = typeof kanbanDb;

View File

@@ -20,7 +20,7 @@
\s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';} \s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';}
// \%\%[^\n]*\n /* skip comments */ // \%\%[^\n]*\n /* skip comments */
"kanban" return 'MINDMAP'; "kanban" return 'KANBAN';
":::" { this.begin('CLASS'); } ":::" { this.begin('CLASS'); }
<CLASS>.+ { this.popState();return 'CLASS'; } <CLASS>.+ { this.popState();return 'CLASS'; }
<CLASS>\n { this.popState();} <CLASS>\n { this.popState();}
@@ -80,8 +80,8 @@ spaceLines
; ;
mindMap mindMap
: MINDMAP document { return yy; } : KANBAN document { return yy; }
| MINDMAP NL document { return yy; } | KANBAN NL document { return yy; }
; ;
stop stop

View File

@@ -0,0 +1,105 @@
```mermaid
kanban
New
Sometimes wrong Shape type is highlighted
In progress
````
```mermaid
kanban
Todo
Create JISON
Update DB function
Create parsing tests
define getData
Create renderer
In progress
Design grammar
````
Adding ID
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
````
Background color for section
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
style n2 stroke:#AA00FF,fill:#E1BEE7
````
Background color for section
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
id2@{
assigned: knsv
icon: heart
priority: high
descr: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
style n1 stroke:#AA00FF,fill:#E1BEE7
````
Background color for section
```mermaid
---
config:
kanban:
showIds: true
fields: [[title],[description][id, assigned]]
---
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
id2@{
assigned: knsv
icon: heart
priority: high
descr: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
style n1 stroke:#AA00FF,fill:#E1BEE7
````
priority - dedicated
link - dedicated

View File

@@ -1,5 +1,5 @@
import { createText } from '../../rendering-util/createText.js'; import { createText } from '../../rendering-util/createText.js';
import type { FilledMindMapNode, MindmapDB } from './kanbanTypes.js'; import type { FilledKanbanNode, KanbanDB } from './kanbanTypes.js';
import type { Point, D3Element } from '../../types.js'; import type { Point, D3Element } from '../../types.js';
import { parseFontSize } from '../../utils.js'; import { parseFontSize } from '../../utils.js';
import type { MermaidConfig } from '../../config.type.js'; import type { MermaidConfig } from '../../config.type.js';
@@ -7,9 +7,9 @@ import type { MermaidConfig } from '../../config.type.js';
const MAX_SECTIONS = 12; const MAX_SECTIONS = 12;
type ShapeFunction = ( type ShapeFunction = (
db: MindmapDB, db: KanbanDB,
elem: D3Element, elem: D3Element,
node: FilledMindMapNode, node: FilledKanbanNode,
section?: number section?: number
) => void; ) => void;
@@ -120,7 +120,7 @@ function insertPolygonShape(
w: number, w: number,
h: number, h: number,
points: Point[], points: Point[],
node: FilledMindMapNode node: FilledKanbanNode
) { ) {
return parent return parent
.insert('polygon', ':first-child') .insert('polygon', ':first-child')
@@ -136,9 +136,9 @@ function insertPolygonShape(
} }
const hexagonBkg: ShapeFunction = function ( const hexagonBkg: ShapeFunction = function (
_db: MindmapDB, _db: KanbanDB,
elem: D3Element, elem: D3Element,
node: FilledMindMapNode node: FilledKanbanNode
) { ) {
const h = node.height; const h = node.height;
const f = 4; const f = 4;
@@ -175,9 +175,9 @@ const roundedRectBkg: ShapeFunction = function (db, elem, node) {
* @returns The height nodes dom element * @returns The height nodes dom element
*/ */
export const drawNode = async function ( export const drawNode = async function (
db: MindmapDB, db: KanbanDB,
elem: D3Element, elem: D3Element,
node: FilledMindMapNode, node: FilledKanbanNode,
fullSection: number, fullSection: number,
conf: MermaidConfig conf: MermaidConfig
): Promise<number> { ): Promise<number> {
@@ -298,7 +298,7 @@ export const drawNode = async function (
return node.height; return node.height;
}; };
export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) { export const positionNode = function (db: KanbanDB, node: FilledKanbanNode) {
const nodeElem = db.getElementById(node.id); const nodeElem = db.getElementById(node.id);
const x = node.x || 0; const x = node.x || 0;

View File

@@ -280,6 +280,127 @@ const roundedWithTitle = async (parent, node) => {
return { cluster: shapeSvg, labelBBox: bbox }; return { cluster: shapeSvg, labelBBox: bbox };
}; };
const kanbanSection = async (parent, node) => {
const siteConfig = getConfig();
const { themeVariables, handDrawnSeed } = siteConfig;
const { altBackground, compositeBackground, compositeTitleBackground, nodeBorder } =
themeVariables;
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', node.cssClasses)
.attr('id', node.id)
.attr('data-id', node.id)
.attr('data-look', node.look);
// add the rect
const outerRectG = shapeSvg.insert('g', ':first-child');
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
let innerRect = shapeSvg.append('rect');
const text = label
.node()
.appendChild(await createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
// Rounded With Title
const padding = 0 * node.padding;
const halfPadding = padding / 2;
const width =
(node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width) + padding;
if (node.width <= bbox.width + node.padding) {
node.diff = (width - node.width) / 2 - node.padding;
} else {
node.diff = -node.padding;
}
const height = node.height + padding;
// const height = node.height + padding;
const innerHeight = node.height + padding - bbox.height - 6;
const x = node.x - width / 2;
const y = node.y - height / 2;
node.width = width;
const innerY = node.y - node.height / 2 - halfPadding + bbox.height + 2;
// add the rect
let rect;
if (node.look === 'handDrawn') {
const isAlt = node.cssClasses.includes('statediagram-cluster-alt');
const rc = rough.svg(shapeSvg);
const roughOuterNode =
node.rx || node.ry
? rc.path(createRoundedRectPathD(x, y, width, height, 10), {
roughness: 0.7,
fill: compositeTitleBackground,
fillStyle: 'solid',
stroke: nodeBorder,
seed: handDrawnSeed,
})
: rc.rectangle(x, y, width, height, { seed: handDrawnSeed });
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
const roughInnerNode = rc.rectangle(x, innerY, width, innerHeight, {
fill: isAlt ? altBackground : compositeBackground,
fillStyle: isAlt ? 'hachure' : 'solid',
stroke: nodeBorder,
seed: handDrawnSeed,
});
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
innerRect = shapeSvg.insert(() => roughInnerNode);
} else {
rect = outerRectG.insert('rect', ':first-child');
const outerRectClass = 'outer';
// center the rect around its coordinate
rect
.attr('class', outerRectClass)
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', height)
.attr('data-look', node.look);
innerRect
.attr('class', 'inner')
.attr('x', x)
.attr('y', innerY)
.attr('width', width)
.attr('height', innerHeight);
}
label.attr(
'transform',
`translate(${node.x - bbox.width / 2}, ${y + 1 - (evaluate(siteConfig.flowchart.htmlLabels) ? 0 : 3)})`
);
const rectBox = rect.node().getBBox();
node.height = rectBox.height;
node.offsetX = 0;
// Used by layout engine to position subgraph in parent
node.offsetY = bbox.height - node.padding / 2;
node.labelBBox = bbox;
node.intersect = function (point) {
return intersectRect(node, point);
};
return { cluster: shapeSvg, labelBBox: bbox };
};
const divider = (parent, node) => { const divider = (parent, node) => {
const siteConfig = getConfig(); const siteConfig = getConfig();
@@ -355,6 +476,7 @@ const shapes = {
roundedWithTitle, roundedWithTitle,
noteGroup, noteGroup,
divider, divider,
kanbanSection,
}; };
let clusterElems = new Map(); let clusterElems = new Map();