Add found messages and fix minor rendering issue with groups containing arrows to sides [#37]
This commit is contained in:
parent
b8491e25dc
commit
394dcb0e42
|
@ -645,8 +645,13 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
)};
|
||||
}
|
||||
|
||||
const colonTextToEnd = {
|
||||
type: 'operator',
|
||||
suggest: true,
|
||||
then: {'': textToEnd, '\n': hiddenEnd},
|
||||
};
|
||||
const agentListToText = agentListTo({
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
':': colonTextToEnd,
|
||||
});
|
||||
const agentList2ToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
|
@ -656,7 +661,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
const singleAgentToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
',': CM_ERROR,
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
':': colonTextToEnd,
|
||||
}};
|
||||
const agentToOptText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
|
@ -700,7 +705,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
}
|
||||
|
||||
function makeOpBlock(exit) {
|
||||
function makeOpBlock(exit, sourceExit) {
|
||||
const op = {type: 'operator', suggest: true, then: {
|
||||
'+': CM_ERROR,
|
||||
'-': CM_ERROR,
|
||||
|
@ -729,13 +734,13 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
'': exit,
|
||||
}},
|
||||
'*': {type: 'operator', suggest: true, then: {
|
||||
'*': {type: 'operator', suggest: true, then: Object.assign({
|
||||
'+': op,
|
||||
'-': op,
|
||||
'*': CM_ERROR,
|
||||
'!': CM_ERROR,
|
||||
'': exit,
|
||||
}},
|
||||
}, sourceExit)},
|
||||
'!': op,
|
||||
'': exit,
|
||||
};
|
||||
|
@ -745,7 +750,10 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
const connect = {
|
||||
type: 'keyword',
|
||||
suggest: true,
|
||||
then: makeOpBlock(agentToOptText),
|
||||
then: makeOpBlock(agentToOptText, {
|
||||
':': colonTextToEnd,
|
||||
'\n': hiddenEnd,
|
||||
}),
|
||||
};
|
||||
|
||||
const then = {'': 0};
|
||||
|
@ -756,7 +764,10 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
|||
override: 'Label',
|
||||
then: {},
|
||||
};
|
||||
return makeOpBlock({type: 'variable', suggest: 'Agent', then});
|
||||
return makeOpBlock(
|
||||
{type: 'variable', suggest: 'Agent', then},
|
||||
then
|
||||
);
|
||||
}
|
||||
|
||||
const BASE_THEN = {
|
||||
|
@ -1597,10 +1608,10 @@ define('sequence/Parser',[
|
|||
})());
|
||||
|
||||
const CONNECT_AGENT_FLAGS = {
|
||||
'*': 'begin',
|
||||
'+': 'start',
|
||||
'-': 'stop',
|
||||
'!': 'end',
|
||||
'*': {flag: 'begin', allowBlankName: true, blankNameFlag: 'source'},
|
||||
'+': {flag: 'start'},
|
||||
'-': {flag: 'stop'},
|
||||
'!': {flag: 'end'},
|
||||
};
|
||||
|
||||
const TERMINATOR_TYPES = [
|
||||
|
@ -1712,7 +1723,7 @@ define('sequence/Parser',[
|
|||
return -1;
|
||||
}
|
||||
|
||||
function readAgentAlias(line, start, end, enableAlias) {
|
||||
function readAgentAlias(line, start, end, {enableAlias, allowBlankName}) {
|
||||
let aliasSep = -1;
|
||||
if(enableAlias) {
|
||||
aliasSep = findToken(line, 'as', start);
|
||||
|
@ -1720,7 +1731,7 @@ define('sequence/Parser',[
|
|||
if(aliasSep === -1 || aliasSep >= end) {
|
||||
aliasSep = end;
|
||||
}
|
||||
if(start >= aliasSep) {
|
||||
if(start >= aliasSep && !allowBlankName) {
|
||||
throw makeError('Missing agent name', errToken(line, start));
|
||||
}
|
||||
return {
|
||||
|
@ -1734,25 +1745,31 @@ define('sequence/Parser',[
|
|||
aliases = false,
|
||||
} = {}) {
|
||||
const flags = [];
|
||||
const blankNameFlags = [];
|
||||
let p = start;
|
||||
let allowBlankName = false;
|
||||
for(; p < end; ++ p) {
|
||||
const token = line[p];
|
||||
const rawFlag = tokenKeyword(token);
|
||||
const flag = flagTypes[rawFlag];
|
||||
if(flag) {
|
||||
if(flags.includes(flag)) {
|
||||
throw makeError('Duplicate agent flag: ' + rawFlag, token);
|
||||
}
|
||||
flags.push(flag);
|
||||
} else {
|
||||
if(!flag) {
|
||||
break;
|
||||
}
|
||||
if(flags.includes(flag.flag)) {
|
||||
throw makeError('Duplicate agent flag: ' + rawFlag, token);
|
||||
}
|
||||
allowBlankName = allowBlankName || Boolean(flag.allowBlankName);
|
||||
flags.push(flag.flag);
|
||||
blankNameFlags.push(flag.blankNameFlag);
|
||||
}
|
||||
const {name, alias} = readAgentAlias(line, p, end, aliases);
|
||||
const {name, alias} = readAgentAlias(line, p, end, {
|
||||
enableAlias: aliases,
|
||||
allowBlankName,
|
||||
});
|
||||
return {
|
||||
name,
|
||||
alias,
|
||||
flags,
|
||||
flags: name ? flags : blankNameFlags,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2091,8 +2108,8 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
equals: (a, b) => {
|
||||
return a.id === b.id;
|
||||
},
|
||||
make: (id, {anchorRight = false} = {}) => {
|
||||
return {id, anchorRight};
|
||||
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => {
|
||||
return {id, anchorRight, isVirtualSource};
|
||||
},
|
||||
indexOf: (list, gAgent) => {
|
||||
return array.indexOf(list, gAgent, GAgent.equals);
|
||||
|
@ -2100,6 +2117,14 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
hasIntersection: (a, b) => {
|
||||
return array.hasIntersection(a, b, GAgent.equals);
|
||||
},
|
||||
addNearby: (target, reference, item, offset) => {
|
||||
const p = array.indexOf(target, reference, GAgent.equals);
|
||||
if(p === -1) {
|
||||
target.push(item);
|
||||
} else {
|
||||
target.splice(p + offset, 0, item);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const NOTE_DEFAULT_G_AGENTS = {
|
||||
|
@ -2108,6 +2133,8 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
'note right': [GAgent.make(']')],
|
||||
};
|
||||
|
||||
const SPECIAL_AGENT_IDS = ['[', ']'];
|
||||
|
||||
const MERGABLE = {
|
||||
'agent begin': {
|
||||
check: ['mode'],
|
||||
|
@ -2282,7 +2309,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
this.activeGroups = new Map();
|
||||
this.gAgents = [];
|
||||
this.labelPattern = null;
|
||||
this.blockCount = 0;
|
||||
this.nextID = 0;
|
||||
this.nesting = [];
|
||||
this.markers = new Set();
|
||||
this.currentSection = null;
|
||||
|
@ -2311,7 +2338,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
this.endGroup = this.endGroup.bind(this);
|
||||
}
|
||||
|
||||
toGAgent({alias, name}) {
|
||||
toGAgent({name, alias, flags}) {
|
||||
if(alias) {
|
||||
if(this.agentAliases.has(name)) {
|
||||
throw new Error(
|
||||
|
@ -2330,7 +2357,9 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
this.agentAliases.set(alias, name);
|
||||
}
|
||||
return GAgent.make(this.agentAliases.get(name) || name);
|
||||
return GAgent.make(this.agentAliases.get(name) || name, {
|
||||
isVirtualSource: flags.includes('source'),
|
||||
});
|
||||
}
|
||||
|
||||
addStage(stage, isVisible = true) {
|
||||
|
@ -2366,7 +2395,12 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
|
||||
defineGAgents(gAgents) {
|
||||
array.mergeSets(this.currentNest.gAgents, gAgents, GAgent.equals);
|
||||
array.mergeSets(
|
||||
this.currentNest.gAgents,
|
||||
gAgents.filter((gAgent) =>
|
||||
!SPECIAL_AGENT_IDS.includes(gAgent.id)),
|
||||
GAgent.equals
|
||||
);
|
||||
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
|
||||
}
|
||||
|
||||
|
@ -2390,7 +2424,9 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
validateGAgents(gAgents, {
|
||||
allowGrouped = false,
|
||||
rejectGrouped = false,
|
||||
allowVirtual = false,
|
||||
} = {}) {
|
||||
/* jshint -W074 */ // agent validity checking requires several steps
|
||||
gAgents.forEach((gAgent) => {
|
||||
const state = this.getGAgentState(gAgent);
|
||||
if(state.covered) {
|
||||
|
@ -2404,6 +2440,9 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
if(state.blocked && (!allowGrouped || state.group === null)) {
|
||||
throw new Error('Duplicate agent name: ' + gAgent.id);
|
||||
}
|
||||
if(!allowVirtual && gAgent.isVirtualSource) {
|
||||
throw new Error('cannot use message source here');
|
||||
}
|
||||
if(gAgent.id.startsWith('__')) {
|
||||
throw new Error(gAgent.id + ' is a reserved name');
|
||||
}
|
||||
|
@ -2497,12 +2536,18 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
this.replaceGAgentState(rightGAgent, AgentState.LOCKED);
|
||||
this.nesting.push(this.currentNest);
|
||||
|
||||
return {gAgents, stages};
|
||||
return {stages};
|
||||
}
|
||||
|
||||
nextBlockName() {
|
||||
const name = '__BLOCK' + this.blockCount;
|
||||
++ this.blockCount;
|
||||
const name = '__BLOCK' + this.nextID;
|
||||
++ this.nextID;
|
||||
return name;
|
||||
}
|
||||
|
||||
nextVirtualAgentName() {
|
||||
const name = '__' + this.nextID;
|
||||
++ this.nextID;
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -2706,9 +2751,13 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
let ind1 = GAgent.indexOf(this.gAgents, gAgents1[0]);
|
||||
let ind2 = GAgent.indexOf(this.gAgents, gAgents2[0]);
|
||||
if(ind1 === -1) {
|
||||
ind1 = this.gAgents.length;
|
||||
// Virtual sources written as '* -> Ref' will spawn to the left,
|
||||
// not the right (as non-virtual agents would)
|
||||
ind1 = gAgents1[0].isVirtualSource ? -1 : this.gAgents.length;
|
||||
}
|
||||
if(ind2 === -1) {
|
||||
// Virtual and non-virtual agents written as 'Ref -> *' will
|
||||
// spawn to the right
|
||||
ind2 = this.gAgents.length;
|
||||
}
|
||||
if(ind1 === ind2) {
|
||||
|
@ -2755,21 +2804,79 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
return {beginGAgents, endGAgents, startGAgents, stopGAgents};
|
||||
}
|
||||
|
||||
makeVirtualAgent(anchorRight) {
|
||||
const virtualGAgent = GAgent.make(this.nextVirtualAgentName(), {
|
||||
anchorRight,
|
||||
isVirtualSource: true,
|
||||
});
|
||||
this.replaceGAgentState(virtualGAgent, AgentState.LOCKED);
|
||||
return virtualGAgent;
|
||||
}
|
||||
|
||||
addNearbyAgent(gAgentReference, gAgent, offset) {
|
||||
GAgent.addNearby(
|
||||
this.currentNest.gAgents,
|
||||
gAgentReference,
|
||||
gAgent,
|
||||
offset
|
||||
);
|
||||
GAgent.addNearby(
|
||||
this.gAgents,
|
||||
gAgentReference,
|
||||
gAgent,
|
||||
offset
|
||||
);
|
||||
}
|
||||
|
||||
expandVirtualSourceAgents(gAgents) {
|
||||
if(gAgents[0].isVirtualSource) {
|
||||
if(gAgents[1].isVirtualSource) {
|
||||
throw new Error('Cannot connect found messages');
|
||||
}
|
||||
if(SPECIAL_AGENT_IDS.includes(gAgents[1].id)) {
|
||||
throw new Error(
|
||||
'Cannot connect found messages to special agents'
|
||||
);
|
||||
}
|
||||
const virtualGAgent = this.makeVirtualAgent(true);
|
||||
this.addNearbyAgent(gAgents[1], virtualGAgent, 0);
|
||||
return [virtualGAgent, gAgents[1]];
|
||||
}
|
||||
if(gAgents[1].isVirtualSource) {
|
||||
if(SPECIAL_AGENT_IDS.includes(gAgents[0].id)) {
|
||||
throw new Error(
|
||||
'Cannot connect found messages to special agents'
|
||||
);
|
||||
}
|
||||
const virtualGAgent = this.makeVirtualAgent(false);
|
||||
this.addNearbyAgent(gAgents[0], virtualGAgent, 1);
|
||||
return [gAgents[0], virtualGAgent];
|
||||
}
|
||||
return gAgents;
|
||||
}
|
||||
|
||||
handleConnect({agents, label, options}) {
|
||||
const flags = this.filterConnectFlags(agents);
|
||||
|
||||
let gAgents = agents.map(this.toGAgent);
|
||||
this.validateGAgents(gAgents, {allowGrouped: true});
|
||||
this.validateGAgents(gAgents, {
|
||||
allowGrouped: true,
|
||||
allowVirtual: true,
|
||||
});
|
||||
|
||||
const allGAgents = array.flatMap(gAgents, this.expandGroupedGAgent);
|
||||
this.defineGAgents(allGAgents);
|
||||
this.defineGAgents(allGAgents
|
||||
.filter((gAgent) => !gAgent.isVirtualSource)
|
||||
);
|
||||
|
||||
gAgents = this.expandGroupedGAgentConnection(gAgents);
|
||||
gAgents = this.expandVirtualSourceAgents(gAgents);
|
||||
const agentIDs = gAgents.map((gAgent) => gAgent.id);
|
||||
|
||||
const implicitBeginGAgents = (agents
|
||||
.filter(PAgent.hasFlag('begin', false))
|
||||
.map(this.toGAgent)
|
||||
.filter((gAgent) => !gAgent.isVirtualSource)
|
||||
);
|
||||
this.addStage(this.setGAgentVis(implicitBeginGAgents, true, 'box'));
|
||||
|
||||
|
@ -2866,7 +2973,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
|
|||
this.agentAliases.clear();
|
||||
this.activeGroups.clear();
|
||||
this.gAgents.length = 0;
|
||||
this.blockCount = 0;
|
||||
this.nextID = 0;
|
||||
this.nesting.length = 0;
|
||||
this.labelPattern = [{token: 'label'}];
|
||||
}
|
||||
|
@ -3457,6 +3564,7 @@ define('sequence/components/BaseComponent',[],() => {
|
|||
theme,
|
||||
agentInfos,
|
||||
visibleAgentIDs,
|
||||
momentaryAgentIDs,
|
||||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
|
@ -3469,6 +3577,7 @@ define('sequence/components/BaseComponent',[],() => {
|
|||
theme,
|
||||
agentInfos,
|
||||
visibleAgentIDs,
|
||||
momentaryAgentIDs,
|
||||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
|
@ -4211,10 +4320,12 @@ define('sequence/components/AgentHighlight',['./BaseComponent'], (BaseComponent)
|
|||
});
|
||||
|
||||
define('sequence/components/Connect',[
|
||||
'core/ArrayUtilities',
|
||||
'./BaseComponent',
|
||||
'svg/SVGUtilities',
|
||||
'svg/SVGShapes',
|
||||
], (
|
||||
array,
|
||||
BaseComponent,
|
||||
svg,
|
||||
SVGShapes
|
||||
|
@ -4318,6 +4429,18 @@ define('sequence/components/Connect',[
|
|||
];
|
||||
|
||||
class Connect extends BaseComponent {
|
||||
separationPre({agentIDs}, env) {
|
||||
const r = env.theme.connect.source.radius;
|
||||
agentIDs.forEach((id) => {
|
||||
const agentInfo = env.agentInfos.get(id);
|
||||
if(!agentInfo.isVirtualSource) {
|
||||
return;
|
||||
}
|
||||
agentInfo.currentRad = r;
|
||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||
});
|
||||
}
|
||||
|
||||
separation({label, agentIDs, options}, env) {
|
||||
const config = env.theme.connect;
|
||||
|
||||
|
@ -4359,6 +4482,8 @@ define('sequence/components/Connect',[
|
|||
) * 2
|
||||
);
|
||||
}
|
||||
|
||||
array.mergeSets(env.momentaryAgentIDs, agentIDs);
|
||||
}
|
||||
|
||||
renderSelfConnect({label, agentIDs, options}, env) {
|
||||
|
@ -4436,14 +4561,59 @@ define('sequence/components/Connect',[
|
|||
);
|
||||
}
|
||||
|
||||
renderSimpleLine(x0, x1, options, env) {
|
||||
const dir = (x0 < x1) ? 1 : -1;
|
||||
|
||||
const config = env.theme.connect;
|
||||
const line = config.line[options.line];
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
const rendered = line.renderFlat(line.attrs, {
|
||||
x1: x0,
|
||||
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
x2: x1,
|
||||
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
y: env.primaryY,
|
||||
});
|
||||
env.shapeLayer.appendChild(rendered.shape);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
renderSimpleArrowheads(options, renderedLine, env, dir) {
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
lArrow.render(env.shapeLayer, env.theme, renderedLine.p1, dir);
|
||||
rArrow.render(env.shapeLayer, env.theme, renderedLine.p2, -dir);
|
||||
|
||||
return {lArrow, rArrow};
|
||||
}
|
||||
|
||||
renderVirtualSources(from, to, renderedLine, env) {
|
||||
const config = env.theme.connect.source;
|
||||
|
||||
if(from.isVirtualSource) {
|
||||
env.shapeLayer.appendChild(config.render({
|
||||
x: renderedLine.p1.x - config.radius,
|
||||
y: renderedLine.p1.y,
|
||||
radius: config.radius,
|
||||
}));
|
||||
}
|
||||
if(to.isVirtualSource) {
|
||||
env.shapeLayer.appendChild(config.render({
|
||||
x: renderedLine.p2.x + config.radius,
|
||||
y: renderedLine.p2.y,
|
||||
radius: config.radius,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
renderSimpleConnect({label, agentIDs, options}, env) {
|
||||
const config = env.theme.connect;
|
||||
const from = env.agentInfos.get(agentIDs[0]);
|
||||
const to = env.agentInfos.get(agentIDs[1]);
|
||||
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
const dir = (from.x < to.x) ? 1 : -1;
|
||||
|
||||
const height = (
|
||||
|
@ -4469,18 +4639,12 @@ define('sequence/components/Connect',[
|
|||
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
const line = config.line[options.line];
|
||||
const rendered = line.renderFlat(line.attrs, {
|
||||
x1: x0,
|
||||
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
x2: x1,
|
||||
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
y,
|
||||
});
|
||||
env.shapeLayer.appendChild(rendered.shape);
|
||||
|
||||
lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);
|
||||
rArrow.render(env.shapeLayer, env.theme, rendered.p2, -dir);
|
||||
const rendered = this.renderSimpleLine(x0, x1, options, env);
|
||||
const {
|
||||
lArrow,
|
||||
rArrow
|
||||
} = this.renderSimpleArrowheads(options, rendered, env, dir);
|
||||
this.renderVirtualSources(from, to, rendered, env);
|
||||
|
||||
const arrowSpread = Math.max(
|
||||
lArrow.height(env.theme),
|
||||
|
@ -4996,6 +5160,7 @@ define('sequence/Renderer',[
|
|||
theme: this.theme,
|
||||
agentInfos: this.agentInfos,
|
||||
visibleAgentIDs: this.visibleAgentIDs,
|
||||
momentaryAgentIDs: agentIDs,
|
||||
textSizer: this.sizer,
|
||||
addSpacing,
|
||||
addSeparation: this.addSeparation,
|
||||
|
@ -5200,6 +5365,7 @@ define('sequence/Renderer',[
|
|||
id: agent.id,
|
||||
formattedLabel: agent.formattedLabel,
|
||||
anchorRight: agent.anchorRight,
|
||||
isVirtualSource: agent.isVirtualSource,
|
||||
index,
|
||||
x: null,
|
||||
latestYStart: null,
|
||||
|
@ -5906,6 +6072,19 @@ define('sequence/themes/Basic',[
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 2,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
@ -6227,6 +6406,19 @@ define('sequence/themes/Monospace',[
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 2,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
@ -6547,6 +6739,19 @@ define('sequence/themes/Chunky',[
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 5,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 3,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 1,
|
||||
|
@ -7287,6 +7492,19 @@ define('sequence/themes/Sketch',[
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 1,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
@ -7722,7 +7940,7 @@ define('sequence/themes/Sketch',[
|
|||
);
|
||||
return {
|
||||
shape: svg.make('path', Object.assign({'d': ln.nodes}, attrs)),
|
||||
p1: {x: ln.p1.x - dx1, y: ln.p2.y},
|
||||
p1: {x: ln.p1.x - dx1, y: ln.p1.y},
|
||||
p2: {x: ln.p2.x - dx2, y: ln.p2.y},
|
||||
};
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -56,6 +56,10 @@
|
|||
title: 'Self-connection',
|
||||
code: '{Agent1} -> {Agent1}: {Message}',
|
||||
},
|
||||
{
|
||||
title: 'Found message',
|
||||
code: '* -> {Agent1}: {Message}',
|
||||
},
|
||||
{
|
||||
title: 'Request/response pair',
|
||||
code: (
|
||||
|
|
|
@ -42,8 +42,13 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
)};
|
||||
}
|
||||
|
||||
const colonTextToEnd = {
|
||||
type: 'operator',
|
||||
suggest: true,
|
||||
then: {'': textToEnd, '\n': hiddenEnd},
|
||||
};
|
||||
const agentListToText = agentListTo({
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
':': colonTextToEnd,
|
||||
});
|
||||
const agentList2ToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
|
@ -53,7 +58,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const singleAgentToText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
',': CM_ERROR,
|
||||
':': {type: 'operator', suggest: true, then: {'': textToEnd}},
|
||||
':': colonTextToEnd,
|
||||
}};
|
||||
const agentToOptText = {type: 'variable', suggest: 'Agent', then: {
|
||||
'': 0,
|
||||
|
@ -97,7 +102,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
};
|
||||
}
|
||||
|
||||
function makeOpBlock(exit) {
|
||||
function makeOpBlock(exit, sourceExit) {
|
||||
const op = {type: 'operator', suggest: true, then: {
|
||||
'+': CM_ERROR,
|
||||
'-': CM_ERROR,
|
||||
|
@ -126,13 +131,13 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}},
|
||||
'': exit,
|
||||
}},
|
||||
'*': {type: 'operator', suggest: true, then: {
|
||||
'*': {type: 'operator', suggest: true, then: Object.assign({
|
||||
'+': op,
|
||||
'-': op,
|
||||
'*': CM_ERROR,
|
||||
'!': CM_ERROR,
|
||||
'': exit,
|
||||
}},
|
||||
}, sourceExit)},
|
||||
'!': op,
|
||||
'': exit,
|
||||
};
|
||||
|
@ -142,7 +147,10 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const connect = {
|
||||
type: 'keyword',
|
||||
suggest: true,
|
||||
then: makeOpBlock(agentToOptText),
|
||||
then: makeOpBlock(agentToOptText, {
|
||||
':': colonTextToEnd,
|
||||
'\n': hiddenEnd,
|
||||
}),
|
||||
};
|
||||
|
||||
const then = {'': 0};
|
||||
|
@ -153,7 +161,10 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
override: 'Label',
|
||||
then: {},
|
||||
};
|
||||
return makeOpBlock({type: 'variable', suggest: 'Agent', then});
|
||||
return makeOpBlock(
|
||||
{type: 'variable', suggest: 'Agent', then},
|
||||
then
|
||||
);
|
||||
}
|
||||
|
||||
const BASE_THEN = {
|
||||
|
|
|
@ -36,8 +36,8 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
equals: (a, b) => {
|
||||
return a.id === b.id;
|
||||
},
|
||||
make: (id, {anchorRight = false} = {}) => {
|
||||
return {id, anchorRight};
|
||||
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => {
|
||||
return {id, anchorRight, isVirtualSource};
|
||||
},
|
||||
indexOf: (list, gAgent) => {
|
||||
return array.indexOf(list, gAgent, GAgent.equals);
|
||||
|
@ -45,6 +45,14 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
hasIntersection: (a, b) => {
|
||||
return array.hasIntersection(a, b, GAgent.equals);
|
||||
},
|
||||
addNearby: (target, reference, item, offset) => {
|
||||
const p = array.indexOf(target, reference, GAgent.equals);
|
||||
if(p === -1) {
|
||||
target.push(item);
|
||||
} else {
|
||||
target.splice(p + offset, 0, item);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const NOTE_DEFAULT_G_AGENTS = {
|
||||
|
@ -53,6 +61,8 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'note right': [GAgent.make(']')],
|
||||
};
|
||||
|
||||
const SPECIAL_AGENT_IDS = ['[', ']'];
|
||||
|
||||
const MERGABLE = {
|
||||
'agent begin': {
|
||||
check: ['mode'],
|
||||
|
@ -227,7 +237,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.activeGroups = new Map();
|
||||
this.gAgents = [];
|
||||
this.labelPattern = null;
|
||||
this.blockCount = 0;
|
||||
this.nextID = 0;
|
||||
this.nesting = [];
|
||||
this.markers = new Set();
|
||||
this.currentSection = null;
|
||||
|
@ -256,7 +266,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.endGroup = this.endGroup.bind(this);
|
||||
}
|
||||
|
||||
toGAgent({alias, name}) {
|
||||
toGAgent({name, alias, flags}) {
|
||||
if(alias) {
|
||||
if(this.agentAliases.has(name)) {
|
||||
throw new Error(
|
||||
|
@ -275,7 +285,9 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
this.agentAliases.set(alias, name);
|
||||
}
|
||||
return GAgent.make(this.agentAliases.get(name) || name);
|
||||
return GAgent.make(this.agentAliases.get(name) || name, {
|
||||
isVirtualSource: flags.includes('source'),
|
||||
});
|
||||
}
|
||||
|
||||
addStage(stage, isVisible = true) {
|
||||
|
@ -311,7 +323,12 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
}
|
||||
|
||||
defineGAgents(gAgents) {
|
||||
array.mergeSets(this.currentNest.gAgents, gAgents, GAgent.equals);
|
||||
array.mergeSets(
|
||||
this.currentNest.gAgents,
|
||||
gAgents.filter((gAgent) =>
|
||||
!SPECIAL_AGENT_IDS.includes(gAgent.id)),
|
||||
GAgent.equals
|
||||
);
|
||||
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
|
||||
}
|
||||
|
||||
|
@ -335,7 +352,9 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
validateGAgents(gAgents, {
|
||||
allowGrouped = false,
|
||||
rejectGrouped = false,
|
||||
allowVirtual = false,
|
||||
} = {}) {
|
||||
/* jshint -W074 */ // agent validity checking requires several steps
|
||||
gAgents.forEach((gAgent) => {
|
||||
const state = this.getGAgentState(gAgent);
|
||||
if(state.covered) {
|
||||
|
@ -349,6 +368,9 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
if(state.blocked && (!allowGrouped || state.group === null)) {
|
||||
throw new Error('Duplicate agent name: ' + gAgent.id);
|
||||
}
|
||||
if(!allowVirtual && gAgent.isVirtualSource) {
|
||||
throw new Error('cannot use message source here');
|
||||
}
|
||||
if(gAgent.id.startsWith('__')) {
|
||||
throw new Error(gAgent.id + ' is a reserved name');
|
||||
}
|
||||
|
@ -442,12 +464,18 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.replaceGAgentState(rightGAgent, AgentState.LOCKED);
|
||||
this.nesting.push(this.currentNest);
|
||||
|
||||
return {gAgents, stages};
|
||||
return {stages};
|
||||
}
|
||||
|
||||
nextBlockName() {
|
||||
const name = '__BLOCK' + this.blockCount;
|
||||
++ this.blockCount;
|
||||
const name = '__BLOCK' + this.nextID;
|
||||
++ this.nextID;
|
||||
return name;
|
||||
}
|
||||
|
||||
nextVirtualAgentName() {
|
||||
const name = '__' + this.nextID;
|
||||
++ this.nextID;
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -651,9 +679,13 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
let ind1 = GAgent.indexOf(this.gAgents, gAgents1[0]);
|
||||
let ind2 = GAgent.indexOf(this.gAgents, gAgents2[0]);
|
||||
if(ind1 === -1) {
|
||||
ind1 = this.gAgents.length;
|
||||
// Virtual sources written as '* -> Ref' will spawn to the left,
|
||||
// not the right (as non-virtual agents would)
|
||||
ind1 = gAgents1[0].isVirtualSource ? -1 : this.gAgents.length;
|
||||
}
|
||||
if(ind2 === -1) {
|
||||
// Virtual and non-virtual agents written as 'Ref -> *' will
|
||||
// spawn to the right
|
||||
ind2 = this.gAgents.length;
|
||||
}
|
||||
if(ind1 === ind2) {
|
||||
|
@ -700,21 +732,79 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
return {beginGAgents, endGAgents, startGAgents, stopGAgents};
|
||||
}
|
||||
|
||||
makeVirtualAgent(anchorRight) {
|
||||
const virtualGAgent = GAgent.make(this.nextVirtualAgentName(), {
|
||||
anchorRight,
|
||||
isVirtualSource: true,
|
||||
});
|
||||
this.replaceGAgentState(virtualGAgent, AgentState.LOCKED);
|
||||
return virtualGAgent;
|
||||
}
|
||||
|
||||
addNearbyAgent(gAgentReference, gAgent, offset) {
|
||||
GAgent.addNearby(
|
||||
this.currentNest.gAgents,
|
||||
gAgentReference,
|
||||
gAgent,
|
||||
offset
|
||||
);
|
||||
GAgent.addNearby(
|
||||
this.gAgents,
|
||||
gAgentReference,
|
||||
gAgent,
|
||||
offset
|
||||
);
|
||||
}
|
||||
|
||||
expandVirtualSourceAgents(gAgents) {
|
||||
if(gAgents[0].isVirtualSource) {
|
||||
if(gAgents[1].isVirtualSource) {
|
||||
throw new Error('Cannot connect found messages');
|
||||
}
|
||||
if(SPECIAL_AGENT_IDS.includes(gAgents[1].id)) {
|
||||
throw new Error(
|
||||
'Cannot connect found messages to special agents'
|
||||
);
|
||||
}
|
||||
const virtualGAgent = this.makeVirtualAgent(true);
|
||||
this.addNearbyAgent(gAgents[1], virtualGAgent, 0);
|
||||
return [virtualGAgent, gAgents[1]];
|
||||
}
|
||||
if(gAgents[1].isVirtualSource) {
|
||||
if(SPECIAL_AGENT_IDS.includes(gAgents[0].id)) {
|
||||
throw new Error(
|
||||
'Cannot connect found messages to special agents'
|
||||
);
|
||||
}
|
||||
const virtualGAgent = this.makeVirtualAgent(false);
|
||||
this.addNearbyAgent(gAgents[0], virtualGAgent, 1);
|
||||
return [gAgents[0], virtualGAgent];
|
||||
}
|
||||
return gAgents;
|
||||
}
|
||||
|
||||
handleConnect({agents, label, options}) {
|
||||
const flags = this.filterConnectFlags(agents);
|
||||
|
||||
let gAgents = agents.map(this.toGAgent);
|
||||
this.validateGAgents(gAgents, {allowGrouped: true});
|
||||
this.validateGAgents(gAgents, {
|
||||
allowGrouped: true,
|
||||
allowVirtual: true,
|
||||
});
|
||||
|
||||
const allGAgents = array.flatMap(gAgents, this.expandGroupedGAgent);
|
||||
this.defineGAgents(allGAgents);
|
||||
this.defineGAgents(allGAgents
|
||||
.filter((gAgent) => !gAgent.isVirtualSource)
|
||||
);
|
||||
|
||||
gAgents = this.expandGroupedGAgentConnection(gAgents);
|
||||
gAgents = this.expandVirtualSourceAgents(gAgents);
|
||||
const agentIDs = gAgents.map((gAgent) => gAgent.id);
|
||||
|
||||
const implicitBeginGAgents = (agents
|
||||
.filter(PAgent.hasFlag('begin', false))
|
||||
.map(this.toGAgent)
|
||||
.filter((gAgent) => !gAgent.isVirtualSource)
|
||||
);
|
||||
this.addStage(this.setGAgentVis(implicitBeginGAgents, true, 'box'));
|
||||
|
||||
|
@ -811,7 +901,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.agentAliases.clear();
|
||||
this.activeGroups.clear();
|
||||
this.gAgents.length = 0;
|
||||
this.blockCount = 0;
|
||||
this.nextID = 0;
|
||||
this.nesting.length = 0;
|
||||
this.labelPattern = [{token: 'label'}];
|
||||
}
|
||||
|
|
|
@ -110,6 +110,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
};
|
||||
|
||||
const GENERATED = {
|
||||
agent: (id, {
|
||||
formattedLabel = any(),
|
||||
anchorRight = any(),
|
||||
isVirtualSource = any(),
|
||||
} = {}) => {
|
||||
return {id, formattedLabel, anchorRight, isVirtualSource};
|
||||
},
|
||||
|
||||
beginAgents: (agentIDs, {
|
||||
mode = any(),
|
||||
ln = any(),
|
||||
|
@ -264,8 +272,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
it('includes implicit hidden left/right agents', () => {
|
||||
const sequence = invoke([]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('[', {anchorRight: true}),
|
||||
GENERATED.agent(']', {anchorRight: false}),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -296,13 +304,13 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
PARSED.beginAgents(['E']),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'E', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent('E'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -311,10 +319,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
PARSED.connect(['A', 'B']),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: any()},
|
||||
{id: 'A', formattedLabel: 'A!', anchorRight: any()},
|
||||
{id: 'B', formattedLabel: 'B!', anchorRight: any()},
|
||||
{id: ']', formattedLabel: any(), anchorRight: any()},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A', {formattedLabel: 'A!'}),
|
||||
GENERATED.agent('B', {formattedLabel: 'B!'}),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -323,9 +331,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
PARSED.connect([']', 'B']),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -335,10 +343,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
PARSED.connect(['A', 'B']),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -348,10 +356,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
PARSED.connect(['A', 'B']),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'Baz', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('Baz'),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -419,6 +427,119 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('converts source agents into virtual agents', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.connect([
|
||||
'A',
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__0', {
|
||||
anchorRight: false,
|
||||
isVirtualSource: true,
|
||||
}),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A']),
|
||||
GENERATED.connect(['A', '__0']),
|
||||
GENERATED.endAgents(['A']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts sources into distinct virtual agents', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.connect([
|
||||
'A',
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
PARSED.connect([
|
||||
'A',
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__1'),
|
||||
GENERATED.agent('__0'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
expect(sequence.stages).toEqual([
|
||||
GENERATED.beginAgents(['A']),
|
||||
GENERATED.connect(['A', '__0']),
|
||||
GENERATED.connect(['A', '__1']),
|
||||
GENERATED.endAgents(['A']),
|
||||
]);
|
||||
});
|
||||
|
||||
it('places source agents near the connected agent', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.beginAgents(['A', 'B', 'C']),
|
||||
PARSED.connect([
|
||||
'B',
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__0', {
|
||||
anchorRight: false,
|
||||
isVirtualSource: true,
|
||||
}),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('places source agents left when connections are reversed', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.beginAgents(['A', 'B', 'C']),
|
||||
PARSED.connect([
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
'B',
|
||||
]),
|
||||
]);
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__0', {
|
||||
anchorRight: true,
|
||||
isVirtualSource: true,
|
||||
}),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects connections between virtual agents', () => {
|
||||
expect(() => invoke([
|
||||
PARSED.connect([
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
])).toThrow(new Error(
|
||||
'Cannot connect found messages at line 1'
|
||||
));
|
||||
});
|
||||
|
||||
it('rejects connections between virtual agents and sides', () => {
|
||||
expect(() => invoke([
|
||||
PARSED.connect([
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
']',
|
||||
]),
|
||||
])).toThrow(new Error(
|
||||
'Cannot connect found messages to special agents at line 1'
|
||||
));
|
||||
});
|
||||
|
||||
it('uses label patterns for connections', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.labelPattern(['foo ', {token: 'label'}, ' bar']),
|
||||
|
@ -819,12 +940,12 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('__BLOCK0[', {anchorRight: true}),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__BLOCK0]', {anchorRight: false}),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -841,20 +962,40 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'E', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'F', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1]', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'G', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'H', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent('__BLOCK1['),
|
||||
GENERATED.agent('E'),
|
||||
GENERATED.agent('F'),
|
||||
GENERATED.agent('__BLOCK1]'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('G'),
|
||||
GENERATED.agent('H'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignores side agents when calculating block bounds', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.beginAgents(['A', 'B', 'C']),
|
||||
PARSED.blockBegin('if', 'abc'),
|
||||
PARSED.connect(['[', 'B']),
|
||||
PARSED.connect(['B', ']']),
|
||||
PARSED.blockEnd(),
|
||||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -916,15 +1057,15 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK1[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1]', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('__BLOCK1['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('__BLOCK1]'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
|
||||
const bounds0 = {
|
||||
|
@ -955,14 +1096,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK1[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1]', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('__BLOCK1['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__BLOCK1]'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
|
||||
const bounds0 = {
|
||||
|
@ -1051,12 +1192,12 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
};
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
|
||||
expect(sequence.stages).toEqual([
|
||||
|
@ -1116,14 +1257,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -1135,15 +1276,15 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'E', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('E'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -1342,16 +1483,16 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequenceR.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1]', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__BLOCK1['),
|
||||
GENERATED.agent('__BLOCK0[', {anchorRight: true}),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('__BLOCK0]', {anchorRight: false}),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent('__BLOCK1]'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
|
||||
const sequenceL = invoke([
|
||||
|
@ -1364,16 +1505,51 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
|
||||
expect(sequenceL.agents).toEqual([
|
||||
{id: '[', formattedLabel: any(), anchorRight: true},
|
||||
{id: '__BLOCK1[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'A', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0[', formattedLabel: any(), anchorRight: true},
|
||||
{id: 'B', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'C', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK0]', formattedLabel: any(), anchorRight: false},
|
||||
{id: '__BLOCK1]', formattedLabel: any(), anchorRight: false},
|
||||
{id: 'D', formattedLabel: any(), anchorRight: false},
|
||||
{id: ']', formattedLabel: any(), anchorRight: false},
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('__BLOCK1['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('__BLOCK1]'),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows connections between sources and references', () => {
|
||||
const sequence = invoke([
|
||||
PARSED.beginAgents(['A', 'B', 'C', 'D']),
|
||||
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
|
||||
PARSED.connect([
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
'Bar',
|
||||
]),
|
||||
PARSED.connect([
|
||||
'Bar',
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
]),
|
||||
PARSED.endAgents(['Bar']),
|
||||
]);
|
||||
|
||||
expect(sequence.agents).toEqual([
|
||||
GENERATED.agent('['),
|
||||
GENERATED.agent('A'),
|
||||
GENERATED.agent('__1', {
|
||||
anchorRight: true,
|
||||
isVirtualSource: true,
|
||||
}),
|
||||
GENERATED.agent('__BLOCK0['),
|
||||
GENERATED.agent('B'),
|
||||
GENERATED.agent('C'),
|
||||
GENERATED.agent('__BLOCK0]'),
|
||||
GENERATED.agent('__2', {
|
||||
anchorRight: false,
|
||||
isVirtualSource: true,
|
||||
}),
|
||||
GENERATED.agent('D'),
|
||||
GENERATED.agent(']'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -67,10 +67,10 @@ define([
|
|||
})());
|
||||
|
||||
const CONNECT_AGENT_FLAGS = {
|
||||
'*': 'begin',
|
||||
'+': 'start',
|
||||
'-': 'stop',
|
||||
'!': 'end',
|
||||
'*': {flag: 'begin', allowBlankName: true, blankNameFlag: 'source'},
|
||||
'+': {flag: 'start'},
|
||||
'-': {flag: 'stop'},
|
||||
'!': {flag: 'end'},
|
||||
};
|
||||
|
||||
const TERMINATOR_TYPES = [
|
||||
|
@ -182,7 +182,7 @@ define([
|
|||
return -1;
|
||||
}
|
||||
|
||||
function readAgentAlias(line, start, end, enableAlias) {
|
||||
function readAgentAlias(line, start, end, {enableAlias, allowBlankName}) {
|
||||
let aliasSep = -1;
|
||||
if(enableAlias) {
|
||||
aliasSep = findToken(line, 'as', start);
|
||||
|
@ -190,7 +190,7 @@ define([
|
|||
if(aliasSep === -1 || aliasSep >= end) {
|
||||
aliasSep = end;
|
||||
}
|
||||
if(start >= aliasSep) {
|
||||
if(start >= aliasSep && !allowBlankName) {
|
||||
throw makeError('Missing agent name', errToken(line, start));
|
||||
}
|
||||
return {
|
||||
|
@ -204,25 +204,31 @@ define([
|
|||
aliases = false,
|
||||
} = {}) {
|
||||
const flags = [];
|
||||
const blankNameFlags = [];
|
||||
let p = start;
|
||||
let allowBlankName = false;
|
||||
for(; p < end; ++ p) {
|
||||
const token = line[p];
|
||||
const rawFlag = tokenKeyword(token);
|
||||
const flag = flagTypes[rawFlag];
|
||||
if(flag) {
|
||||
if(flags.includes(flag)) {
|
||||
throw makeError('Duplicate agent flag: ' + rawFlag, token);
|
||||
}
|
||||
flags.push(flag);
|
||||
} else {
|
||||
if(!flag) {
|
||||
break;
|
||||
}
|
||||
if(flags.includes(flag.flag)) {
|
||||
throw makeError('Duplicate agent flag: ' + rawFlag, token);
|
||||
}
|
||||
allowBlankName = allowBlankName || Boolean(flag.allowBlankName);
|
||||
flags.push(flag.flag);
|
||||
blankNameFlags.push(flag.blankNameFlag);
|
||||
}
|
||||
const {name, alias} = readAgentAlias(line, p, end, aliases);
|
||||
const {name, alias} = readAgentAlias(line, p, end, {
|
||||
enableAlias: aliases,
|
||||
allowBlankName,
|
||||
});
|
||||
return {
|
||||
name,
|
||||
alias,
|
||||
flags,
|
||||
flags: name ? flags : blankNameFlags,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -185,6 +185,38 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
));
|
||||
});
|
||||
|
||||
it('parses source agents', () => {
|
||||
const parsed = parser.parse('A -> *');
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'connect',
|
||||
ln: jasmine.anything(),
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
],
|
||||
label: jasmine.anything(),
|
||||
options: jasmine.anything(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('parses source agents with labels', () => {
|
||||
const parsed = parser.parse('A -> *: foo');
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'connect',
|
||||
ln: jasmine.anything(),
|
||||
agents: [
|
||||
{name: 'A', alias: '', flags: []},
|
||||
{name: '', alias: '', flags: ['source']},
|
||||
],
|
||||
label: 'foo',
|
||||
options: jasmine.anything(),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts multiple entries', () => {
|
||||
const parsed = parser.parse('A -> B\nB -> A');
|
||||
expect(parsed.stages).toEqual([
|
||||
|
|
|
@ -188,6 +188,7 @@ define([
|
|||
theme: this.theme,
|
||||
agentInfos: this.agentInfos,
|
||||
visibleAgentIDs: this.visibleAgentIDs,
|
||||
momentaryAgentIDs: agentIDs,
|
||||
textSizer: this.sizer,
|
||||
addSpacing,
|
||||
addSeparation: this.addSeparation,
|
||||
|
@ -392,6 +393,7 @@ define([
|
|||
id: agent.id,
|
||||
formattedLabel: agent.formattedLabel,
|
||||
anchorRight: agent.anchorRight,
|
||||
isVirtualSource: agent.isVirtualSource,
|
||||
index,
|
||||
x: null,
|
||||
latestYStart: null,
|
||||
|
|
|
@ -13,6 +13,7 @@ define(() => {
|
|||
theme,
|
||||
agentInfos,
|
||||
visibleAgentIDs,
|
||||
momentaryAgentIDs,
|
||||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
|
@ -25,6 +26,7 @@ define(() => {
|
|||
theme,
|
||||
agentInfos,
|
||||
visibleAgentIDs,
|
||||
momentaryAgentIDs,
|
||||
textSizer,
|
||||
addSpacing,
|
||||
addSeparation,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
define([
|
||||
'core/ArrayUtilities',
|
||||
'./BaseComponent',
|
||||
'svg/SVGUtilities',
|
||||
'svg/SVGShapes',
|
||||
], (
|
||||
array,
|
||||
BaseComponent,
|
||||
svg,
|
||||
SVGShapes
|
||||
|
@ -106,6 +108,18 @@ define([
|
|||
];
|
||||
|
||||
class Connect extends BaseComponent {
|
||||
separationPre({agentIDs}, env) {
|
||||
const r = env.theme.connect.source.radius;
|
||||
agentIDs.forEach((id) => {
|
||||
const agentInfo = env.agentInfos.get(id);
|
||||
if(!agentInfo.isVirtualSource) {
|
||||
return;
|
||||
}
|
||||
agentInfo.currentRad = r;
|
||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||
});
|
||||
}
|
||||
|
||||
separation({label, agentIDs, options}, env) {
|
||||
const config = env.theme.connect;
|
||||
|
||||
|
@ -147,6 +161,8 @@ define([
|
|||
) * 2
|
||||
);
|
||||
}
|
||||
|
||||
array.mergeSets(env.momentaryAgentIDs, agentIDs);
|
||||
}
|
||||
|
||||
renderSelfConnect({label, agentIDs, options}, env) {
|
||||
|
@ -224,14 +240,59 @@ define([
|
|||
);
|
||||
}
|
||||
|
||||
renderSimpleLine(x0, x1, options, env) {
|
||||
const dir = (x0 < x1) ? 1 : -1;
|
||||
|
||||
const config = env.theme.connect;
|
||||
const line = config.line[options.line];
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
const rendered = line.renderFlat(line.attrs, {
|
||||
x1: x0,
|
||||
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
x2: x1,
|
||||
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
y: env.primaryY,
|
||||
});
|
||||
env.shapeLayer.appendChild(rendered.shape);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
renderSimpleArrowheads(options, renderedLine, env, dir) {
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
lArrow.render(env.shapeLayer, env.theme, renderedLine.p1, dir);
|
||||
rArrow.render(env.shapeLayer, env.theme, renderedLine.p2, -dir);
|
||||
|
||||
return {lArrow, rArrow};
|
||||
}
|
||||
|
||||
renderVirtualSources(from, to, renderedLine, env) {
|
||||
const config = env.theme.connect.source;
|
||||
|
||||
if(from.isVirtualSource) {
|
||||
env.shapeLayer.appendChild(config.render({
|
||||
x: renderedLine.p1.x - config.radius,
|
||||
y: renderedLine.p1.y,
|
||||
radius: config.radius,
|
||||
}));
|
||||
}
|
||||
if(to.isVirtualSource) {
|
||||
env.shapeLayer.appendChild(config.render({
|
||||
x: renderedLine.p2.x + config.radius,
|
||||
y: renderedLine.p2.y,
|
||||
radius: config.radius,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
renderSimpleConnect({label, agentIDs, options}, env) {
|
||||
const config = env.theme.connect;
|
||||
const from = env.agentInfos.get(agentIDs[0]);
|
||||
const to = env.agentInfos.get(agentIDs[1]);
|
||||
|
||||
const lArrow = ARROWHEADS[options.left];
|
||||
const rArrow = ARROWHEADS[options.right];
|
||||
|
||||
const dir = (from.x < to.x) ? 1 : -1;
|
||||
|
||||
const height = (
|
||||
|
@ -257,18 +318,12 @@ define([
|
|||
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||
});
|
||||
|
||||
const line = config.line[options.line];
|
||||
const rendered = line.renderFlat(line.attrs, {
|
||||
x1: x0,
|
||||
dx1: lArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
x2: x1,
|
||||
dx2: -rArrow.lineGap(env.theme, line.attrs) * dir,
|
||||
y,
|
||||
});
|
||||
env.shapeLayer.appendChild(rendered.shape);
|
||||
|
||||
lArrow.render(env.shapeLayer, env.theme, rendered.p1, dir);
|
||||
rArrow.render(env.shapeLayer, env.theme, rendered.p2, -dir);
|
||||
const rendered = this.renderSimpleLine(x0, x1, options, env);
|
||||
const {
|
||||
lArrow,
|
||||
rArrow
|
||||
} = this.renderSimpleArrowheads(options, rendered, env, dir);
|
||||
this.renderVirtualSources(from, to, rendered, env);
|
||||
|
||||
const arrowSpread = Math.max(
|
||||
lArrow.height(env.theme),
|
||||
|
|
|
@ -150,6 +150,19 @@ define([
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 2,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
|
|
@ -160,6 +160,19 @@ define([
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 5,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 3,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 1,
|
||||
|
|
|
@ -157,6 +157,19 @@ define([
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 2,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
|
|
@ -139,6 +139,19 @@ define([
|
|||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
source: {
|
||||
radius: 1,
|
||||
render: ({x, y, radius}) => {
|
||||
return svg.make('circle', {
|
||||
'cx': x,
|
||||
'cy': y,
|
||||
'r': radius,
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
|
@ -574,7 +587,7 @@ define([
|
|||
);
|
||||
return {
|
||||
shape: svg.make('path', Object.assign({'d': ln.nodes}, attrs)),
|
||||
p1: {x: ln.p1.x - dx1, y: ln.p2.y},
|
||||
p1: {x: ln.p1.x - dx1, y: ln.p1.y},
|
||||
p2: {x: ln.p2.x - dx2, y: ln.p2.y},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue