Add support for creating and destroying agents during connections
This commit is contained in:
parent
b58506d546
commit
7457131d1e
|
@ -55,6 +55,10 @@ Foo -> Foo: Foo talks to itself
|
||||||
Foo -> +Bar: Foo asks Bar
|
Foo -> +Bar: Foo asks Bar
|
||||||
-Bar --> Foo: and Bar replies
|
-Bar --> Foo: and Bar replies
|
||||||
|
|
||||||
|
# * and ! cause agents to be created and destroyed inline
|
||||||
|
Bar -> *Baz
|
||||||
|
Bar <- !Baz
|
||||||
|
|
||||||
# Arrows leaving on the left and right of the diagram
|
# Arrows leaving on the left and right of the diagram
|
||||||
[ -> Foo: From the left
|
[ -> Foo: From the left
|
||||||
[ <- Foo: To the left
|
[ <- Foo: To the left
|
||||||
|
@ -258,7 +262,7 @@ Note: the linter can't run from the local filesystem, so you'll need to
|
||||||
run a local HTTP server to ensure linting is successful. One option if
|
run a local HTTP server to ensure linting is successful. One option if
|
||||||
you have NPM installed is:
|
you have NPM installed is:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
# Setup
|
# Setup
|
||||||
npm install http-server -g;
|
npm install http-server -g;
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 53 KiB |
|
@ -18,7 +18,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const SAMPLE_REGEX = new RegExp(
|
const SAMPLE_REGEX = new RegExp(
|
||||||
/<img src="screenshots\/([^"]*)"[^>]*>[\s]*```([^]+?)```/g
|
/<img src="screenshots\/([^"]*)"[^>]*>[\s]*```(?!shell).*\n([^]+?)```/g
|
||||||
);
|
);
|
||||||
|
|
||||||
function findSamples(content) {
|
function findSamples(content) {
|
||||||
|
|
|
@ -46,24 +46,57 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CM_CONNECT = {type: 'keyword', suggest: true, then: {
|
function makeCMOperatorBlock(exit) {
|
||||||
'+': {type: 'operator', suggest: true, then: {'': CM_AGENT_TO_OPTTEXT}},
|
const op = {type: 'operator', suggest: true, then: {
|
||||||
'-': {type: 'operator', suggest: true, then: {'': CM_AGENT_TO_OPTTEXT}},
|
'+': CM_ERROR,
|
||||||
'': CM_AGENT_TO_OPTTEXT,
|
'-': CM_ERROR,
|
||||||
}};
|
'*': CM_ERROR,
|
||||||
|
'!': CM_ERROR,
|
||||||
|
'': exit,
|
||||||
|
}};
|
||||||
|
const pm = {type: 'operator', suggest: true, then: {
|
||||||
|
'+': CM_ERROR,
|
||||||
|
'-': CM_ERROR,
|
||||||
|
'*': op,
|
||||||
|
'!': op,
|
||||||
|
'': exit,
|
||||||
|
}};
|
||||||
|
const se = {type: 'operator', suggest: true, then: {
|
||||||
|
'+': op,
|
||||||
|
'-': op,
|
||||||
|
'*': CM_ERROR,
|
||||||
|
'!': CM_ERROR,
|
||||||
|
'': exit,
|
||||||
|
}};
|
||||||
|
return {
|
||||||
|
'+': pm,
|
||||||
|
'-': pm,
|
||||||
|
'*': se,
|
||||||
|
'!': se,
|
||||||
|
'': exit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const CM_CONNECT_FULL = {type: 'variable', suggest: 'Agent', then: {
|
function makeCMConnect() {
|
||||||
'->': CM_CONNECT,
|
const connect = {
|
||||||
'-->': CM_CONNECT,
|
type: 'keyword',
|
||||||
'<-': CM_CONNECT,
|
suggest: true,
|
||||||
'<--': CM_CONNECT,
|
then: makeCMOperatorBlock(CM_AGENT_TO_OPTTEXT),
|
||||||
'<->': CM_CONNECT,
|
};
|
||||||
'<-->': CM_CONNECT,
|
|
||||||
':': {type: 'operator', suggest: true, override: 'Label', then: {}},
|
|
||||||
'': 0,
|
|
||||||
}};
|
|
||||||
|
|
||||||
const CM_COMMANDS = {type: 'error line-error', then: {
|
return makeCMOperatorBlock({type: 'variable', suggest: 'Agent', then: {
|
||||||
|
'->': connect,
|
||||||
|
'-->': connect,
|
||||||
|
'<-': connect,
|
||||||
|
'<--': connect,
|
||||||
|
'<->': connect,
|
||||||
|
'<-->': connect,
|
||||||
|
':': {type: 'operator', suggest: true, override: 'Label', then: {}},
|
||||||
|
'': 0,
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const CM_COMMANDS = {type: 'error line-error', then: Object.assign({
|
||||||
'title': {type: 'keyword', suggest: true, then: {
|
'title': {type: 'keyword', suggest: true, then: {
|
||||||
'': CM_TEXT_TO_END,
|
'': CM_TEXT_TO_END,
|
||||||
}},
|
}},
|
||||||
|
@ -134,10 +167,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
'+': {type: 'operator', suggest: true, then: {'': CM_CONNECT_FULL}},
|
}, makeCMConnect())};
|
||||||
'-': {type: 'operator', suggest: true, then: {'': CM_CONNECT_FULL}},
|
|
||||||
'': CM_CONNECT_FULL,
|
|
||||||
}};
|
|
||||||
|
|
||||||
function cmCappedToken(token, current) {
|
function cmCappedToken(token, current) {
|
||||||
if(Object.keys(current.then).length > 0) {
|
if(Object.keys(current.then).length > 0) {
|
||||||
|
|
|
@ -25,8 +25,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return agent.name;
|
return agent.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function agentHasFlag(flag) {
|
function agentHasFlag(flag, has = true) {
|
||||||
return (agent) => agent.flags.includes(flag);
|
return (agent) => (agent.flags.includes(flag) === has);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MERGABLE = {
|
const MERGABLE = {
|
||||||
|
@ -235,7 +235,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
array.mergeSets(this.agents, agents, agentEqCheck);
|
array.mergeSets(this.agents, agents, agentEqCheck);
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentVis(agents, visible, mode, checked = false) {
|
setAgentVisRaw(agents, visible, mode, checked = false) {
|
||||||
const filteredAgents = agents.filter((agent) => {
|
const filteredAgents = agents.filter((agent) => {
|
||||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||||
if(state.locked) {
|
if(state.locked) {
|
||||||
|
@ -269,6 +269,15 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAgentVis(agents, visible, mode, checked = false) {
|
||||||
|
return this.setAgentVisRaw(
|
||||||
|
agents.map(convertAgent),
|
||||||
|
visible,
|
||||||
|
mode,
|
||||||
|
checked
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setAgentHighlight(agents, highlighted, checked = false) {
|
setAgentHighlight(agents, highlighted, checked = false) {
|
||||||
const filteredAgents = agents.filter((agent) => {
|
const filteredAgents = agents.filter((agent) => {
|
||||||
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
const state = this.agentStates.get(agent.name) || DEFAULT_AGENT;
|
||||||
|
@ -340,15 +349,24 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConnect({agents, label, options}) {
|
handleConnect({agents, label, options}) {
|
||||||
const colAgents = agents.map(convertAgent);
|
const beginAgents = agents.filter(agentHasFlag('begin'));
|
||||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
const endAgents = agents.filter(agentHasFlag('end'));
|
||||||
this.defineAgents(colAgents);
|
if(array.hasIntersection(beginAgents, endAgents, agentEqCheck)) {
|
||||||
|
throw new Error('Cannot set agent visibility multiple times');
|
||||||
|
}
|
||||||
|
|
||||||
const startAgents = agents.filter(agentHasFlag('start'));
|
const startAgents = agents.filter(agentHasFlag('start'));
|
||||||
const stopAgents = agents.filter(agentHasFlag('stop'));
|
const stopAgents = agents.filter(agentHasFlag('stop'));
|
||||||
|
array.mergeSets(stopAgents, endAgents);
|
||||||
if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) {
|
if(array.hasIntersection(startAgents, stopAgents, agentEqCheck)) {
|
||||||
throw new Error('Cannot set agent highlighting multiple times');
|
throw new Error('Cannot set agent highlighting multiple times');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.defineAgents(agents.map(convertAgent));
|
||||||
|
|
||||||
|
const implicitBegin = agents.filter(agentHasFlag('begin', false));
|
||||||
|
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
||||||
|
|
||||||
const connectStage = {
|
const connectStage = {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
agentNames: agents.map(getAgentName),
|
agentNames: agents.map(getAgentName),
|
||||||
|
@ -357,9 +375,11 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
|
this.setAgentVis(beginAgents, true, 'box', true),
|
||||||
this.setAgentHighlight(startAgents, true, true),
|
this.setAgentHighlight(startAgents, true, true),
|
||||||
connectStage,
|
connectStage,
|
||||||
this.setAgentHighlight(stopAgents, false, true),
|
this.setAgentHighlight(stopAgents, false, true),
|
||||||
|
this.setAgentVis(endAgents, false, 'cross', true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +391,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
colAgents = agents.map(convertAgent);
|
colAgents = agents.map(convertAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
this.addStage(this.setAgentVisRaw(colAgents, true, 'box'));
|
||||||
this.defineAgents(colAgents);
|
this.defineAgents(colAgents);
|
||||||
|
|
||||||
this.addStage({
|
this.addStage({
|
||||||
|
@ -383,23 +403,17 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentDefine({agents}) {
|
handleAgentDefine({agents}) {
|
||||||
const colAgents = agents.map(convertAgent);
|
this.defineAgents(agents.map(convertAgent));
|
||||||
this.defineAgents(colAgents);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentBegin({agents, mode}) {
|
handleAgentBegin({agents, mode}) {
|
||||||
this.addStage(this.setAgentVis(
|
this.addStage(this.setAgentVis(agents, true, mode, true));
|
||||||
agents.map(convertAgent),
|
|
||||||
true,
|
|
||||||
mode,
|
|
||||||
true
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentEnd({agents, mode}) {
|
handleAgentEnd({agents, mode}) {
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentHighlight(agents, false),
|
this.setAgentHighlight(agents, false),
|
||||||
this.setAgentVis(agents.map(convertAgent), false, mode, true),
|
this.setAgentVis(agents, false, mode, true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +485,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentHighlight(this.agents, false),
|
this.setAgentHighlight(this.agents, false),
|
||||||
this.setAgentVis(this.agents, false, terminators),
|
this.setAgentVisRaw(this.agents, false, terminators),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
addBounds(
|
addBounds(
|
||||||
|
|
|
@ -426,6 +426,51 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds parallel begin stages', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.connect(['A', {name: 'B', flags: ['begin']}]),
|
||||||
|
]});
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
GENERATED.beginAgents(['A']),
|
||||||
|
GENERATED.parallel([
|
||||||
|
GENERATED.beginAgents(['B']),
|
||||||
|
GENERATED.connect(['A', 'B']),
|
||||||
|
]),
|
||||||
|
GENERATED.endAgents(['A', 'B']),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds parallel end stages', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||||
|
]});
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
GENERATED.beginAgents(['A', 'B']),
|
||||||
|
GENERATED.parallel([
|
||||||
|
GENERATED.connect(['A', 'B']),
|
||||||
|
GENERATED.endAgents(['B']),
|
||||||
|
]),
|
||||||
|
GENERATED.endAgents(['A']),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('implicitly ends highlighting when ending a stage', () => {
|
||||||
|
const sequence = generator.generate({stages: [
|
||||||
|
PARSED.connect(['A', {name: 'B', flags: ['start']}]),
|
||||||
|
PARSED.connect(['A', {name: 'B', flags: ['end']}]),
|
||||||
|
]});
|
||||||
|
expect(sequence.stages).toEqual([
|
||||||
|
jasmine.anything(),
|
||||||
|
jasmine.anything(),
|
||||||
|
GENERATED.parallel([
|
||||||
|
GENERATED.connect(['A', 'B']),
|
||||||
|
GENERATED.highlight(['B'], false),
|
||||||
|
GENERATED.endAgents(['B']),
|
||||||
|
]),
|
||||||
|
GENERATED.endAgents(['A']),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects conflicting flags', () => {
|
it('rejects conflicting flags', () => {
|
||||||
expect(() => generator.generate({stages: [
|
expect(() => generator.generate({stages: [
|
||||||
PARSED.connect(['A', {name: 'B', flags: ['start', 'stop']}]),
|
PARSED.connect(['A', {name: 'B', flags: ['start', 'stop']}]),
|
||||||
|
@ -437,6 +482,17 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
||||||
{name: 'A', flags: ['stop']},
|
{name: 'A', flags: ['stop']},
|
||||||
]),
|
]),
|
||||||
]})).toThrow();
|
]})).toThrow();
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.connect(['A', {name: 'B', flags: ['begin', 'end']}]),
|
||||||
|
]})).toThrow();
|
||||||
|
|
||||||
|
expect(() => generator.generate({stages: [
|
||||||
|
PARSED.connect([
|
||||||
|
{name: 'A', flags: ['begin']},
|
||||||
|
{name: 'A', flags: ['end']},
|
||||||
|
]),
|
||||||
|
]})).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds implicit highlight end with implicit terminator', () => {
|
it('adds implicit highlight end with implicit terminator', () => {
|
||||||
|
|
|
@ -26,8 +26,10 @@ define([
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONNECT_AGENT_FLAGS = {
|
const CONNECT_AGENT_FLAGS = {
|
||||||
|
'*': 'begin',
|
||||||
'+': 'start',
|
'+': 'start',
|
||||||
'-': 'stop',
|
'-': 'stop',
|
||||||
|
'!': 'end',
|
||||||
};
|
};
|
||||||
|
|
||||||
const TERMINATOR_TYPES = [
|
const TERMINATOR_TYPES = [
|
||||||
|
@ -116,13 +118,20 @@ define([
|
||||||
const flags = [];
|
const flags = [];
|
||||||
let p = start;
|
let p = start;
|
||||||
for(; p < end; ++ p) {
|
for(; p < end; ++ p) {
|
||||||
const flag = flagTypes[tokenKeyword(line[p])];
|
const rawFlag = tokenKeyword(line[p]);
|
||||||
|
const flag = flagTypes[rawFlag];
|
||||||
if(flag) {
|
if(flag) {
|
||||||
|
if(flags.includes(flag)) {
|
||||||
|
throw new Error('Duplicate agent flag: ' + rawFlag);
|
||||||
|
}
|
||||||
flags.push(flag);
|
flags.push(flag);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(p >= end) {
|
||||||
|
throw new Error('Missing agent name');
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: joinLabel(line, p, end),
|
name: joinLabel(line, p, end),
|
||||||
flags,
|
flags,
|
||||||
|
|
|
@ -79,13 +79,13 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses optional flags', () => {
|
it('parses optional flags', () => {
|
||||||
const parsed = parser.parse('+A -> -B');
|
const parsed = parser.parse('+A -> -*!B');
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
{
|
{
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
agents: [
|
agents: [
|
||||||
{name: 'A', flags: ['start']},
|
{name: 'A', flags: ['start']},
|
||||||
{name: 'B', flags: ['stop']},
|
{name: 'B', flags: ['stop', 'begin', 'end']},
|
||||||
],
|
],
|
||||||
label: jasmine.anything(),
|
label: jasmine.anything(),
|
||||||
options: jasmine.anything(),
|
options: jasmine.anything(),
|
||||||
|
@ -93,6 +93,15 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects duplicate flags', () => {
|
||||||
|
expect(() => parser.parse('A -> +*+B')).toThrow();
|
||||||
|
expect(() => parser.parse('A -> **B')).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects missing agent names', () => {
|
||||||
|
expect(() => parser.parse('A -> +')).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
it('converts multiple entries', () => {
|
it('converts multiple entries', () => {
|
||||||
const parsed = parser.parse('A -> B\nB -> A');
|
const parsed = parser.parse('A -> B\nB -> A');
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
|
|
|
@ -161,10 +161,10 @@ define([
|
||||||
const agentSpaces = new Map();
|
const agentSpaces = new Map();
|
||||||
const agentNames = this.visibleAgents.slice();
|
const agentNames = this.visibleAgents.slice();
|
||||||
|
|
||||||
const addSpacing = (agentName, spacing) => {
|
const addSpacing = (agentName, {left, right}) => {
|
||||||
const current = agentSpaces.get(agentName);
|
const current = agentSpaces.get(agentName);
|
||||||
current.left = Math.max(current.left, spacing.left);
|
current.left = Math.max(current.left, left);
|
||||||
current.right = Math.max(current.right, spacing.right);
|
current.right = Math.max(current.right, right);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.agentInfos.forEach((agentInfo) => {
|
this.agentInfos.forEach((agentInfo) => {
|
||||||
|
@ -366,7 +366,7 @@ define([
|
||||||
const touchedAgentNames = [];
|
const touchedAgentNames = [];
|
||||||
stages.forEach((stage) => {
|
stages.forEach((stage) => {
|
||||||
const component = this.components.get(stage.type);
|
const component = this.components.get(stage.type);
|
||||||
const r = component.renderPre(stage, envPre);
|
const r = component.renderPre(stage, envPre) || {};
|
||||||
if(r.topShift !== undefined) {
|
if(r.topShift !== undefined) {
|
||||||
maxTopShift = Math.max(maxTopShift, r.topShift);
|
maxTopShift = Math.max(maxTopShift, r.topShift);
|
||||||
}
|
}
|
||||||
|
@ -460,6 +460,7 @@ define([
|
||||||
x: null,
|
x: null,
|
||||||
latestYStart: null,
|
latestYStart: null,
|
||||||
currentRad: 0,
|
currentRad: 0,
|
||||||
|
currentMaxRad: 0,
|
||||||
latestY: 0,
|
latestY: 0,
|
||||||
maxRPad: 0,
|
maxRPad: 0,
|
||||||
maxLPad: 0,
|
maxLPad: 0,
|
||||||
|
|
|
@ -31,10 +31,13 @@ define(['./CodeMirrorMode'], (CMMode) => {
|
||||||
unescape,
|
unescape,
|
||||||
baseToken: {q: true},
|
baseToken: {q: true},
|
||||||
},
|
},
|
||||||
{start: /(?=[^ \t\r\n:+\-<>,])/y, end: /(?=[ \t\r\n:+\-<>,])|$/y},
|
{start: /(?=[^ \t\r\n:+\-*!<>,])/y, end: /(?=[ \t\r\n:+\-*!<>,])|$/y},
|
||||||
{start: /(?=[+\-<>])/y, end: /(?=[^+\-<>])|$/y},
|
{start: /(?=[\-<>])/y, end: /(?=[^\-<>])|$/y},
|
||||||
{start: /,/y, baseToken: {v: ','}},
|
{start: /,/y, baseToken: {v: ','}},
|
||||||
{start: /:/y, baseToken: {v: ':'}},
|
{start: /:/y, baseToken: {v: ':'}},
|
||||||
|
{start: /!/y, baseToken: {v: '!'}},
|
||||||
|
{start: /\+/y, baseToken: {v: '+'}},
|
||||||
|
{start: /\*/y, baseToken: {v: '*'}},
|
||||||
{start: /\n/y, baseToken: {v: '\n'}},
|
{start: /\n/y, baseToken: {v: '\n'}},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,18 @@ define([
|
||||||
return {
|
return {
|
||||||
left: width / 2,
|
left: width / 2,
|
||||||
right: width / 2,
|
right: width / 2,
|
||||||
|
radius: width / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
topShift() {
|
topShift({label}, env) {
|
||||||
return 0;
|
const config = env.theme.agentCap.box;
|
||||||
|
const height = (
|
||||||
|
env.textSizer.measureHeight(config.labelAttrs, label) +
|
||||||
|
config.padding.top +
|
||||||
|
config.padding.bottom
|
||||||
|
);
|
||||||
|
return Math.max(0, height - config.arrowBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(y, {x, label}, env) {
|
render(y, {x, label}, env) {
|
||||||
|
@ -57,6 +64,7 @@ define([
|
||||||
return {
|
return {
|
||||||
left: config.size / 2,
|
left: config.size / 2,
|
||||||
right: config.size / 2,
|
right: config.size / 2,
|
||||||
|
radius: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +106,7 @@ define([
|
||||||
return {
|
return {
|
||||||
left: width / 2,
|
left: width / 2,
|
||||||
right: width / 2,
|
right: width / 2,
|
||||||
|
radius: width / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +140,11 @@ define([
|
||||||
|
|
||||||
class CapNone {
|
class CapNone {
|
||||||
separation({currentRad}) {
|
separation({currentRad}) {
|
||||||
return {left: currentRad, right: currentRad};
|
return {
|
||||||
|
left: currentRad,
|
||||||
|
right: currentRad,
|
||||||
|
radius: currentRad,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
topShift(agentInfo, env) {
|
topShift(agentInfo, env) {
|
||||||
|
@ -162,18 +175,24 @@ define([
|
||||||
this.begin = begin;
|
this.begin = begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
separationPre({mode, agentNames}, env) {
|
||||||
|
agentNames.forEach((name) => {
|
||||||
|
const agentInfo = env.agentInfos.get(name);
|
||||||
|
const sep = AGENT_CAPS[mode].separation(agentInfo, env);
|
||||||
|
env.addSpacing(name, sep);
|
||||||
|
agentInfo.currentMaxRad = Math.max(
|
||||||
|
agentInfo.currentMaxRad,
|
||||||
|
sep.radius
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
separation({mode, agentNames}, env) {
|
separation({mode, agentNames}, env) {
|
||||||
if(this.begin) {
|
if(this.begin) {
|
||||||
array.mergeSets(env.visibleAgents, agentNames);
|
array.mergeSets(env.visibleAgents, agentNames);
|
||||||
} else {
|
} else {
|
||||||
array.removeAll(env.visibleAgents, agentNames);
|
array.removeAll(env.visibleAgents, agentNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
agentNames.forEach((name) => {
|
|
||||||
const agentInfo = env.agentInfos.get(name);
|
|
||||||
const separationFn = AGENT_CAPS[mode].separation;
|
|
||||||
env.addSpacing(name, separationFn(agentInfo, env));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre({mode, agentNames}, env) {
|
renderPre({mode, agentNames}, env) {
|
||||||
|
@ -182,6 +201,9 @@ define([
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(name);
|
||||||
const topShift = AGENT_CAPS[mode].topShift(agentInfo, env);
|
const topShift = AGENT_CAPS[mode].topShift(agentInfo, env);
|
||||||
maxTopShift = Math.max(maxTopShift, topShift);
|
maxTopShift = Math.max(maxTopShift, topShift);
|
||||||
|
|
||||||
|
const r = AGENT_CAPS[mode].separation(agentInfo, env).radius;
|
||||||
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
agentNames,
|
agentNames,
|
||||||
|
|
|
@ -2,21 +2,32 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class AgentHighlight extends BaseComponent {
|
class AgentHighlight extends BaseComponent {
|
||||||
|
radius(highlighted, env) {
|
||||||
|
return highlighted ? env.theme.agentLineHighlightRadius : 0;
|
||||||
|
}
|
||||||
|
|
||||||
separationPre({agentNames, highlighted}, env) {
|
separationPre({agentNames, highlighted}, env) {
|
||||||
const rad = highlighted ? env.theme.agentLineHighlightRadius : 0;
|
const r = this.radius(highlighted, env);
|
||||||
agentNames.forEach((name) => {
|
agentNames.forEach((name) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(name);
|
||||||
const maxRad = Math.max(agentInfo.currentMaxRad, rad);
|
agentInfo.currentRad = r;
|
||||||
agentInfo.currentRad = rad;
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
agentInfo.currentMaxRad = maxRad;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPre({agentNames, highlighted}, env) {
|
||||||
|
const r = this.radius(highlighted, env);
|
||||||
|
agentNames.forEach((name) => {
|
||||||
|
const agentInfo = env.agentInfos.get(name);
|
||||||
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render({agentNames, highlighted}, env) {
|
render({agentNames, highlighted}, env) {
|
||||||
const rad = highlighted ? env.theme.agentLineHighlightRadius : 0;
|
const r = this.radius(highlighted, env);
|
||||||
agentNames.forEach((name) => {
|
agentNames.forEach((name) => {
|
||||||
env.drawAgentLine(name, env.primaryY);
|
env.drawAgentLine(name, env.primaryY);
|
||||||
env.agentInfos.get(name).currentRad = rad;
|
env.agentInfos.get(name).currentRad = r;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ define(() => {
|
||||||
textSizer,
|
textSizer,
|
||||||
state,
|
state,
|
||||||
}*/) {
|
}*/) {
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(/*stage, {
|
render(/*stage, {
|
||||||
|
@ -49,7 +48,6 @@ define(() => {
|
||||||
SVGTextBlockClass,
|
SVGTextBlockClass,
|
||||||
state,
|
state,
|
||||||
}*/) {
|
}*/) {
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ define([
|
||||||
config.label.margin.bottom
|
config.label.margin.bottom
|
||||||
);
|
);
|
||||||
|
|
||||||
const lineX = from.x + from.currentRad;
|
const lineX = from.x + from.currentMaxRad;
|
||||||
const y0 = env.primaryY;
|
const y0 = env.primaryY;
|
||||||
const x0 = (
|
const x0 = (
|
||||||
lineX +
|
lineX +
|
||||||
|
@ -159,8 +159,8 @@ define([
|
||||||
config.label.margin.bottom
|
config.label.margin.bottom
|
||||||
);
|
);
|
||||||
|
|
||||||
const x0 = from.x + from.currentRad * dir;
|
const x0 = from.x + from.currentMaxRad * dir;
|
||||||
const x1 = to.x - to.currentRad * dir;
|
const x1 = to.x - to.currentMaxRad * dir;
|
||||||
let y = env.primaryY;
|
let y = env.primaryY;
|
||||||
|
|
||||||
SVGShapes.renderBoxedText(label, {
|
SVGShapes.renderBoxedText(label, {
|
||||||
|
|
|
@ -131,8 +131,8 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
return this.renderNote({
|
return this.renderNote({
|
||||||
x0: infoL.x - infoL.currentRad - config.overlap.left,
|
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
|
||||||
x1: infoR.x + infoR.currentRad + config.overlap.right,
|
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
|
||||||
anchor: 'middle',
|
anchor: 'middle',
|
||||||
mode,
|
mode,
|
||||||
label,
|
label,
|
||||||
|
@ -186,7 +186,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
||||||
if(this.isRight) {
|
if(this.isRight) {
|
||||||
const info = env.agentInfos.get(right);
|
const info = env.agentInfos.get(right);
|
||||||
const x0 = info.x + info.currentRad + config.margin.left;
|
const x0 = info.x + info.currentMaxRad + config.margin.left;
|
||||||
return this.renderNote({
|
return this.renderNote({
|
||||||
x0,
|
x0,
|
||||||
anchor: 'start',
|
anchor: 'start',
|
||||||
|
@ -195,7 +195,7 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
}, env);
|
}, env);
|
||||||
} else {
|
} else {
|
||||||
const info = env.agentInfos.get(left);
|
const info = env.agentInfos.get(left);
|
||||||
const x1 = info.x - info.currentRad - config.margin.right;
|
const x1 = info.x - info.currentMaxRad - config.margin.right;
|
||||||
return this.renderNote({
|
return this.renderNote({
|
||||||
x1,
|
x1,
|
||||||
anchor: 'end',
|
anchor: 'end',
|
||||||
|
@ -232,8 +232,8 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
const xMid = (
|
const xMid = (
|
||||||
infoL.x + infoL.currentRad +
|
infoL.x + infoL.currentMaxRad +
|
||||||
infoR.x - infoR.currentRad
|
infoR.x - infoR.currentMaxRad
|
||||||
) / 2;
|
) / 2;
|
||||||
|
|
||||||
return this.renderNote({
|
return this.renderNote({
|
||||||
|
|
|
@ -108,176 +108,32 @@ defineDescribe('Sequence Integration', [
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders the "Simple Usage" example without error', () => {
|
const SAMPLE_REGEX = new RegExp(
|
||||||
const parsed = parser.parse(
|
/```(?!shell).*\n([^]+?)```/g
|
||||||
'title Labyrinth\n' +
|
);
|
||||||
'\n' +
|
|
||||||
'Bowie -> Gremlin: You remind me of the babe\n' +
|
|
||||||
'Gremlin -> Bowie: What babe?\n' +
|
|
||||||
'Bowie -> Gremlin: The babe with the power\n' +
|
|
||||||
'Gremlin -> Bowie: What power?\n' +
|
|
||||||
'note right of Bowie, Gremlin: Most people get muddled here!\n' +
|
|
||||||
'Bowie -> Gremlin: \'The power of voodoo\'\n' +
|
|
||||||
'Gremlin -> Bowie: "Who-do?"\n' +
|
|
||||||
'Bowie -> Gremlin: You do!\n' +
|
|
||||||
'Gremlin -> Bowie: Do what?\n' +
|
|
||||||
'Bowie -> Gremlin: Remind me of the babe!\n' +
|
|
||||||
'\n' +
|
|
||||||
'Bowie -> Audience: Sings\n' +
|
|
||||||
'\n' +
|
|
||||||
'terminators box\n'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Connection Types" example without error', () => {
|
function findSamples(content) {
|
||||||
const parsed = parser.parse(
|
SAMPLE_REGEX.lastIndex = 0;
|
||||||
'title Connection Types\n' +
|
const results = [];
|
||||||
'\n' +
|
while(true) {
|
||||||
'Foo -> Bar: Simple arrow\n' +
|
const match = SAMPLE_REGEX.exec(content);
|
||||||
'Foo --> Bar: Dashed arrow\n' +
|
if(!match) {
|
||||||
'Foo <- Bar: Reversed arrow\n' +
|
break;
|
||||||
'Foo <-- Bar: Reversed dashed arrow\n' +
|
}
|
||||||
'Foo <-> Bar: Double arrow\n' +
|
results.push(match[1]);
|
||||||
'Foo <--> Bar: Double dashed arrow\n' +
|
}
|
||||||
'\n' +
|
return results;
|
||||||
'# An arrow with no label:\n' +
|
}
|
||||||
'Foo -> Bar\n' +
|
|
||||||
'\n' +
|
|
||||||
'Foo -> Foo: Foo talks to itself\n' +
|
|
||||||
'\n' +
|
|
||||||
'# Arrows leaving on the left and right of the diagram\n' +
|
|
||||||
'[ -> Foo: From the left\n' +
|
|
||||||
'[ <- Foo: To the left\n' +
|
|
||||||
'Foo -> ]: To the right\n' +
|
|
||||||
'Foo <- ]: From the right\n' +
|
|
||||||
'[ -> ]: Left to right!\n' +
|
|
||||||
'# (etc.)\n'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Notes & State" example without error', () => {
|
return (fetch('README.md')
|
||||||
const parsed = parser.parse(
|
.then((response) => response.text())
|
||||||
'title Note Placements\n' +
|
.then(findSamples)
|
||||||
'\n' +
|
.then((samples) => samples.forEach((code, i) => {
|
||||||
'note over Foo: Foo says something\n' +
|
it('Renders readme example #' + (i + 1) + ' without error', () => {
|
||||||
'note left of Foo: Stuff\n' +
|
const parsed = parser.parse(code);
|
||||||
'note right of Bar: More stuff\n' +
|
const sequence = generator.generate(parsed);
|
||||||
'note over Foo, Bar: "Foo and Bar\n' +
|
expect(() => renderer.render(sequence)).not.toThrow();
|
||||||
'on multiple lines"\n' +
|
});
|
||||||
'note between Foo, Bar: Link\n' +
|
}))
|
||||||
'\n' +
|
);
|
||||||
'text right: \'Comments\\nOver here\!\'\n' +
|
|
||||||
'\n' +
|
|
||||||
'state over Foo: Foo is ponderous'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Logic" example without error', () => {
|
|
||||||
const parsed = parser.parse(
|
|
||||||
'title At the Bank\n' +
|
|
||||||
'\n' +
|
|
||||||
'begin Person, ATM, Bank\n' +
|
|
||||||
'Person -> ATM: Request money\n' +
|
|
||||||
'ATM -> Bank: Check funds\n' +
|
|
||||||
'if fraud detected\n' +
|
|
||||||
' Bank -> Police: "Get \'em!"\n' +
|
|
||||||
' Police -> Person: "You\'re nicked"\n' +
|
|
||||||
' end Police\n' +
|
|
||||||
'else if sufficient funds\n' +
|
|
||||||
' ATM -> Bank: Withdraw funds\n' +
|
|
||||||
' repeat until "all requested money\n' +
|
|
||||||
' has been handed over"\n' +
|
|
||||||
' ATM -> Person: Dispense note\n' +
|
|
||||||
' end\n' +
|
|
||||||
'else\n' +
|
|
||||||
' ATM -> Person: Error\n' +
|
|
||||||
'end'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Multiline Text" example without error', () => {
|
|
||||||
const parsed = parser.parse(
|
|
||||||
'title \'My Multiline\n' +
|
|
||||||
'Title\'\n' +
|
|
||||||
'\n' +
|
|
||||||
'note over Foo: \'Also possible\\nwith escapes\'\n' +
|
|
||||||
'\n' +
|
|
||||||
'Foo -> Bar: \'Lines of text\\non this arrow\'\n' +
|
|
||||||
'\n' +
|
|
||||||
'if \'Even multiline\\ninside conditions like this\'\n' +
|
|
||||||
' Foo -> \'Multiline\\nagent\'\n' +
|
|
||||||
'end\n' +
|
|
||||||
'\n' +
|
|
||||||
'state over Foo: \'Newlines here,\\ntoo!\''
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Short-Lived Agents" example without error', () => {
|
|
||||||
const parsed = parser.parse(
|
|
||||||
'title "Baz doesn\'t live long"\n' +
|
|
||||||
'\n' +
|
|
||||||
'Foo -> Bar\n' +
|
|
||||||
'begin Baz\n' +
|
|
||||||
'Bar -> Baz\n' +
|
|
||||||
'Baz -> Foo\n' +
|
|
||||||
'end Baz\n' +
|
|
||||||
'Foo -> Bar\n' +
|
|
||||||
'\n' +
|
|
||||||
'# Foo and Bar end with black bars\n' +
|
|
||||||
'terminators bar\n' +
|
|
||||||
'# (options are: box, bar, cross, none)'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Alternative Agent Ordering" example without error', () => {
|
|
||||||
const parsed = parser.parse(
|
|
||||||
'define Baz, Foo\n' +
|
|
||||||
'Foo -> Bar\n' +
|
|
||||||
'Bar -> Baz\n'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders the "Simultaneous Actions" example without error', () => {
|
|
||||||
const parsed = parser.parse(
|
|
||||||
'begin A, B, C, D\n' +
|
|
||||||
'A -> C\n' +
|
|
||||||
'\n' +
|
|
||||||
'# Define a marker which can be returned to later\n' +
|
|
||||||
'\n' +
|
|
||||||
'some primary process:\n' +
|
|
||||||
'A -> B\n' +
|
|
||||||
'B -> A\n' +
|
|
||||||
'A -> B\n' +
|
|
||||||
'B -> A\n' +
|
|
||||||
'\n' +
|
|
||||||
'# Return to the defined marker\n' +
|
|
||||||
'# (should be interpreted as no-higher-then the marker; may be\n' +
|
|
||||||
'# pushed down to keep relative action ordering consistent)\n' +
|
|
||||||
'\n' +
|
|
||||||
'simultaneously with some primary process:\n' +
|
|
||||||
'C -> D\n' +
|
|
||||||
'D -> C\n' +
|
|
||||||
'end D\n' +
|
|
||||||
'C -> A\n' +
|
|
||||||
'\n' +
|
|
||||||
'# The marker name is optional; using "simultaneously:" with no\n' +
|
|
||||||
'# marker will jump to the top of the entire sequence.'
|
|
||||||
);
|
|
||||||
const sequence = generator.generate(parsed);
|
|
||||||
expect(() => renderer.render(sequence)).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,7 @@ define(['core/ArrayUtilities', 'svg/SVGShapes'], (array, SVGShapes) => {
|
||||||
right: 10,
|
right: 10,
|
||||||
bottom: 5,
|
bottom: 5,
|
||||||
},
|
},
|
||||||
|
arrowBottom: 5 + 12 * 1.3 / 2,
|
||||||
boxAttrs: {
|
boxAttrs: {
|
||||||
'fill': '#FFFFFF',
|
'fill': '#FFFFFF',
|
||||||
'stroke': '#000000',
|
'stroke': '#000000',
|
||||||
|
|
Loading…
Reference in New Issue