Add initial support for asynchronous action blocks [#12]
This commit is contained in:
parent
4772b16783
commit
912c9dbb64
33
README.md
33
README.md
|
@ -150,6 +150,39 @@ Foo -> Bar
|
|||
Bar -> Baz
|
||||
```
|
||||
|
||||
### Simultaneous Actions (Beta!)
|
||||
|
||||
<img src="screenshots/SimultaneousActions.png" alt="Simultaneous Actions preview" width="200" align="right" />
|
||||
|
||||
This is a work-in-progress feature. There are situations where this can
|
||||
lead to [ugly / unreadable overlapping content](https://github.com/davidje13/SequenceDiagram/issues/13).
|
||||
|
||||
```
|
||||
begin A, B, C, D
|
||||
A -> C
|
||||
|
||||
# Define a marker which can be returned to later
|
||||
|
||||
some primary process:
|
||||
A -> B
|
||||
B -> A
|
||||
A -> B
|
||||
B -> A
|
||||
|
||||
# Return to the defined marker
|
||||
# (should be interpreted as no-higher-then the marker; may still be
|
||||
# pushed down to keep relative action ordering consistent)
|
||||
|
||||
simultaneously with some primary process:
|
||||
C -> D
|
||||
D -> C
|
||||
end D
|
||||
C -> A
|
||||
|
||||
# The marker name is optional; using "simultaneously:" with no marker
|
||||
# will jump to the top of the entire sequence.
|
||||
```
|
||||
|
||||
## DSL Basics
|
||||
|
||||
Comments begin with a `#` and end at the next newline:
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
|
@ -17,10 +17,13 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.agents = [];
|
||||
this.blockCount = 0;
|
||||
this.nesting = [];
|
||||
this.markers = new Set();
|
||||
this.currentSection = null;
|
||||
this.currentNest = null;
|
||||
|
||||
this.stageHandlers = {
|
||||
'mark': this.handleMark.bind(this),
|
||||
'async': this.handleAsync.bind(this),
|
||||
'agent define': this.handleAgentDefine.bind(this),
|
||||
'agent begin': this.handleAgentBegin.bind(this),
|
||||
'agent end': this.handleAgentEnd.bind(this),
|
||||
|
@ -83,6 +86,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
agents: filteredAgents,
|
||||
mode,
|
||||
});
|
||||
this.currentNest.hasContent = true;
|
||||
}
|
||||
array.mergeSets(this.currentNest.agents, filteredAgents);
|
||||
array.mergeSets(this.agents, filteredAgents);
|
||||
|
@ -100,6 +104,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
this.currentNest = {
|
||||
agents,
|
||||
hasContent: false,
|
||||
stage: {
|
||||
type: 'block',
|
||||
sections: [this.currentSection],
|
||||
|
@ -114,6 +119,18 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return {agents, stages};
|
||||
}
|
||||
|
||||
handleMark(stage) {
|
||||
this.markers.add(stage.name);
|
||||
this.currentSection.stages.push(stage);
|
||||
}
|
||||
|
||||
handleAsync(stage) {
|
||||
if(stage.target !== '' && !this.markers.has(stage.target)) {
|
||||
throw new Error('Unknown marker: ' + stage.target);
|
||||
}
|
||||
this.currentSection.stages.push(stage);
|
||||
}
|
||||
|
||||
handleAgentDefine({agents}) {
|
||||
array.mergeSets(this.currentNest.agents, agents);
|
||||
array.mergeSets(this.agents, agents);
|
||||
|
@ -149,10 +166,10 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
if(this.nesting.length <= 1) {
|
||||
throw new Error('Invalid block nesting');
|
||||
}
|
||||
const {stage, agents} = this.nesting.pop();
|
||||
const {hasContent, stage, agents} = this.nesting.pop();
|
||||
this.currentNest = array.last(this.nesting);
|
||||
this.currentSection = array.last(this.currentNest.stage.sections);
|
||||
if(stage.sections.some((section) => section.stages.length > 0)) {
|
||||
if(hasContent) {
|
||||
array.mergeSets(this.currentNest.agents, agents);
|
||||
array.mergeSets(this.agents, agents);
|
||||
this.addBounds(
|
||||
|
@ -162,15 +179,19 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
agents
|
||||
);
|
||||
this.currentSection.stages.push(stage);
|
||||
this.currentNest.hasContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleUnknownStage(stage) {
|
||||
if(stage.agents) {
|
||||
this.setAgentVis(stage.agents, true, 'box');
|
||||
this.currentSection.stages.push(stage);
|
||||
array.mergeSets(this.currentNest.agents, stage.agents);
|
||||
array.mergeSets(this.agents, stage.agents);
|
||||
}
|
||||
this.currentSection.stages.push(stage);
|
||||
this.currentNest.hasContent = true;
|
||||
}
|
||||
|
||||
handleStage(stage) {
|
||||
const handler = this.stageHandlers[stage.type];
|
||||
|
@ -183,6 +204,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
|
||||
generate({stages, meta = {}}) {
|
||||
this.agentStates.clear();
|
||||
this.markers.clear();
|
||||
this.agents.length = 0;
|
||||
this.blockCount = 0;
|
||||
this.nesting.length = 0;
|
||||
|
|
|
@ -31,6 +31,26 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
expect(sequence.agents).toEqual(['[', ']']);
|
||||
});
|
||||
|
||||
it('passes marks and async through', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
{type: 'mark', name: 'foo'},
|
||||
{type: 'async', target: 'foo'},
|
||||
{type: 'async', target: ''},
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
{type: 'mark', name: 'foo'},
|
||||
{type: 'async', target: 'foo'},
|
||||
{type: 'async', target: ''},
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects attempts to jump to markers not yet defined', () => {
|
||||
expect(() => generator.generate({stages: [
|
||||
{type: 'async', target: 'foo'},
|
||||
{type: 'mark', name: 'foo'},
|
||||
]})).toThrow();
|
||||
});
|
||||
|
||||
it('returns aggregated agents', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
{type: '->', agents: ['A', 'B']},
|
||||
|
@ -369,10 +389,11 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
expect(sequence.stages).toEqual([]);
|
||||
});
|
||||
|
||||
it('removes blocks which only contain define statements', () => {
|
||||
it('removes blocks containing only define statements / markers', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
{type: BLOCK_BEGIN, mode: 'if', label: 'abc'},
|
||||
{type: AGENT_DEFINE, agents: ['A']},
|
||||
{type: 'mark', name: 'foo'},
|
||||
{type: BLOCK_END},
|
||||
]});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(() => {
|
||||
define(['core/ArrayUtilities'], (array) => {
|
||||
'use strict';
|
||||
|
||||
function execAt(str, reg, i) {
|
||||
|
@ -160,7 +160,29 @@ define(() => {
|
|||
return list;
|
||||
}
|
||||
|
||||
function parseBlockCommand(line) {
|
||||
const PARSERS = [
|
||||
(line, meta) => { // title
|
||||
if(line[0] !== 'title') {
|
||||
return null;
|
||||
}
|
||||
|
||||
meta.title = line.slice(1).join(' ');
|
||||
return true;
|
||||
},
|
||||
|
||||
(line, meta) => { // terminators
|
||||
if(line[0] !== 'terminators') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(TERMINATOR_TYPES.indexOf(line[1]) === -1) {
|
||||
throw new Error('Unknown termination: ' + line.join(' '));
|
||||
}
|
||||
meta.terminators = line[1];
|
||||
return true;
|
||||
},
|
||||
|
||||
(line) => { // block
|
||||
if(line[0] === 'end' && line.length === 1) {
|
||||
return {type: 'block end'};
|
||||
}
|
||||
|
@ -179,9 +201,9 @@ define(() => {
|
|||
mode: type.mode,
|
||||
label: line.slice(skip).join(' '),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
function parseAgentCommand(line) {
|
||||
(line) => { // agent
|
||||
const type = AGENT_MANIPULATION_TYPES[line[0]];
|
||||
if(!type) {
|
||||
return null;
|
||||
|
@ -192,9 +214,29 @@ define(() => {
|
|||
return Object.assign({
|
||||
agents: parseCommaList(line.slice(1)),
|
||||
}, type);
|
||||
}
|
||||
},
|
||||
|
||||
function parseNote(line) {
|
||||
(line) => { // async
|
||||
if(line[0] !== 'simultaneously') {
|
||||
return null;
|
||||
}
|
||||
if(array.last(line) !== ':') {
|
||||
return null;
|
||||
}
|
||||
let target = '';
|
||||
if(line.length > 2) {
|
||||
if(line[1] !== 'with') {
|
||||
return null;
|
||||
}
|
||||
target = line.slice(2, line.length - 1).join(' ');
|
||||
}
|
||||
return {
|
||||
type: 'async',
|
||||
target,
|
||||
};
|
||||
},
|
||||
|
||||
(line) => { // note
|
||||
const mode = NOTE_TYPES[line[0]];
|
||||
const labelSplit = line.indexOf(':');
|
||||
if(!mode || labelSplit === -1) {
|
||||
|
@ -219,9 +261,9 @@ define(() => {
|
|||
mode: mode.mode,
|
||||
label: line.slice(labelSplit + 1).join(' '),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
function parseConnection(line) {
|
||||
(line) => { // connection
|
||||
let labelSplit = line.indexOf(':');
|
||||
if(labelSplit === -1) {
|
||||
labelSplit = line.length;
|
||||
|
@ -247,38 +289,34 @@ define(() => {
|
|||
],
|
||||
label: line.slice(labelSplit + 1).join(' '),
|
||||
}, options);
|
||||
}
|
||||
},
|
||||
|
||||
function parseMeta(line, meta) {
|
||||
if(line[0] === 'title') {
|
||||
meta.title = line.slice(1).join(' ');
|
||||
return true;
|
||||
}
|
||||
if(line[0] === 'terminators') {
|
||||
if(TERMINATOR_TYPES.indexOf(line[1]) === -1) {
|
||||
throw new Error('Unrecognised termination: ' + line.join(' '));
|
||||
}
|
||||
meta.terminators = line[1];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
(line) => { // marker
|
||||
if(line.length < 2 || array.last(line) !== ':') {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'mark',
|
||||
name: line.slice(0, line.length - 1).join(' '),
|
||||
};
|
||||
},
|
||||
];
|
||||
|
||||
function parseLine(line, {meta, stages}) {
|
||||
if(parseMeta(line, meta)) {
|
||||
return;
|
||||
let stage = null;
|
||||
for(let i = 0; i < PARSERS.length; ++ i) {
|
||||
stage = PARSERS[i](line, meta);
|
||||
if(stage) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const stage = (
|
||||
parseBlockCommand(line) ||
|
||||
parseAgentCommand(line) ||
|
||||
parseNote(line) ||
|
||||
parseConnection(line)
|
||||
);
|
||||
if(!stage) {
|
||||
throw new Error('Unrecognised command: ' + line.join(' '));
|
||||
}
|
||||
if(typeof stage === 'object') {
|
||||
stages.push(stage);
|
||||
}
|
||||
}
|
||||
|
||||
return class Parser {
|
||||
tokenise(src) {
|
||||
|
|
|
@ -355,6 +355,30 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('converts markers', () => {
|
||||
const parsed = parser.parse('abc:');
|
||||
expect(parsed.stages).toEqual([{
|
||||
type: 'mark',
|
||||
name: 'abc',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('converts "simultaneously" flow commands', () => {
|
||||
const parsed = parser.parse('simultaneously:');
|
||||
expect(parsed.stages).toEqual([{
|
||||
type: 'async',
|
||||
target: '',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('converts named "simultaneously" flow commands', () => {
|
||||
const parsed = parser.parse('simultaneously with abc:');
|
||||
expect(parsed.stages).toEqual([{
|
||||
type: 'async',
|
||||
target: 'abc',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('converts conditional blocks', () => {
|
||||
const parsed = parser.parse(
|
||||
'if something happens\n' +
|
||||
|
|
|
@ -61,12 +61,14 @@ define([
|
|||
};
|
||||
|
||||
this.separationAction = {
|
||||
'mark': this.separationMark.bind(this),
|
||||
'async': this.separationAsync.bind(this),
|
||||
'agent begin': this.separationAgent.bind(this),
|
||||
'agent end': this.separationAgent.bind(this),
|
||||
'connection': this.separationConnection.bind(this),
|
||||
'note over': this.separationNoteOver.bind(this),
|
||||
'note left': this.separationNoteLeft.bind(this),
|
||||
'note right': this.separationNoteRight.bind(this),
|
||||
'note left': this.separationNoteSide.bind(this, false),
|
||||
'note right': this.separationNoteSide.bind(this, true),
|
||||
'note between': this.separationNoteBetween.bind(this),
|
||||
};
|
||||
|
||||
|
@ -78,6 +80,8 @@ define([
|
|||
};
|
||||
|
||||
this.renderAction = {
|
||||
'mark': this.renderMark.bind(this),
|
||||
'async': this.renderAsync.bind(this),
|
||||
'agent begin': this.renderAgentBegin.bind(this),
|
||||
'agent end': this.renderAgentEnd.bind(this),
|
||||
'connection': this.renderConnection.bind(this),
|
||||
|
@ -104,6 +108,7 @@ define([
|
|||
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.marks = new Map();
|
||||
this.theme = theme;
|
||||
this.currentSequence = null;
|
||||
this.buildStaticElements();
|
||||
|
@ -180,6 +185,12 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
separationMark() {
|
||||
}
|
||||
|
||||
separationAsync() {
|
||||
}
|
||||
|
||||
separationAgentCapBox({label}) {
|
||||
const config = this.theme.agentCap.box;
|
||||
const width = (
|
||||
|
@ -300,39 +311,23 @@ define([
|
|||
this.addSeparations(this.visibleAgents, agentSpaces);
|
||||
}
|
||||
|
||||
separationNoteLeft({agents, mode, label}) {
|
||||
separationNoteSide(isRight, {agents, mode, label}) {
|
||||
const config = this.theme.note[mode];
|
||||
const {left} = this.findExtremes(agents);
|
||||
|
||||
const agentSpaces = new Map();
|
||||
agentSpaces.set(left, {
|
||||
left: (
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
const width = (
|
||||
this.sizer.measure(config.labelAttrs, label).width +
|
||||
config.padding.left +
|
||||
config.padding.right +
|
||||
config.margin.left +
|
||||
config.margin.right
|
||||
),
|
||||
right: 0,
|
||||
});
|
||||
this.addSeparations(this.visibleAgents, agentSpaces);
|
||||
);
|
||||
|
||||
const agentSpaces = new Map();
|
||||
if(isRight) {
|
||||
agentSpaces.set(right, {left: 0, right: width});
|
||||
} else {
|
||||
agentSpaces.set(left, {left: width, right: 0});
|
||||
}
|
||||
|
||||
separationNoteRight({agents, mode, label}) {
|
||||
const config = this.theme.note[mode];
|
||||
const {right} = this.findExtremes(agents);
|
||||
|
||||
const agentSpaces = new Map();
|
||||
agentSpaces.set(right, {
|
||||
left: 0,
|
||||
right: (
|
||||
this.sizer.measure(config.labelAttrs, label).width +
|
||||
config.padding.left +
|
||||
config.padding.right +
|
||||
config.margin.left +
|
||||
config.margin.right
|
||||
),
|
||||
});
|
||||
this.addSeparations(this.visibleAgents, agentSpaces);
|
||||
}
|
||||
|
||||
|
@ -378,6 +373,18 @@ define([
|
|||
this.separationAction[stage.type](stage);
|
||||
}
|
||||
|
||||
renderMark({name}) {
|
||||
this.marks.set(name, this.currentY);
|
||||
}
|
||||
|
||||
renderAsync({target}) {
|
||||
if(target) {
|
||||
this.currentY = this.marks.get(target) || 0;
|
||||
} else {
|
||||
this.currentY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
renderAgentCapBox({x, label}) {
|
||||
const config = this.theme.agentCap.box;
|
||||
const {height} = SVGShapes.renderBoxedText(label, {
|
||||
|
@ -449,7 +456,30 @@ define([
|
|||
};
|
||||
}
|
||||
|
||||
checkAgentRange(agents) {
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
const leftX = this.agentInfos.get(left).x;
|
||||
const rightX = this.agentInfos.get(right).x;
|
||||
this.agentInfos.forEach((agentInfo) => {
|
||||
if(agentInfo.x >= leftX && agentInfo.x <= rightX) {
|
||||
this.currentY = Math.max(this.currentY, agentInfo.latestY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
markAgentRange(agents) {
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
const leftX = this.agentInfos.get(left).x;
|
||||
const rightX = this.agentInfos.get(right).x;
|
||||
this.agentInfos.forEach((agentInfo) => {
|
||||
if(agentInfo.x >= leftX && agentInfo.x <= rightX) {
|
||||
agentInfo.latestY = this.currentY;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderAgentBegin({mode, agents}) {
|
||||
this.checkAgentRange(agents);
|
||||
let maxHeight = 0;
|
||||
agents.forEach((agent) => {
|
||||
const agentInfo = this.agentInfos.get(agent);
|
||||
|
@ -458,9 +488,11 @@ define([
|
|||
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
||||
});
|
||||
this.currentY += maxHeight + this.theme.actionMargin;
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderAgentEnd({mode, agents}) {
|
||||
this.checkAgentRange(agents);
|
||||
let maxHeight = 0;
|
||||
agents.forEach((agent) => {
|
||||
const agentInfo = this.agentInfos.get(agent);
|
||||
|
@ -477,6 +509,7 @@ define([
|
|||
agentInfo.latestYStart = null;
|
||||
});
|
||||
this.currentY += maxHeight + this.theme.actionMargin;
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderSelfConnection({label, agents, line, left, right}) {
|
||||
|
@ -609,11 +642,13 @@ define([
|
|||
}
|
||||
|
||||
renderConnection(stage) {
|
||||
this.checkAgentRange(stage.agents);
|
||||
if(stage.agents[0] === stage.agents[1]) {
|
||||
this.renderSelfConnection(stage);
|
||||
} else {
|
||||
this.renderSimpleConnection(stage);
|
||||
}
|
||||
this.markAgentRange(stage.agents);
|
||||
}
|
||||
|
||||
renderNote({xMid = null, x0 = null, x1 = null}, anchor, mode, label) {
|
||||
|
@ -679,6 +714,7 @@ define([
|
|||
}
|
||||
|
||||
renderNoteOver({agents, mode, label}) {
|
||||
this.checkAgentRange(agents);
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
if(agents.length > 1) {
|
||||
|
@ -691,25 +727,31 @@ define([
|
|||
const xMid = this.agentInfos.get(agents[0]).x;
|
||||
this.renderNote({xMid}, 'middle', mode, label);
|
||||
}
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderNoteLeft({agents, mode, label}) {
|
||||
this.checkAgentRange(agents);
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
const {left} = this.findExtremes(agents);
|
||||
const x1 = this.agentInfos.get(left).x - config.margin.right;
|
||||
this.renderNote({x1}, 'end', mode, label);
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderNoteRight({agents, mode, label}) {
|
||||
this.checkAgentRange(agents);
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
const {right} = this.findExtremes(agents);
|
||||
const x0 = this.agentInfos.get(right).x + config.margin.left;
|
||||
this.renderNote({x0}, 'start', mode, label);
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderNoteBetween({agents, mode, label}) {
|
||||
this.checkAgentRange(agents);
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
const xMid = (
|
||||
this.agentInfos.get(left).x +
|
||||
|
@ -717,16 +759,20 @@ define([
|
|||
) / 2;
|
||||
|
||||
this.renderNote({xMid}, 'middle', mode, label);
|
||||
this.markAgentRange(agents);
|
||||
}
|
||||
|
||||
renderBlockBegin(scope) {
|
||||
renderBlockBegin(scope, {left, right}) {
|
||||
this.checkAgentRange([left, right]);
|
||||
this.currentY += this.theme.block.margin.top;
|
||||
|
||||
scope.y = this.currentY;
|
||||
scope.first = true;
|
||||
this.markAgentRange([left, right]);
|
||||
}
|
||||
|
||||
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
||||
this.checkAgentRange([left, right]);
|
||||
const config = this.theme.block;
|
||||
const agentInfoL = this.agentInfos.get(left);
|
||||
const agentInfoR = this.agentInfos.get(right);
|
||||
|
@ -767,12 +813,14 @@ define([
|
|||
Math.max(modeRender.height, labelRender.height) +
|
||||
config.section.padding.top
|
||||
);
|
||||
this.markAgentRange([left, right]);
|
||||
}
|
||||
|
||||
renderSectionEnd(/*scope, block, section*/) {
|
||||
}
|
||||
|
||||
renderBlockEnd(scope, {left, right}) {
|
||||
this.checkAgentRange([left, right]);
|
||||
const config = this.theme.block;
|
||||
this.currentY += config.section.padding.bottom;
|
||||
|
||||
|
@ -786,6 +834,7 @@ define([
|
|||
}, config.boxAttrs)));
|
||||
|
||||
this.currentY += config.margin.bottom + this.theme.actionMargin;
|
||||
this.markAgentRange([left, right]);
|
||||
}
|
||||
|
||||
addAction(stage) {
|
||||
|
@ -835,6 +884,7 @@ define([
|
|||
index,
|
||||
x: null,
|
||||
latestYStart: null,
|
||||
latestY: 0,
|
||||
separations: new Map(),
|
||||
});
|
||||
});
|
||||
|
@ -884,6 +934,7 @@ define([
|
|||
svg.empty(this.sections);
|
||||
svg.empty(this.actionShapes);
|
||||
svg.empty(this.actionLabels);
|
||||
this.marks.clear();
|
||||
|
||||
this.title.set({
|
||||
attrs: this.theme.titleAttrs,
|
||||
|
@ -896,6 +947,7 @@ define([
|
|||
|
||||
this.currentY = 0;
|
||||
traverse(sequence.stages, this.renderTraversalFns);
|
||||
this.checkAgentRange(['[', ']']);
|
||||
|
||||
const stagesHeight = Math.max(
|
||||
this.currentY - this.theme.actionMargin,
|
||||
|
|
Loading…
Reference in New Issue