Convert self-connections into asynchronous connections for better rendering [#43]

This commit is contained in:
David Evans 2018-01-27 16:55:48 +00:00
parent bbb9350e15
commit 5283c511e9
16 changed files with 223 additions and 112 deletions

View File

@ -3112,7 +3112,36 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
]; ];
} }
_isSelfConnect(agents) {
const gAgents = agents.map(this.toGAgent);
const expandedGAgents = this.expandGroupedGAgentConnection(gAgents);
if(expandedGAgents[0].id !== expandedGAgents[1].id) {
return false;
}
if(expandedGAgents.some((gAgent) => gAgent.isVirtualSource)) {
return false;
}
return true;
}
handleConnect({agents, label, options}) { handleConnect({agents, label, options}) {
if(this._isSelfConnect(agents)) {
const tag = {};
this.handleConnectDelayBegin({
agent: agents[0],
tag,
options,
ln: 0,
});
this.handleConnectDelayEnd({
agent: agents[1],
tag,
label,
options,
});
return;
}
let {flags, gAgents} = this._handlePartialConnect(agents); let {flags, gAgents} = this._handlePartialConnect(agents);
gAgents = this.expandGroupedGAgentConnection(gAgents); gAgents = this.expandGroupedGAgentConnection(gAgents);
@ -3922,13 +3951,18 @@ define('sequence/components/BaseComponent',[],() => {
} }
BaseComponent.cleanRenderPreResult = ({ BaseComponent.cleanRenderPreResult = ({
topShift = 0,
agentIDs = [], agentIDs = [],
topShift = 0,
y = null,
asynchronousY = null, asynchronousY = null,
} = {}, currentY = null) => { } = {}, currentY = null) => {
if(y !== null && currentY !== null) {
topShift = Math.max(topShift, y - currentY);
}
return { return {
topShift,
agentIDs, agentIDs,
topShift,
y,
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY, asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
}; };
}; };
@ -4025,7 +4059,7 @@ define('sequence/components/Block',[
'width': agentInfoR.x - agentInfoL.x, 'width': agentInfoR.x - agentInfoL.x,
'height': labelHeight, 'height': labelHeight,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
if(!first) { if(!first) {
@ -4157,8 +4191,9 @@ define('sequence/components/Parallel',[
function mergeResults(a, b) { function mergeResults(a, b) {
array.mergeSets(a.agentIDs, b.agentIDs); array.mergeSets(a.agentIDs, b.agentIDs);
return { return {
topShift: Math.max(a.topShift, b.topShift),
agentIDs: a.agentIDs, agentIDs: a.agentIDs,
topShift: Math.max(a.topShift, b.topShift),
y: nullableMax(a.y, b.y),
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY), asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
}; };
} }
@ -4309,7 +4344,7 @@ define('sequence/components/AgentCap',[
'width': width, 'width': width,
'height': height, 'height': height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return { return {
@ -4347,7 +4382,7 @@ define('sequence/components/AgentCap',[
'width': d * 2, 'width': d * 2,
'height': d * 2, 'height': d * 2,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -4402,7 +4437,7 @@ define('sequence/components/AgentCap',[
'width': width, 'width': width,
'height': height, 'height': height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -4463,7 +4498,7 @@ define('sequence/components/AgentCap',[
'width': config.width, 'width': config.width,
'height': config.height, 'height': config.height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -4498,7 +4533,7 @@ define('sequence/components/AgentCap',[
'width': w, 'width': w,
'height': config.height, 'height': config.height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -4872,16 +4907,12 @@ define('sequence/components/Connect',[
to.x + to.currentMaxRad + rArrow.width(env.theme), to.x + to.currentMaxRad + rArrow.width(env.theme),
xL + labelW xL + labelW
); );
const y2 = Math.max(
yBegin + config.loopbackRadius * 2,
env.primaryY
);
this.renderRevArrowLine({ this.renderRevArrowLine({
x1: from.x + from.currentMaxRad, x1: from.x + from.currentMaxRad,
y1: yBegin, y1: yBegin,
x2: to.x + to.currentMaxRad, x2: to.x + to.currentMaxRad,
y2, y2: env.primaryY,
xR, xR,
}, options, env); }, options, env);
@ -4892,12 +4923,16 @@ define('sequence/components/Connect',[
'x': from.x, 'x': from.x,
'y': yBegin - raise, 'y': yBegin - raise,
'width': xR + config.loopbackRadius - from.x, 'width': xR + config.loopbackRadius - from.x,
'height': raise + y2 - yBegin + arrowDip, 'height': raise + env.primaryY - yBegin + arrowDip,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return y2 + Math.max(arrowDip, 0) + env.theme.actionMargin; return (
env.primaryY +
Math.max(arrowDip, 0) +
env.theme.actionMargin
);
} }
renderArrowLine({x1, y1, x2, y2}, options, env) { renderArrowLine({x1, y1, x2, y2}, options, env) {
@ -5031,7 +5066,7 @@ define('sequence/components/Connect',[
'Z' 'Z'
), ),
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
this.renderSimpleLabel(label, { this.renderSimpleLabel(label, {
@ -5122,11 +5157,21 @@ define('sequence/components/Connect',[
separation() {} separation() {}
renderPre({tag}, env) { renderPre({tag}, env) {
const config = env.theme.connect;
const dc = env.state.delayedConnections; const dc = env.state.delayedConnections;
const beginStage = dc.get(tag).stage; const begin = dc.get(tag);
return Object.assign(super.renderPre(beginStage, env), { const beginStage = begin.stage;
agentIDs: [beginStage.agentIDs[1]], const agentIDs = [beginStage.agentIDs[1]];
});
if(beginStage.agentIDs[0] === beginStage.agentIDs[1]) {
return {
agentIDs,
y: begin.y + config.loopbackRadius * 2,
};
}
return Object.assign(super.renderPre(beginStage, env), {agentIDs});
} }
render({tag}, env) { render({tag}, env) {
@ -5241,7 +5286,7 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base
'width': x1 - x0, 'width': x1 - x0,
'height': fullH, 'height': fullH,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return ( return (
@ -5500,7 +5545,7 @@ define('sequence/components/Divider',[
'width': right.x - left.x + config.extend * 2, 'width': right.x - left.x + config.extend * 2,
'height': fullHeight, 'height': fullHeight,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return env.primaryY + fullHeight + env.theme.actionMargin; return env.primaryY + fullHeight + env.theme.actionMargin;
@ -5815,7 +5860,7 @@ define('sequence/Renderer',[
}; };
const component = this.components.get(stage.type); const component = this.components.get(stage.type);
const result = component.renderPre(stage, envPre); const result = component.renderPre(stage, envPre);
const {topShift, agentIDs, asynchronousY} = const {agentIDs, topShift, asynchronousY} =
BaseComponent.cleanRenderPreResult(result, this.currentY); BaseComponent.cleanRenderPreResult(result, this.currentY);
const topY = this.checkAgentRange(agentIDs, asynchronousY); const topY = this.checkAgentRange(agentIDs, asynchronousY);

File diff suppressed because one or more lines are too long

View File

@ -593,6 +593,19 @@ Marks elements generated by the specified line with a "focus" CSS class, which
can be used to style them. Only one line can be highlighted at a time. Calling can be used to style them. Only one line can be highlighted at a time. Calling
with no parameter (or <code>null</code>) will remove the highlighting. with no parameter (or <code>null</code>) will remove the highlighting.
</p> </p>
<p>
The outline effect seen in the editor can be achieved by targetting
<code>.focus .outline</code>. All elements contain an element with the class
"outline", which defines a simple shape surrounding the entire element. For
example:
</p>
<pre data-lang="css">
.region.focus .outline {
stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5);
}
</pre>
<h3 id="API_addEventListener">.addEventListener</h3> <h3 id="API_addEventListener">.addEventListener</h3>

View File

@ -844,7 +844,36 @@ define(['core/ArrayUtilities'], (array) => {
]; ];
} }
_isSelfConnect(agents) {
const gAgents = agents.map(this.toGAgent);
const expandedGAgents = this.expandGroupedGAgentConnection(gAgents);
if(expandedGAgents[0].id !== expandedGAgents[1].id) {
return false;
}
if(expandedGAgents.some((gAgent) => gAgent.isVirtualSource)) {
return false;
}
return true;
}
handleConnect({agents, label, options}) { handleConnect({agents, label, options}) {
if(this._isSelfConnect(agents)) {
const tag = {};
this.handleConnectDelayBegin({
agent: agents[0],
tag,
options,
ln: 0,
});
this.handleConnectDelayEnd({
agent: agents[1],
tag,
label,
options,
});
return;
}
let {flags, gAgents} = this._handlePartialConnect(agents); let {flags, gAgents} = this._handlePartialConnect(agents);
gAgents = this.expandGroupedGAgentConnection(gAgents); gAgents = this.expandGroupedGAgentConnection(gAgents);

View File

@ -20,6 +20,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const any = () => jasmine.anything(); const any = () => jasmine.anything();
const PARSED = { const PARSED = {
sourceAgent: {name: '', alias: '', flags: ['source']},
blockBegin: (tag, label, {ln = 0} = {}) => { blockBegin: (tag, label, {ln = 0} = {}) => {
return {type: 'block begin', blockType: tag, tag, label, ln}; return {type: 'block begin', blockType: tag, tag, label, ln};
}, },
@ -522,10 +524,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('converts source agents into virtual agents', () => { it('converts source agents into virtual agents', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.connect([ PARSED.connect(['A', PARSED.sourceAgent]),
'A',
{name: '', alias: '', flags: ['source']},
]),
]); ]);
expect(sequence.agents).toEqual([ expect(sequence.agents).toEqual([
GENERATED.agent('['), GENERATED.agent('['),
@ -545,14 +544,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('converts sources into distinct virtual agents', () => { it('converts sources into distinct virtual agents', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.connect([ PARSED.connect(['A', PARSED.sourceAgent]),
'A', PARSED.connect(['A', PARSED.sourceAgent]),
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect([
'A',
{name: '', alias: '', flags: ['source']},
]),
]); ]);
expect(sequence.agents).toEqual([ expect(sequence.agents).toEqual([
GENERATED.agent('['), GENERATED.agent('['),
@ -572,10 +565,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('places source agents near the connected agent', () => { it('places source agents near the connected agent', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C']), PARSED.beginAgents(['A', 'B', 'C']),
PARSED.connect([ PARSED.connect(['B', PARSED.sourceAgent]),
'B',
{name: '', alias: '', flags: ['source']},
]),
]); ]);
expect(sequence.agents).toEqual([ expect(sequence.agents).toEqual([
GENERATED.agent('['), GENERATED.agent('['),
@ -593,10 +583,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('places source agents left when connections are reversed', () => { it('places source agents left when connections are reversed', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C']), PARSED.beginAgents(['A', 'B', 'C']),
PARSED.connect([ PARSED.connect([PARSED.sourceAgent, 'B']),
{name: '', alias: '', flags: ['source']},
'B',
]),
]); ]);
expect(sequence.agents).toEqual([ expect(sequence.agents).toEqual([
GENERATED.agent('['), GENERATED.agent('['),
@ -613,10 +600,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects connections between virtual agents', () => { it('rejects connections between virtual agents', () => {
expect(() => invoke([ expect(() => invoke([
PARSED.connect([ PARSED.connect([PARSED.sourceAgent, PARSED.sourceAgent]),
{name: '', alias: '', flags: ['source']},
{name: '', alias: '', flags: ['source']},
]),
])).toThrow(new Error( ])).toThrow(new Error(
'Cannot connect found messages at line 1' 'Cannot connect found messages at line 1'
)); ));
@ -624,10 +608,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects connections between virtual agents and sides', () => { it('rejects connections between virtual agents and sides', () => {
expect(() => invoke([ expect(() => invoke([
PARSED.connect([ PARSED.connect([PARSED.sourceAgent, ']']),
{name: '', alias: '', flags: ['source']},
']',
]),
])).toThrow(new Error( ])).toThrow(new Error(
'Cannot connect found messages to special agents at line 1' 'Cannot connect found messages to special agents at line 1'
)); ));
@ -720,6 +701,55 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]); ]);
}); });
it('converts self connections into delayed connections', () => {
const sequence = invoke([
PARSED.connect(['A', 'A'], {
ln: 0,
label: 'woo',
line: 'solid',
left: 0,
right: 1,
}),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.connectDelayBegin(['A', 'A'], {
label: 'woo!',
tag: '__0',
line: 'solid',
left: 0,
right: 1,
ln: 0,
}),
GENERATED.connectDelayEnd({
tag: '__0',
ln: 0,
}),
any(),
]);
});
it('adds parallel highlighting stages to self connections', () => {
const sequence = invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['start']},
{name: 'A', alias: '', flags: ['stop']},
], {label: 'woo'}),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.parallel([
GENERATED.highlight(['A'], true),
GENERATED.connectDelayBegin(['A', 'A'], {label: 'woo!'}),
]),
GENERATED.parallel([
GENERATED.connectDelayEnd(),
GENERATED.highlight(['A'], false),
]),
any(),
]);
});
it('merges delayed connect arrows', () => { it('merges delayed connect arrows', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.beginAgents(['A', 'B']), PARSED.beginAgents(['A', 'B']),
@ -1123,15 +1153,6 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
'Cannot set agent highlighting multiple times at line 1' 'Cannot set agent highlighting multiple times at line 1'
)); ));
expect(() => invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['start']},
{name: 'A', alias: '', flags: ['stop']},
]),
])).toThrow(new Error(
'Cannot set agent highlighting multiple times at line 1'
));
expect(() => invoke([ expect(() => invoke([
PARSED.connect([ PARSED.connect([
'A', 'A',
@ -1140,15 +1161,6 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
])).toThrow(new Error( ])).toThrow(new Error(
'Cannot set agent visibility multiple times at line 1' 'Cannot set agent visibility multiple times at line 1'
)); ));
expect(() => invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['begin']},
{name: 'A', alias: '', flags: ['end']},
]),
])).toThrow(new Error(
'Cannot set agent visibility multiple times at line 1'
));
}); });
it('adds implicit highlight end with implicit terminator', () => { it('adds implicit highlight end with implicit terminator', () => {
@ -1640,8 +1652,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
expect(sequence.stages).toEqual([ expect(sequence.stages).toEqual([
any(), any(),
any(), any(),
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']), GENERATED.connectDelayBegin(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']), GENERATED.connectDelayEnd(),
GENERATED.connectDelayBegin(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connectDelayEnd(),
any(), any(),
any(), any(),
]); ]);
@ -1792,14 +1806,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const sequence = invoke([ const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C', 'D']), PARSED.beginAgents(['A', 'B', 'C', 'D']),
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}), PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
PARSED.connect([ PARSED.connect([PARSED.sourceAgent, 'Bar']),
{name: '', alias: '', flags: ['source']}, PARSED.connect(['Bar', PARSED.sourceAgent]),
'Bar',
]),
PARSED.connect([
'Bar',
{name: '', alias: '', flags: ['source']},
]),
PARSED.endAgents(['Bar']), PARSED.endAgents(['Bar']),
]); ]);

View File

@ -301,7 +301,7 @@ define([
}; };
const component = this.components.get(stage.type); const component = this.components.get(stage.type);
const result = component.renderPre(stage, envPre); const result = component.renderPre(stage, envPre);
const {topShift, agentIDs, asynchronousY} = const {agentIDs, topShift, asynchronousY} =
BaseComponent.cleanRenderPreResult(result, this.currentY); BaseComponent.cleanRenderPreResult(result, this.currentY);
const topY = this.checkAgentRange(agentIDs, asynchronousY); const topY = this.checkAgentRange(agentIDs, asynchronousY);

View File

@ -57,7 +57,7 @@ define([
'width': width, 'width': width,
'height': height, 'height': height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return { return {
@ -95,7 +95,7 @@ define([
'width': d * 2, 'width': d * 2,
'height': d * 2, 'height': d * 2,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -150,7 +150,7 @@ define([
'width': width, 'width': width,
'height': height, 'height': height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -211,7 +211,7 @@ define([
'width': config.width, 'width': config.width,
'height': config.height, 'height': config.height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {
@ -246,7 +246,7 @@ define([
'width': w, 'width': w,
'height': config.height, 'height': config.height,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
return { return {

View File

@ -65,13 +65,18 @@ define(() => {
} }
BaseComponent.cleanRenderPreResult = ({ BaseComponent.cleanRenderPreResult = ({
topShift = 0,
agentIDs = [], agentIDs = [],
topShift = 0,
y = null,
asynchronousY = null, asynchronousY = null,
} = {}, currentY = null) => { } = {}, currentY = null) => {
if(y !== null && currentY !== null) {
topShift = Math.max(topShift, y - currentY);
}
return { return {
topShift,
agentIDs, agentIDs,
topShift,
y,
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY, asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
}; };
}; };

View File

@ -77,7 +77,7 @@ define([
'width': agentInfoR.x - agentInfoL.x, 'width': agentInfoR.x - agentInfoL.x,
'height': labelHeight, 'height': labelHeight,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
if(!first) { if(!first) {

View File

@ -239,16 +239,12 @@ define([
to.x + to.currentMaxRad + rArrow.width(env.theme), to.x + to.currentMaxRad + rArrow.width(env.theme),
xL + labelW xL + labelW
); );
const y2 = Math.max(
yBegin + config.loopbackRadius * 2,
env.primaryY
);
this.renderRevArrowLine({ this.renderRevArrowLine({
x1: from.x + from.currentMaxRad, x1: from.x + from.currentMaxRad,
y1: yBegin, y1: yBegin,
x2: to.x + to.currentMaxRad, x2: to.x + to.currentMaxRad,
y2, y2: env.primaryY,
xR, xR,
}, options, env); }, options, env);
@ -259,12 +255,16 @@ define([
'x': from.x, 'x': from.x,
'y': yBegin - raise, 'y': yBegin - raise,
'width': xR + config.loopbackRadius - from.x, 'width': xR + config.loopbackRadius - from.x,
'height': raise + y2 - yBegin + arrowDip, 'height': raise + env.primaryY - yBegin + arrowDip,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return y2 + Math.max(arrowDip, 0) + env.theme.actionMargin; return (
env.primaryY +
Math.max(arrowDip, 0) +
env.theme.actionMargin
);
} }
renderArrowLine({x1, y1, x2, y2}, options, env) { renderArrowLine({x1, y1, x2, y2}, options, env) {
@ -398,7 +398,7 @@ define([
'Z' 'Z'
), ),
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
})); }));
this.renderSimpleLabel(label, { this.renderSimpleLabel(label, {
@ -489,11 +489,21 @@ define([
separation() {} separation() {}
renderPre({tag}, env) { renderPre({tag}, env) {
const config = env.theme.connect;
const dc = env.state.delayedConnections; const dc = env.state.delayedConnections;
const beginStage = dc.get(tag).stage; const begin = dc.get(tag);
return Object.assign(super.renderPre(beginStage, env), { const beginStage = begin.stage;
agentIDs: [beginStage.agentIDs[1]], const agentIDs = [beginStage.agentIDs[1]];
});
if(beginStage.agentIDs[0] === beginStage.agentIDs[1]) {
return {
agentIDs,
y: begin.y + config.loopbackRadius * 2,
};
}
return Object.assign(super.renderPre(beginStage, env), {agentIDs});
} }
render({tag}, env) { render({tag}, env) {

View File

@ -82,7 +82,7 @@ define([
'width': right.x - left.x + config.extend * 2, 'width': right.x - left.x + config.extend * 2,
'height': fullHeight, 'height': fullHeight,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return env.primaryY + fullHeight + env.theme.actionMargin; return env.primaryY + fullHeight + env.theme.actionMargin;

View File

@ -92,7 +92,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
'width': x1 - x0, 'width': x1 - x0,
'height': fullH, 'height': fullH,
'fill': 'transparent', 'fill': 'transparent',
'class': 'vis', 'class': 'outline',
}), clickable.firstChild); }), clickable.firstChild);
return ( return (

View File

@ -20,8 +20,9 @@ define([
function mergeResults(a, b) { function mergeResults(a, b) {
array.mergeSets(a.agentIDs, b.agentIDs); array.mergeSets(a.agentIDs, b.agentIDs);
return { return {
topShift: Math.max(a.topShift, b.topShift),
agentIDs: a.agentIDs, agentIDs: a.agentIDs,
topShift: Math.max(a.topShift, b.topShift),
y: nullableMax(a.y, b.y),
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY), asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
}; };
} }

View File

@ -36,7 +36,7 @@ define(['jshintConfig', 'specs'], (jshintConfig) => {
const OPTS_TEST = Object.assign({}, jshintConfig, { const OPTS_TEST = Object.assign({}, jshintConfig, {
predef: PREDEF_TEST, predef: PREDEF_TEST,
maxstatements: 100, // allow lots of tests maxstatements: 200, // allow lots of tests
}); });
function formatError(error) { function formatError(error) {

View File

@ -153,17 +153,17 @@ html, body {
height: 100%; height: 100%;
} }
.pane-view .region:hover .vis { .pane-view .region:hover .outline {
stroke-width: 5px; stroke-width: 5px;
stroke: rgba(255, 255, 0, 0.5); stroke: rgba(255, 255, 0, 0.5);
} }
.pane-view .region.focus .vis { .pane-view .region.focus .outline {
stroke-width: 5px; stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5); stroke: rgba(255, 128, 0, 0.5);
} }
.pane-view .region.focus:hover .vis { .pane-view .region.focus:hover .outline {
stroke-width: 5px; stroke-width: 5px;
stroke: rgba(255, 192, 0, 0.5); stroke: rgba(255, 192, 0, 0.5);
} }

View File

@ -89,7 +89,7 @@ a:hover, a:active {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
} }
.region.focus rect { .region.focus .outline {
stroke-width: 5px; stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5); stroke: rgba(255, 128, 0, 0.5);
} }