mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Adding ticket handling
This commit is contained in:
parent
93f2c241b8
commit
290c678dc7
@ -19,6 +19,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
|||||||
'xyChart',
|
'xyChart',
|
||||||
'requirement',
|
'requirement',
|
||||||
'mindmap',
|
'mindmap',
|
||||||
|
'kanban',
|
||||||
'timeline',
|
'timeline',
|
||||||
'gitGraph',
|
'gitGraph',
|
||||||
'c4',
|
'c4',
|
||||||
|
@ -12,6 +12,7 @@ gantt
|
|||||||
gitgraph
|
gitgraph
|
||||||
gzipped
|
gzipped
|
||||||
handDrawn
|
handDrawn
|
||||||
|
kanban
|
||||||
knsv
|
knsv
|
||||||
Knut
|
Knut
|
||||||
marginx
|
marginx
|
||||||
|
@ -84,19 +84,27 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<pre id="diagram4" class="mermaid">
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
kanban:
|
||||||
|
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
|
||||||
|
---
|
||||||
kanban
|
kanban
|
||||||
id1[Todo]
|
id1[Todo]
|
||||||
id2[Create JISON]
|
docs[Create Documentation]
|
||||||
id3[Update DB function]
|
docs[Create Blog about the new diagram]
|
||||||
id4[Create parsing tests]
|
|
||||||
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes.]
|
|
||||||
id66[last item]
|
|
||||||
id7[In progress]
|
id7[In progress]
|
||||||
id8[Design grammar]
|
|
||||||
id9[Ready for deploy]
|
id9[Ready for deploy]
|
||||||
id10[Ready for test]
|
id10[Ready for test]
|
||||||
id5[define getData]
|
|
||||||
id11[Done]
|
id11[Done]
|
||||||
|
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes. And some more just for the extra flare.]
|
||||||
|
id8[Design grammar]@{ assigned: 'knsv' }
|
||||||
|
id5[define getData]
|
||||||
|
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
|
||||||
|
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
|
||||||
|
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
|
||||||
|
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
|
||||||
|
|
||||||
id12[Can't reproduce]
|
id12[Can't reproduce]
|
||||||
</pre>
|
</pre>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
@ -193,6 +193,7 @@ export interface MermaidConfig {
|
|||||||
requirement?: RequirementDiagramConfig;
|
requirement?: RequirementDiagramConfig;
|
||||||
architecture?: ArchitectureDiagramConfig;
|
architecture?: ArchitectureDiagramConfig;
|
||||||
mindmap?: MindmapDiagramConfig;
|
mindmap?: MindmapDiagramConfig;
|
||||||
|
kanban?: KanbanDiagramConfig;
|
||||||
gitGraph?: GitGraphDiagramConfig;
|
gitGraph?: GitGraphDiagramConfig;
|
||||||
c4?: C4DiagramConfig;
|
c4?: C4DiagramConfig;
|
||||||
sankey?: SankeyDiagramConfig;
|
sankey?: SankeyDiagramConfig;
|
||||||
@ -1023,6 +1024,17 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
|
|||||||
padding?: number;
|
padding?: number;
|
||||||
maxNodeWidth?: number;
|
maxNodeWidth?: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The object containing configurations specific for kanban diagrams
|
||||||
|
*
|
||||||
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
|
* via the `definition` "KanbanDiagramConfig".
|
||||||
|
*/
|
||||||
|
export interface KanbanDiagramConfig extends BaseDiagramConfig {
|
||||||
|
padding?: number;
|
||||||
|
sectionWidth?: number;
|
||||||
|
ticketBaseUrl?: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
* via the `definition` "GitGraphDiagramConfig".
|
* via the `definition` "GitGraphDiagramConfig".
|
||||||
|
@ -58,6 +58,12 @@ const config: RequiredDeep<MermaidConfig> = {
|
|||||||
tickInterval: undefined,
|
tickInterval: undefined,
|
||||||
useWidth: undefined, // can probably be removed since `configKeys` already includes this
|
useWidth: undefined, // can probably be removed since `configKeys` already includes this
|
||||||
},
|
},
|
||||||
|
kanban: {
|
||||||
|
ticketBaseUrl: '', // can probably be removed since `configKeys` already includes this
|
||||||
|
padding: 8,
|
||||||
|
sectionWidth: 200,
|
||||||
|
...defaultConfigJson.kanban,
|
||||||
|
},
|
||||||
c4: {
|
c4: {
|
||||||
...defaultConfigJson.c4,
|
...defaultConfigJson.c4,
|
||||||
useWidth: undefined,
|
useWidth: undefined,
|
||||||
|
@ -380,3 +380,89 @@ root
|
|||||||
expect(child2.nodeId).toEqual('B');
|
expect(child2.nodeId).toEqual('B');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('item data data', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
kanban.yy = kanbanDB;
|
||||||
|
kanban.yy.clear();
|
||||||
|
setLogLevel('trace');
|
||||||
|
});
|
||||||
|
it('KNBN-30 should be possible to set the priority', function () {
|
||||||
|
let str = `kanban
|
||||||
|
root
|
||||||
|
`;
|
||||||
|
str = `kanban
|
||||||
|
root@{ priority: high }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].priority).toEqual('high');
|
||||||
|
});
|
||||||
|
it('KNBN-31 should be possible to set the assignment', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ assigned: knsv }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].assigned).toEqual('knsv');
|
||||||
|
});
|
||||||
|
it('KNBN-32 should be possible to set the icon', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ icon: star }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].icon).toEqual('star');
|
||||||
|
});
|
||||||
|
it('KNBN-33 should be possible to set the icon', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ icon: star }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].icon).toEqual('star');
|
||||||
|
});
|
||||||
|
it('KNBN-34 should be possible to set the metadata using multiple lines', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{
|
||||||
|
icon: star
|
||||||
|
assigned: knsv
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].icon).toEqual('star');
|
||||||
|
expect(sections[0].assigned).toEqual('knsv');
|
||||||
|
});
|
||||||
|
it('KNBN-35 should be possible to set the metadata using one line', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ icon: star, assigned: knsv }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].icon).toEqual('star');
|
||||||
|
expect(sections[0].assigned).toEqual('knsv');
|
||||||
|
});
|
||||||
|
it('KNBN-36 should be possible to set the label using the new syntax', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ icon: star, label: 'fix things' }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].descr).toEqual('fix things');
|
||||||
|
});
|
||||||
|
it('KNBN-37 should be possible to set the external id', function () {
|
||||||
|
const str = `kanban
|
||||||
|
root@{ ticket: MC-1234 }
|
||||||
|
`;
|
||||||
|
kanban.parse(str);
|
||||||
|
const sections = kanban.yy.getSections();
|
||||||
|
expect(sections[0].nodeId).toEqual('root');
|
||||||
|
expect(sections[0].ticket).toEqual('MC-1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -2,12 +2,14 @@ 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 { KanbanNode } from './kanbanTypes.js';
|
import type { KanbanInternalNode } from './kanbanTypes.js';
|
||||||
import type { Node, Edge } from '../../rendering-util/types.js';
|
import type { Node, Edge, KanbanNode } from '../../rendering-util/types.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
|
import type { NodeMetaData } from '../../types.js';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
|
||||||
let nodes: KanbanNode[] = [];
|
let nodes: KanbanInternalNode[] = [];
|
||||||
let sections: KanbanNode[] = [];
|
let sections: KanbanInternalNode[] = [];
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
let elements: Record<number, D3Element> = {};
|
let elements: Record<number, D3Element> = {};
|
||||||
|
|
||||||
@ -38,9 +40,6 @@ const getSection = function (level: number) {
|
|||||||
throw new Error('Items without section detected, found section ("' + nodes[i].descr + '")');
|
throw new Error('Items without section detected, found section ("' + nodes[i].descr + '")');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if (!lastSection) {
|
|
||||||
// // console.log('No last section');
|
|
||||||
// }
|
|
||||||
if (level === lastSection?.level) {
|
if (level === lastSection?.level) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -59,15 +58,15 @@ const getData = function () {
|
|||||||
|
|
||||||
const sections = getSections();
|
const sections = getSections();
|
||||||
const conf = getConfig();
|
const conf = getConfig();
|
||||||
// const id: string = sanitizeText(id, conf) || 'identifier' + cnt++;
|
|
||||||
|
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
const node = {
|
const node = {
|
||||||
id: section.nodeId,
|
id: section.nodeId,
|
||||||
label: sanitizeText(section.descr, conf),
|
label: sanitizeText(section.descr, conf),
|
||||||
isGroup: true,
|
isGroup: true,
|
||||||
|
ticket: section.ticket,
|
||||||
shape: 'kanbanSection',
|
shape: 'kanbanSection',
|
||||||
} satisfies Node;
|
} satisfies KanbanNode;
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
for (const item of section.children) {
|
for (const item of section.children) {
|
||||||
const childNode = {
|
const childNode = {
|
||||||
@ -75,10 +74,14 @@ const getData = function () {
|
|||||||
parentId: section.nodeId,
|
parentId: section.nodeId,
|
||||||
label: sanitizeText(item.descr, conf),
|
label: sanitizeText(item.descr, conf),
|
||||||
isGroup: false,
|
isGroup: false,
|
||||||
|
ticket: item?.ticket,
|
||||||
|
priority: item?.priority,
|
||||||
|
assigned: item?.assigned,
|
||||||
|
icon: item?.icon,
|
||||||
shape: 'kanbanItem',
|
shape: 'kanbanItem',
|
||||||
rx: 5,
|
rx: 5,
|
||||||
cssStyles: ['text-align: left'],
|
cssStyles: ['text-align: left'],
|
||||||
} satisfies Node;
|
} satisfies KanbanNode;
|
||||||
nodes.push(childNode);
|
nodes.push(childNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,7 +89,7 @@ const getData = function () {
|
|||||||
return { nodes, edges, other: {}, config: getConfig() };
|
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, shapeData: string) => {
|
||||||
// log.info('addNode level=', level, 'id=', id, 'descr=', descr, 'type=', 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;
|
||||||
@ -106,9 +109,47 @@ 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 KanbanNode;
|
} satisfies KanbanInternalNode;
|
||||||
|
|
||||||
|
if (shapeData !== undefined) {
|
||||||
|
let yamlData;
|
||||||
|
// detect if shapeData contains a newline character
|
||||||
|
// console.log('shapeData', shapeData);
|
||||||
|
if (!shapeData.includes('\n')) {
|
||||||
|
// console.log('yamlData shapeData has no new lines', shapeData);
|
||||||
|
yamlData = '{\n' + shapeData + '\n}';
|
||||||
|
} else {
|
||||||
|
// console.log('yamlData shapeData has new lines', shapeData);
|
||||||
|
yamlData = shapeData + '\n';
|
||||||
|
}
|
||||||
|
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
|
||||||
|
// console.log('yamlData', doc);
|
||||||
|
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
|
||||||
|
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc?.shape) {
|
||||||
|
node.type = doc?.shape;
|
||||||
|
}
|
||||||
|
if (doc?.label) {
|
||||||
|
node.descr = doc?.label;
|
||||||
|
}
|
||||||
|
if (doc?.icon) {
|
||||||
|
node.icon = doc?.icon;
|
||||||
|
}
|
||||||
|
if (doc?.assigned) {
|
||||||
|
node.assigned = doc?.assigned;
|
||||||
|
}
|
||||||
|
if (doc?.ticket) {
|
||||||
|
node.ticket = doc?.ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc?.priority) {
|
||||||
|
node.priority = doc?.priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const section = getSection(level);
|
const section = getSection(level);
|
||||||
console.log('Node ', node.descr, ' section', section?.descr);
|
|
||||||
if (section) {
|
if (section) {
|
||||||
section.children.push(node);
|
section.children.push(node);
|
||||||
// Keep all nodes in the list
|
// Keep all nodes in the list
|
||||||
|
@ -1,86 +1,13 @@
|
|||||||
import type cytoscape from 'cytoscape';
|
|
||||||
// @ts-expect-error No types available
|
// @ts-expect-error No types available
|
||||||
import coseBilkent from 'cytoscape-cose-bilkent';
|
|
||||||
import { select } from 'd3';
|
|
||||||
import type { MermaidConfig } from '../../config.type.js';
|
|
||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
import type { DrawDefinition } from '../../diagram-api/types.js';
|
import type { DrawDefinition } from '../../diagram-api/types.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.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 { KanbanDB, KanbanNode } from './kanbanTypes.js';
|
import type { KanbanDB } from './kanbanTypes.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
import { insertCluster, positionCluster } from '../../rendering-util/rendering-elements/clusters';
|
import { insertCluster } from '../../rendering-util/rendering-elements/clusters.js';
|
||||||
import { insertNode, positionNode } from '../../rendering-util/rendering-elements/nodes';
|
import { insertNode, positionNode } from '../../rendering-util/rendering-elements/nodes.js';
|
||||||
|
|
||||||
declare module 'cytoscape' {
|
|
||||||
interface EdgeSingular {
|
|
||||||
_private: {
|
|
||||||
bodyBounds: unknown;
|
|
||||||
rscratch: {
|
|
||||||
startX: number;
|
|
||||||
startY: number;
|
|
||||||
midX: number;
|
|
||||||
midY: number;
|
|
||||||
endX: number;
|
|
||||||
endY: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
|
|
||||||
cy.edges().map((edge, id) => {
|
|
||||||
const data = edge.data();
|
|
||||||
if (edge[0]._private.bodyBounds) {
|
|
||||||
const bounds = edge[0]._private.rscratch;
|
|
||||||
log.trace('Edge: ', id, data);
|
|
||||||
edgesEl
|
|
||||||
.insert('path')
|
|
||||||
.attr(
|
|
||||||
'd',
|
|
||||||
`M ${bounds.startX},${bounds.startY} L ${bounds.midX},${bounds.midY} L${bounds.endX},${bounds.endY} `
|
|
||||||
)
|
|
||||||
.attr('class', 'edge section-edge-' + data.section + ' edge-depth-' + data.depth);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNodes(mindmap: KanbanNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
|
|
||||||
cy.add({
|
|
||||||
group: 'nodes',
|
|
||||||
data: {
|
|
||||||
id: mindmap.id.toString(),
|
|
||||||
labelText: mindmap.descr,
|
|
||||||
height: mindmap.height,
|
|
||||||
width: mindmap.width,
|
|
||||||
level: level,
|
|
||||||
nodeId: mindmap.id,
|
|
||||||
padding: mindmap.padding,
|
|
||||||
type: mindmap.type,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
x: mindmap.x!,
|
|
||||||
y: mindmap.y!,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (mindmap.children) {
|
|
||||||
mindmap.children.forEach((child) => {
|
|
||||||
addNodes(child, cy, conf, level + 1);
|
|
||||||
cy.add({
|
|
||||||
group: 'edges',
|
|
||||||
data: {
|
|
||||||
id: `${mindmap.id}_${child.id}`,
|
|
||||||
source: mindmap.id,
|
|
||||||
target: child.id,
|
|
||||||
depth: level,
|
|
||||||
section: child.section,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
@ -106,8 +33,9 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
|
|||||||
const padding = 10;
|
const padding = 10;
|
||||||
|
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
const WIDTH = 200;
|
const WIDTH = conf?.kanban?.sectionWidth || 200;
|
||||||
let y = (-WIDTH * 3) / 2 + 40;
|
const top = (-WIDTH * 3) / 2 + 25;
|
||||||
|
let y = top;
|
||||||
cnt = cnt + 1;
|
cnt = cnt + 1;
|
||||||
section.x = WIDTH * cnt + ((cnt - 1) * padding) / 2;
|
section.x = WIDTH * cnt + ((cnt - 1) * padding) / 2;
|
||||||
section.width = WIDTH;
|
section.width = WIDTH;
|
||||||
@ -118,21 +46,21 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
|
|||||||
|
|
||||||
// Todo, use theme variable THEME_COLOR_LIMIT instead of 10
|
// Todo, use theme variable THEME_COLOR_LIMIT instead of 10
|
||||||
section.cssClasses = section.cssClasses + ' section-' + cnt;
|
section.cssClasses = section.cssClasses + ' section-' + cnt;
|
||||||
const cluster = await insertCluster(sectionsElem, section);
|
const sectionObj = await insertCluster(sectionsElem, section);
|
||||||
const sectionItems = data4Layout.nodes.filter((node) => node.parentId === section.id);
|
const sectionItems = data4Layout.nodes.filter((node) => node.parentId === section.id);
|
||||||
// positionCluster(section);
|
|
||||||
for (const item of sectionItems) {
|
for (const item of sectionItems) {
|
||||||
item.x = section.x;
|
item.x = section.x;
|
||||||
item.width = WIDTH - 2 * padding;
|
item.width = WIDTH - 1.5 * padding;
|
||||||
// item.height = 100;
|
const nodeEl = await insertNode(nodesElem, item, { config: conf });
|
||||||
const nodeEl = await insertNode(nodesElem, item);
|
|
||||||
console.log('ITEM', item, 'bbox=', nodeEl.node().getBBox());
|
|
||||||
const bbox = nodeEl.node().getBBox();
|
const bbox = nodeEl.node().getBBox();
|
||||||
item.y = y + bbox.height / 2;
|
item.y = y + bbox.height / 2;
|
||||||
// item.height = 150;
|
|
||||||
await positionNode(item);
|
await positionNode(item);
|
||||||
y = item.y + bbox.height / 2 + padding;
|
y = item.y + bbox.height / 2 + padding / 2;
|
||||||
}
|
}
|
||||||
|
const rect = sectionObj.cluster.select('rect');
|
||||||
|
const height = Math.max(y - top + 3 * padding, 50);
|
||||||
|
rect.attr('height', height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the view box and size of the svg element
|
// Setup the view box and size of the svg element
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
import type { RequiredDeep } from 'type-fest';
|
import type { RequiredDeep } from 'type-fest';
|
||||||
import type kanbanDb from './kanbanDb.js';
|
import type kanbanDb from './kanbanDb.js';
|
||||||
|
|
||||||
export interface KanbanNode {
|
export interface KanbanInternalNode {
|
||||||
id: number;
|
id: number;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
level: number;
|
level: number;
|
||||||
descr: string;
|
descr: string;
|
||||||
type: number;
|
type: number;
|
||||||
children: KanbanNode[];
|
children: KanbanInternalNode[];
|
||||||
width: number;
|
width: number;
|
||||||
padding: number;
|
padding: number;
|
||||||
section?: number;
|
section?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
class?: string;
|
class?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
ticket?: string;
|
||||||
|
priority?: string;
|
||||||
x?: number;
|
x?: number;
|
||||||
y?: number;
|
y?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FilledKanbanNode = RequiredDeep<KanbanNode>;
|
export type FilledKanbanNode = RequiredDeep<KanbanInternalNode>;
|
||||||
export type KanbanDB = typeof kanbanDb;
|
export type KanbanDB = typeof kanbanDb;
|
||||||
|
@ -15,12 +15,39 @@
|
|||||||
%x NSTR2
|
%x NSTR2
|
||||||
%x ICON
|
%x ICON
|
||||||
%x CLASS
|
%x CLASS
|
||||||
|
%x shapeData
|
||||||
|
%x shapeDataStr
|
||||||
|
%x shapeDataEndBracket
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
\@\{ {
|
||||||
|
// console.log('=> shapeData', yytext);
|
||||||
|
this.pushState("shapeData"); yytext=""; return 'SHAPE_DATA' }
|
||||||
|
<shapeData>["] {
|
||||||
|
// console.log('=> shapeDataStr', yytext);
|
||||||
|
this.pushState("shapeDataStr");
|
||||||
|
return 'SHAPE_DATA';
|
||||||
|
}
|
||||||
|
<shapeDataStr>["] {
|
||||||
|
// console.log('shapeData <==', yytext);
|
||||||
|
this.popState(); return 'SHAPE_DATA'}
|
||||||
|
<shapeDataStr>[^\"]+ {
|
||||||
|
// console.log('shapeData', yytext);
|
||||||
|
const re = /\n\s*/g;
|
||||||
|
yytext = yytext.replace(re,"<br/>");
|
||||||
|
return 'SHAPE_DATA'}
|
||||||
|
<shapeData>[^}^"]+ {
|
||||||
|
// console.log('shapeData', yytext);
|
||||||
|
return 'SHAPE_DATA';
|
||||||
|
}
|
||||||
|
<shapeData>"}" {
|
||||||
|
// console.log('<== root', yytext)
|
||||||
|
this.popState();
|
||||||
|
}
|
||||||
\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 'KANBAN';
|
"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();}
|
||||||
@ -40,7 +67,7 @@
|
|||||||
"[" { this.begin('NODE');return 'NODE_DSTART'; }
|
"[" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||||
[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
|
[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
|
||||||
// !(-\() return 'NODE_ID';
|
// !(-\() return 'NODE_ID';
|
||||||
[^\(\[\n\)\{\}]+ return 'NODE_ID';
|
[^\(\[\n\)\{\}@]+ {return 'NODE_ID';}
|
||||||
<<EOF>> return 'EOF';
|
<<EOF>> return 'EOF';
|
||||||
<NODE>["][`] { this.begin("NSTR2");}
|
<NODE>["][`] { this.begin("NSTR2");}
|
||||||
<NSTR2>[^`"]+ { return "NODE_DESCR";}
|
<NSTR2>[^`"]+ { return "NODE_DESCR";}
|
||||||
@ -97,10 +124,12 @@ document
|
|||||||
;
|
;
|
||||||
|
|
||||||
statement
|
statement
|
||||||
: SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
|
: SPACELIST node shapeData { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type, $3); }
|
||||||
|
| SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
|
||||||
| SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
|
| SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
|
||||||
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
|
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
|
||||||
| SPACELINE { yy.getLogger().trace('SPACELIST');}
|
| SPACELINE { yy.getLogger().trace('SPACELIST');}
|
||||||
|
| node shapeData { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type, $2); }
|
||||||
| node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
|
| node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
|
||||||
| ICON { yy.decorateNode({icon: $1}); }
|
| ICON { yy.decorateNode({icon: $1}); }
|
||||||
| CLASS { yy.decorateNode({class: $1}); }
|
| CLASS { yy.decorateNode({class: $1}); }
|
||||||
@ -120,8 +149,18 @@ nodeWithoutId
|
|||||||
;
|
;
|
||||||
|
|
||||||
nodeWithId
|
nodeWithId
|
||||||
: NODE_ID { $$ = { id: $1, descr: $1, type: yy.nodeType.DEFAULT }; }
|
: NODE_ID { $$ = { id: $1, descr: $1, type: 0 }; }
|
||||||
| NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
|
| NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
|
||||||
{ yy.getLogger().trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
|
{ yy.getLogger().trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
shapeData:
|
||||||
|
shapeData SHAPE_DATA
|
||||||
|
{ $$ = $1 + $2; }
|
||||||
|
| SHAPE_DATA
|
||||||
|
{ $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -14,13 +14,16 @@ const genSections: DiagramStylesProvider = (options) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const adjuster = (color: string, level: number) =>
|
||||||
|
options.darkMode ? darken(color, level) : lighten(color, level);
|
||||||
|
|
||||||
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
|
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
|
||||||
const sw = '' + (17 - 3 * i);
|
const sw = '' + (17 - 3 * i);
|
||||||
sections += `
|
sections += `
|
||||||
.section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
|
.section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
|
||||||
i - 1
|
i - 1
|
||||||
} polygon, .section-${i - 1} path {
|
} polygon, .section-${i - 1} path {
|
||||||
fill: ${options['cScale' + i]};
|
fill: ${adjuster(options['cScale' + i], 10)};
|
||||||
}
|
}
|
||||||
.section-${i - 1} text {
|
.section-${i - 1} text {
|
||||||
fill: ${options['cScaleLabel' + i]};
|
fill: ${options['cScaleLabel' + i]};
|
||||||
@ -56,6 +59,12 @@ const genSections: DiagramStylesProvider = (options) => {
|
|||||||
stroke: ${options.nodeBorder};
|
stroke: ${options.nodeBorder};
|
||||||
stroke-width: 1px;
|
stroke-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanban-ticket-link {
|
||||||
|
fill: ${options.background};
|
||||||
|
stroke: ${options.nodeBorder};
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return sections;
|
return sections;
|
||||||
|
@ -1,29 +1,97 @@
|
|||||||
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
|
import { labelHelper, insertLabel, updateNodeBounds, getNodeClasses } from './util.js';
|
||||||
import intersect from '../intersect/index.js';
|
import intersect from '../intersect/index.js';
|
||||||
import type { Node } from '../../types.js';
|
import type { KanbanNode } from '../../types.js';
|
||||||
import { createRoundedRectPathD } from './roundedRectPath.js';
|
import { createRoundedRectPathD } from './roundedRectPath.js';
|
||||||
import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
|
import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
|
||||||
import rough from 'roughjs';
|
import rough from 'roughjs';
|
||||||
|
const colorFromPriority = (priority: KanbanNode['priority']) => {
|
||||||
export const kanbanItem = async (parent: SVGAElement, node: Node) => {
|
switch (priority) {
|
||||||
|
case 'Very High':
|
||||||
|
return 'red';
|
||||||
|
case 'High':
|
||||||
|
return 'orange';
|
||||||
|
case 'Low':
|
||||||
|
return 'blue';
|
||||||
|
case 'Very Low':
|
||||||
|
return 'lightblue';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const kanbanItem = async (parent: SVGAElement, node: KanbanNode, { config }) => {
|
||||||
const { labelStyles, nodeStyles } = styles2String(node);
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
node.labelStyle = labelStyles;
|
node.labelStyle = labelStyles;
|
||||||
// console.log('IPI labelStyles:', labelStyles);
|
// console.log('IPI labelStyles:', labelStyles);
|
||||||
|
// const labelPaddingX = 10;
|
||||||
const labelPaddingX = 10;
|
const labelPaddingX = 10;
|
||||||
const orgWidth = node.width;
|
const orgWidth = node.width;
|
||||||
node.width = (node.width ?? 200) - 2 * labelPaddingX;
|
node.width = (node.width ?? 200) - 10;
|
||||||
console.log('APA123 kanbanItem', node.labelStyle);
|
// console.log('APA123 kanbanItem priority', node?.priority);
|
||||||
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
|
const {
|
||||||
|
shapeSvg,
|
||||||
|
bbox,
|
||||||
|
label: labelElTitle,
|
||||||
|
} = await labelHelper(parent, node, getNodeClasses(node));
|
||||||
|
const padding = node.padding || 10;
|
||||||
|
|
||||||
|
// elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
|
||||||
|
// console.log('STO node config.kanban:', config.kanban, config.kanban);
|
||||||
|
let ticketUrl = '';
|
||||||
|
let link;
|
||||||
|
// console.log('STO ticket:', node.ticket);
|
||||||
|
if (node.ticket && config.kanban.ticketBaseUrl) {
|
||||||
|
ticketUrl = config.kanban.ticketBaseUrl.replace('#TICKET#', node.ticket);
|
||||||
|
link = shapeSvg
|
||||||
|
.insert('svg:a', ':first-child')
|
||||||
|
.attr('class', 'kanban-ticket-link')
|
||||||
|
.attr('xlink:href', ticketUrl)
|
||||||
|
.attr('target', '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
useHtmlLabels: node.useHtmlLabels,
|
||||||
|
labelStyle: node.labelStyle,
|
||||||
|
width: node.width,
|
||||||
|
icon: node.icon,
|
||||||
|
img: node.img,
|
||||||
|
padding: node.padding,
|
||||||
|
centerLabel: false,
|
||||||
|
};
|
||||||
|
const { label: labelEl, bbox: bbox2 } = await insertLabel(
|
||||||
|
link ? link : shapeSvg,
|
||||||
|
node.ticket || '',
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const { label: labelElAssigned, bbox: bboxAssigned } = await insertLabel(
|
||||||
|
shapeSvg,
|
||||||
|
node.assigned || '',
|
||||||
|
options
|
||||||
|
);
|
||||||
node.width = orgWidth;
|
node.width = orgWidth;
|
||||||
const labelPaddingY = 10;
|
const labelPaddingY = 10;
|
||||||
const totalWidth = node?.width || 0;
|
const totalWidth = node?.width || 0;
|
||||||
const totalHeight = Math.max(bbox.height + labelPaddingY * 2, node?.height || 0);
|
const heightAdj = Math.max(bbox2.height, bboxAssigned.height) / 2;
|
||||||
|
const totalHeight = Math.max(bbox.height + labelPaddingY * 2, node?.height || 0) + heightAdj;
|
||||||
const x = -totalWidth / 2;
|
const x = -totalWidth / 2;
|
||||||
const y = -totalHeight / 2;
|
const y = -totalHeight / 2;
|
||||||
|
labelElTitle.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' + (padding - totalWidth / 2) + ', ' + (-heightAdj - bbox.height / 2) + ')'
|
||||||
|
);
|
||||||
|
labelEl.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' + (padding - totalWidth / 2) + ', ' + (-heightAdj + bbox.height / 2) + ')'
|
||||||
|
);
|
||||||
|
labelElAssigned.attr(
|
||||||
|
'transform',
|
||||||
|
'translate(' +
|
||||||
|
(padding + totalWidth / 2 - bboxAssigned.width - 2 * labelPaddingX) +
|
||||||
|
', ' +
|
||||||
|
(-heightAdj + bbox.height / 2) +
|
||||||
|
')'
|
||||||
|
);
|
||||||
// log.info('IPI node = ', node);
|
// log.info('IPI node = ', node);
|
||||||
|
|
||||||
let rect;
|
let rect;
|
||||||
|
|
||||||
const { rx, ry } = node;
|
const { rx, ry } = node;
|
||||||
const { cssStyles } = node;
|
const { cssStyles } = node;
|
||||||
|
|
||||||
@ -51,9 +119,25 @@ export const kanbanItem = async (parent: SVGAElement, node: Node) => {
|
|||||||
.attr('y', y)
|
.attr('y', y)
|
||||||
.attr('width', totalWidth)
|
.attr('width', totalWidth)
|
||||||
.attr('height', totalHeight);
|
.attr('height', totalHeight);
|
||||||
|
if (node.priority) {
|
||||||
|
const line = shapeSvg.append('line', ':first-child');
|
||||||
|
const lineX = x + 2;
|
||||||
|
|
||||||
|
const y1 = y + Math.floor((rx ?? 0) / 2);
|
||||||
|
const y2 = y + totalHeight - Math.floor((rx ?? 0) / 2);
|
||||||
|
line
|
||||||
|
.attr('x1', lineX)
|
||||||
|
.attr('y1', y1)
|
||||||
|
.attr('x2', lineX)
|
||||||
|
.attr('y2', y2)
|
||||||
|
|
||||||
|
.attr('stroke-width', '4')
|
||||||
|
.attr('stroke', colorFromPriority(node.priority));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNodeBounds(node, rect);
|
updateNodeBounds(node, rect);
|
||||||
|
node.height = totalHeight;
|
||||||
|
|
||||||
node.intersect = function (point) {
|
node.intersect = function (point) {
|
||||||
return intersect.rect(node, point);
|
return intersect.rect(node, point);
|
||||||
|
@ -4,7 +4,7 @@ import { select } from 'd3';
|
|||||||
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
|
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
|
||||||
import { decodeEntities } from '../../../utils.js';
|
import { decodeEntities } from '../../../utils.js';
|
||||||
|
|
||||||
export const labelHelper = async (parent, node, _classes) => {
|
export const labelHelper = async (parent, node, _classes, _shapeSvg) => {
|
||||||
let cssClasses;
|
let cssClasses;
|
||||||
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
|
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
|
||||||
if (!_classes) {
|
if (!_classes) {
|
||||||
@ -14,10 +14,12 @@ export const labelHelper = async (parent, node, _classes) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add outer g element
|
// Add outer g element
|
||||||
const shapeSvg = parent
|
const shapeSvg = _shapeSvg
|
||||||
.insert('g')
|
? _shapeSvg
|
||||||
.attr('class', cssClasses)
|
: parent
|
||||||
.attr('id', node.domId || node.id);
|
.insert('g')
|
||||||
|
.attr('class', cssClasses)
|
||||||
|
.attr('id', node.domId || node.id);
|
||||||
|
|
||||||
// Create the label and insert it after the rect
|
// Create the label and insert it after the rect
|
||||||
const labelEl = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle);
|
const labelEl = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle);
|
||||||
@ -106,6 +108,46 @@ export const labelHelper = async (parent, node, _classes) => {
|
|||||||
return { shapeSvg, bbox, halfPadding, label: labelEl };
|
return { shapeSvg, bbox, halfPadding, label: labelEl };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertLabel = async (parent, label, options) => {
|
||||||
|
const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
|
||||||
|
|
||||||
|
// Create the label and insert it after the rect
|
||||||
|
const labelEl = parent.insert('g').attr('class', 'label').attr('style', options.labelStyle);
|
||||||
|
|
||||||
|
let text;
|
||||||
|
text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
|
||||||
|
useHtmlLabels,
|
||||||
|
width: options.width || getConfig().flowchart.wrappingWidth,
|
||||||
|
cssClasses: 'markdown-node-label',
|
||||||
|
style: options.labelStyle,
|
||||||
|
addSvgBackground: !!options.icon || !!options.img,
|
||||||
|
});
|
||||||
|
// Get the size of the label
|
||||||
|
let bbox = text.getBBox();
|
||||||
|
const halfPadding = options.padding / 2;
|
||||||
|
|
||||||
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
|
const div = text.children[0];
|
||||||
|
const dv = select(text);
|
||||||
|
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center the label
|
||||||
|
if (useHtmlLabels) {
|
||||||
|
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
} else {
|
||||||
|
labelEl.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
}
|
||||||
|
if (options.centerLabel) {
|
||||||
|
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
}
|
||||||
|
labelEl.insert('rect', ':first-child');
|
||||||
|
return { shapeSvg: parent, bbox, halfPadding, label: labelEl };
|
||||||
|
};
|
||||||
|
|
||||||
export const updateNodeBounds = (node, element) => {
|
export const updateNodeBounds = (node, element) => {
|
||||||
const bbox = element.node().getBBox();
|
const bbox = element.node().getBBox();
|
||||||
node.width = bbox.width;
|
node.width = bbox.width;
|
||||||
|
@ -148,3 +148,11 @@ export interface ShapeRenderOptions {
|
|||||||
config: MermaidConfig;
|
config: MermaidConfig;
|
||||||
dir: string;
|
dir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KanbanNode extends Node {
|
||||||
|
// Kanban specif data
|
||||||
|
priority?: 'Very High' | 'High' | 'Medium' | 'Low' | 'Very Low';
|
||||||
|
ticket?: string;
|
||||||
|
assigned?: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
@ -279,6 +279,8 @@ properties:
|
|||||||
$ref: '#/$defs/ArchitectureDiagramConfig'
|
$ref: '#/$defs/ArchitectureDiagramConfig'
|
||||||
mindmap:
|
mindmap:
|
||||||
$ref: '#/$defs/MindmapDiagramConfig'
|
$ref: '#/$defs/MindmapDiagramConfig'
|
||||||
|
kanban:
|
||||||
|
$ref: '#/$defs/KanbanDiagramConfig'
|
||||||
gitGraph:
|
gitGraph:
|
||||||
$ref: '#/$defs/GitGraphDiagramConfig'
|
$ref: '#/$defs/GitGraphDiagramConfig'
|
||||||
c4:
|
c4:
|
||||||
@ -964,6 +966,23 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||||||
type: number
|
type: number
|
||||||
default: 200
|
default: 200
|
||||||
|
|
||||||
|
KanbanDiagramConfig:
|
||||||
|
title: Kanban Diagram Config
|
||||||
|
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
||||||
|
description: The object containing configurations specific for kanban diagrams
|
||||||
|
type: object
|
||||||
|
unevaluatedProperties: false
|
||||||
|
properties:
|
||||||
|
padding:
|
||||||
|
type: number
|
||||||
|
default: 8
|
||||||
|
sectionWidth:
|
||||||
|
type: number
|
||||||
|
default: 200
|
||||||
|
ticketBaseUrl:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
|
||||||
PieDiagramConfig:
|
PieDiagramConfig:
|
||||||
title: Pie Diagram Config
|
title: Pie Diagram Config
|
||||||
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
||||||
|
@ -8,6 +8,7 @@ export interface NodeMetaData {
|
|||||||
w?: string;
|
w?: string;
|
||||||
h?: string;
|
h?: string;
|
||||||
constraint?: 'on' | 'off';
|
constraint?: 'on' | 'off';
|
||||||
|
priority: 'Very High' | 'High' | 'Medium' | 'Low' | 'Very Low';
|
||||||
}
|
}
|
||||||
import type { MermaidConfig } from './config.type.js';
|
import type { MermaidConfig } from './config.type.js';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user