Select code in editor when elements are clicked [#16]

This commit is contained in:
David Evans 2017-11-10 23:38:28 +00:00
parent b562120c33
commit 25ffd6a904
16 changed files with 442 additions and 35 deletions

View File

@ -0,0 +1,61 @@
define(() => {
'use strict';
return class EventObject {
constructor() {
this.listeners = new Map();
this.forwards = new Set();
}
addEventListener(type, callback) {
const l = this.listeners.get(type);
if(l) {
l.push(callback);
} else {
this.listeners.set(type, [callback]);
}
}
removeEventListener(type, fn) {
const l = this.listeners.get(type);
if(!l) {
return;
}
const i = l.indexOf(fn);
if(i !== -1) {
l.splice(i, 1);
}
}
countEventListeners(type) {
return (this.listeners.get(type) || []).length;
}
removeAllEventListeners(type) {
if(type) {
this.listeners.delete(type);
} else {
this.listeners.clear();
}
}
addEventForwarding(target) {
this.forwards.add(target);
}
removeEventForwarding(target) {
this.forwards.delete(target);
}
removeAllEventForwardings() {
this.forwards.clear();
}
trigger(type, params = []) {
(this.listeners.get(type) || []).forEach(
(listener) => listener.apply(null, params)
);
this.forwards.forEach((fwd) => fwd.trigger(type, params));
}
};
});

View File

@ -0,0 +1,188 @@
defineDescribe('EventObject', ['./EventObject'], (EventObject) => {
'use strict';
let o = null;
beforeEach(() => {
o = new EventObject();
});
describe('trigger', () => {
it('invokes registered listeners', () => {
let triggered = 0;
o.addEventListener('foo', () => {
++ triggered;
});
o.trigger('foo');
expect(triggered).toEqual(1);
});
it('invokes with the given parameters', () => {
let capturedParam1 = null;
let capturedParam2 = null;
o.addEventListener('foo', (param1, param2) => {
capturedParam1 = param1;
capturedParam2 = param2;
});
o.trigger('foo', ['a', 'b']);
expect(capturedParam1).toEqual('a');
expect(capturedParam2).toEqual('b');
});
it('only invokes relevant callbacks', () => {
let triggered = 0;
o.addEventListener('foo', () => {
++ triggered;
});
o.trigger('bar');
expect(triggered).toEqual(0);
});
it('forwards to registered objects', () => {
let capturedType = null;
o.addEventForwarding({trigger: (type) => {
capturedType = type;
}});
o.trigger('bar');
expect(capturedType).toEqual('bar');
});
it('forwards with the given parameters', () => {
let capturedParams = null;
o.addEventForwarding({trigger: (type, params) => {
capturedParams = params;
}});
o.trigger('bar', ['a', 'b']);
expect(capturedParams[0]).toEqual('a');
expect(capturedParams[1]).toEqual('b');
});
});
describe('countEventListeners', () => {
it('returns the number of event listeners of a given type', () => {
o.addEventListener('foo', () => {});
o.addEventListener('foo', () => {});
expect(o.countEventListeners('foo')).toEqual(2);
});
it('does not count unrequested types', () => {
o.addEventListener('foo', () => {});
o.addEventListener('foo', () => {});
o.addEventListener('bar', () => {});
expect(o.countEventListeners('bar')).toEqual(1);
});
it('returns 0 for events which have no listeners', () => {
expect(o.countEventListeners('foo')).toEqual(0);
});
});
describe('removeEventListener', () => {
it('removes the requested listener', () => {
let triggered = 0;
const fn = () => {
++ triggered;
};
o.addEventListener('foo', fn);
o.trigger('foo');
expect(triggered).toEqual(1);
triggered = 0;
o.removeEventListener('foo', fn);
o.trigger('foo');
expect(triggered).toEqual(0);
});
it('leaves other listeners', () => {
let triggered = 0;
const fn1 = () => {
};
const fn2 = () => {
++ triggered;
};
o.addEventListener('foo', fn1);
o.addEventListener('foo', fn2);
o.removeEventListener('foo', fn1);
o.trigger('foo');
expect(triggered).toEqual(1);
});
it('leaves other listener types', () => {
let triggered = 0;
const fn = () => {
++ triggered;
};
o.addEventListener('foo', fn);
o.addEventListener('bar', fn);
o.removeEventListener('foo', fn);
o.trigger('bar');
expect(triggered).toEqual(1);
});
it('silently ignores non-existent listeners', () => {
expect(() => o.removeEventListener('foo', () => {})).not.toThrow();
});
});
describe('removeAllEventListeners', () => {
it('removes all listeners for the requested type', () => {
let triggered = 0;
const fn = () => {
++ triggered;
};
o.addEventListener('foo', fn);
o.trigger('foo');
expect(triggered).toEqual(1);
triggered = 0;
o.removeAllEventListeners('foo');
o.trigger('foo');
expect(triggered).toEqual(0);
});
it('leaves other listener types', () => {
let triggered = 0;
const fn = () => {
++ triggered;
};
o.addEventListener('foo', fn);
o.addEventListener('bar', fn);
o.removeAllEventListeners('foo');
o.trigger('bar');
expect(triggered).toEqual(1);
});
it('silently ignores non-existent types', () => {
expect(() => o.removeAllEventListeners('foo')).not.toThrow();
});
it('removes all listener types when given no argument', () => {
let triggered = 0;
const fn = () => {
++ triggered;
};
o.addEventListener('foo', fn);
o.addEventListener('bar', fn);
o.removeAllEventListeners();
o.trigger('foo');
o.trigger('bar');
expect(triggered).toEqual(0);
});
});
});

View File

@ -51,6 +51,8 @@ define([
this.pngDirty = true; this.pngDirty = true;
this.updatingPNG = false; this.updatingPNG = false;
this.marker = null;
this._downloadSVGClick = this._downloadSVGClick.bind(this); this._downloadSVGClick = this._downloadSVGClick.bind(this);
this._downloadPNGClick = this._downloadPNGClick.bind(this); this._downloadPNGClick = this._downloadPNGClick.bind(this);
this._downloadPNGFocus = this._downloadPNGFocus.bind(this); this._downloadPNGFocus = this._downloadPNGFocus.bind(this);
@ -147,6 +149,50 @@ define([
return code; return code;
} }
registerListeners() {
this.code.on('change', () => this.update(false));
this.renderer.addEventListener('mouseover', (element) => {
if(this.marker) {
this.marker.clear();
}
if(element.ln !== undefined) {
this.marker = this.code.markText(
{line: element.ln, ch: 0},
{line: element.ln + 1, ch: 0},
{
className: 'hover',
inclusiveLeft: false,
inclusiveRight: false,
clearOnEnter: true,
}
);
}
});
this.renderer.addEventListener('mouseout', () => {
if(this.marker) {
this.marker.clear();
this.marker = null;
}
});
this.renderer.addEventListener('click', (element) => {
if(this.marker) {
this.marker.clear();
this.marker = null;
}
if(element.ln !== undefined) {
this.code.setSelection(
{line: element.ln, ch: 0},
{line: element.ln + 1, ch: 0},
{origin: '+focus', bias: -1}
);
this.code.focus();
}
});
}
build(container) { build(container) {
const codePane = makeNode('div', {'class': 'pane-code'}); const codePane = makeNode('div', {'class': 'pane-code'});
const viewPane = makeNode('div', {'class': 'pane-view'}); const viewPane = makeNode('div', {'class': 'pane-view'});
@ -172,7 +218,7 @@ define([
this.code = this.buildEditor(codePane); this.code = this.buildEditor(codePane);
this.viewPaneInner.appendChild(this.renderer.svg()); this.viewPaneInner.appendChild(this.renderer.svg());
this.code.on('change', () => this.update(false)); this.registerListeners();
this.update(); this.update();
} }

View File

@ -31,6 +31,7 @@ defineDescribe('Interface', ['./Interface'], (Interface) => {
'render', 'render',
'svg', 'svg',
'getThemeNames', 'getThemeNames',
'addEventListener',
]); ]);
renderer.svg.and.returnValue(document.createElement('svg')); renderer.svg.and.returnValue(document.createElement('svg'));
container = jasmine.createSpyObj('container', ['appendChild']); container = jasmine.createSpyObj('container', ['appendChild']);

View File

@ -21,6 +21,7 @@
BasicTheme, BasicTheme,
ChunkyTheme ChunkyTheme
) => { ) => {
/* jshint +W072 */
const defaultCode = ( const defaultCode = (
'title Labyrinth\n' + 'title Labyrinth\n' +
'\n' + '\n' +

View File

@ -79,6 +79,7 @@
ChunkyTheme, ChunkyTheme,
Exporter Exporter
) => { ) => {
/* jshint +W072 */
const parser = new Parser(); const parser = new Parser();
const generator = new Generator(); const generator = new Generator();
const themes = [ const themes = [

View File

@ -230,6 +230,9 @@ define(['core/ArrayUtilities'], (array) => {
if(!stage) { if(!stage) {
return; return;
} }
if(stage.ln === undefined) {
stage.ln = this.latestLine;
}
this.currentSection.stages.push(stage); this.currentSection.stages.push(stage);
if(isVisible) { if(isVisible) {
this.currentNest.hasContent = true; this.currentNest.hasContent = true;
@ -244,6 +247,11 @@ define(['core/ArrayUtilities'], (array) => {
if(viableStages.length === 1) { if(viableStages.length === 1) {
return this.addStage(viableStages[0]); return this.addStage(viableStages[0]);
} }
viableStages.forEach((stage) => {
if(stage.ln === undefined) {
stage.ln = this.latestLine;
}
});
return this.addStage({ return this.addStage({
type: 'parallel', type: 'parallel',
stages: viableStages, stages: viableStages,
@ -503,6 +511,7 @@ define(['core/ArrayUtilities'], (array) => {
} }
handleStage(stage) { handleStage(stage) {
this.latestLine = stage.ln;
try { try {
this.stageHandlers[stage.type](stage); this.stageHandlers[stage.type](stage);
} catch(e) { } catch(e) {

View File

@ -26,26 +26,29 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
return {type: 'block end'}; return {type: 'block end'};
}, },
defineAgents: (agentNames) => { defineAgents: (agentNames, {ln = 0} = {}) => {
return { return {
type: 'agent define', type: 'agent define',
agents: makeParsedAgents(agentNames), agents: makeParsedAgents(agentNames),
ln,
}; };
}, },
beginAgents: (agentNames, {mode = 'box'} = {}) => { beginAgents: (agentNames, {mode = 'box', ln = 0} = {}) => {
return { return {
type: 'agent begin', type: 'agent begin',
agents: makeParsedAgents(agentNames), agents: makeParsedAgents(agentNames),
mode, mode,
ln,
}; };
}, },
endAgents: (agentNames, {mode = 'cross'} = {}) => { endAgents: (agentNames, {mode = 'cross', ln = 0} = {}) => {
return { return {
type: 'agent end', type: 'agent end',
agents: makeParsedAgents(agentNames), agents: makeParsedAgents(agentNames),
mode, mode,
ln,
}; };
}, },
@ -54,6 +57,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
line = '', line = '',
left = 0, left = 0,
right = 0, right = 0,
ln = 0,
} = {}) => { } = {}) => {
return { return {
type: 'connect', type: 'connect',
@ -64,18 +68,21 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
left, left,
right, right,
}, },
ln,
}; };
}, },
note: (type, agentNames, { note: (type, agentNames, {
mode = '', mode = '',
label = '', label = '',
ln = 0,
} = {}) => { } = {}) => {
return { return {
type, type,
agents: makeParsedAgents(agentNames), agents: makeParsedAgents(agentNames),
mode, mode,
label, label,
ln,
}; };
}, },
}; };
@ -83,21 +90,25 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const GENERATED = { const GENERATED = {
beginAgents: (agentNames, { beginAgents: (agentNames, {
mode = jasmine.anything(), mode = jasmine.anything(),
ln = jasmine.anything(),
} = {}) => { } = {}) => {
return { return {
type: 'agent begin', type: 'agent begin',
agentNames, agentNames,
mode, mode,
ln,
}; };
}, },
endAgents: (agentNames, { endAgents: (agentNames, {
mode = jasmine.anything(), mode = jasmine.anything(),
ln = jasmine.anything(),
} = {}) => { } = {}) => {
return { return {
type: 'agent end', type: 'agent end',
agentNames, agentNames,
mode, mode,
ln,
}; };
}, },
@ -106,6 +117,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
line = jasmine.anything(), line = jasmine.anything(),
left = jasmine.anything(), left = jasmine.anything(),
right = jasmine.anything(), right = jasmine.anything(),
ln = jasmine.anything(),
} = {}) => { } = {}) => {
return { return {
type: 'connect', type: 'connect',
@ -116,33 +128,42 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
left, left,
right, right,
}, },
ln,
}; };
}, },
highlight: (agentNames, highlighted) => { highlight: (agentNames, highlighted, {
ln = jasmine.anything(),
} = {}) => {
return { return {
type: 'agent highlight', type: 'agent highlight',
agentNames, agentNames,
highlighted, highlighted,
ln,
}; };
}, },
note: (type, agentNames, { note: (type, agentNames, {
mode = jasmine.anything(), mode = jasmine.anything(),
label = jasmine.anything(), label = jasmine.anything(),
ln = jasmine.anything(),
} = {}) => { } = {}) => {
return { return {
type, type,
agentNames, agentNames,
mode, mode,
label, label,
ln,
}; };
}, },
parallel: (stages) => { parallel: (stages, {
ln = jasmine.anything(),
} = {}) => {
return { return {
type: 'parallel', type: 'parallel',
stages, stages,
ln,
}; };
}, },
}; };
@ -172,14 +193,14 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('passes marks and async through', () => { it('passes marks and async through', () => {
const sequence = generator.generate({stages: [ const sequence = generator.generate({stages: [
{type: 'mark', name: 'foo'}, {type: 'mark', name: 'foo', ln: 0},
{type: 'async', target: 'foo'}, {type: 'async', target: 'foo', ln: 1},
{type: 'async', target: ''}, {type: 'async', target: '', ln: 2},
]}); ]});
expect(sequence.stages).toEqual([ expect(sequence.stages).toEqual([
{type: 'mark', name: 'foo'}, {type: 'mark', name: 'foo', ln: 0},
{type: 'async', target: 'foo'}, {type: 'async', target: 'foo', ln: 1},
{type: 'async', target: ''}, {type: 'async', target: '', ln: 2},
]); ]);
}); });

View File

@ -1,5 +1,7 @@
/* jshint -W072 */ // Allow several required modules
define([ define([
'core/ArrayUtilities', 'core/ArrayUtilities',
'core/EventObject',
'svg/SVGUtilities', 'svg/SVGUtilities',
'svg/SVGShapes', 'svg/SVGShapes',
'./components/BaseComponent', './components/BaseComponent',
@ -10,10 +12,12 @@ define([
'./components/Note', './components/Note',
], ( ], (
array, array,
EventObject,
svg, svg,
SVGShapes, SVGShapes,
BaseComponent BaseComponent
) => { ) => {
/* jshint +W072 */
'use strict'; 'use strict';
function traverse(stages, callbacks) { function traverse(stages, callbacks) {
@ -77,13 +81,23 @@ define([
let globalNamespace = 0; let globalNamespace = 0;
return class Renderer { function parseNamespace(namespace) {
if(namespace === null) {
namespace = 'R' + globalNamespace;
++ globalNamespace;
}
return namespace;
}
return class Renderer extends EventObject {
constructor({ constructor({
themes = [], themes = [],
namespace = null, namespace = null,
components = null, components = null,
SVGTextBlockClass = SVGShapes.TextBlock, SVGTextBlockClass = SVGShapes.TextBlock,
} = {}) { } = {}) {
super();
if(components === null) { if(components === null) {
components = BaseComponent.getComponents(); components = BaseComponent.getComponents();
} }
@ -111,11 +125,7 @@ define([
this.height = 0; this.height = 0;
this.themes = makeThemes(themes); this.themes = makeThemes(themes);
this.theme = null; this.theme = null;
this.namespace = namespace; this.namespace = parseNamespace(namespace);
if(namespace === null) {
this.namespace = 'R' + globalNamespace;
++ globalNamespace;
}
this.components = components; this.components = components;
this.SVGTextBlockClass = SVGTextBlockClass; this.SVGTextBlockClass = SVGTextBlockClass;
this.knownDefs = new Set(); this.knownDefs = new Set();
@ -442,6 +452,29 @@ define([
}; };
let bottomY = topY; let bottomY = topY;
stages.forEach((stage) => { stages.forEach((stage) => {
const eventOver = () => {
this.trigger('mouseover', [stage]);
};
const eventOut = () => {
this.trigger('mouseout');
};
const eventClick = () => {
this.trigger('click', [stage]);
};
env.makeRegion = (o) => {
if(!o) {
o = svg.make('g');
}
o.addEventListener('mouseenter', eventOver);
o.addEventListener('mouseleave', eventOut);
o.addEventListener('click', eventClick);
this.actionLabels.appendChild(o);
return o;
};
const component = this.components.get(stage.type); const component = this.components.get(stage.type);
const baseY = component.render(stage, env); const baseY = component.render(stage, env);
if(baseY !== undefined) { if(baseY !== undefined) {

View File

@ -39,16 +39,24 @@ define([
render(y, {x, label}, env) { render(y, {x, label}, env) {
const config = env.theme.agentCap.box; const config = env.theme.agentCap.box;
const {height} = SVGShapes.renderBoxedText(label, { const clickable = env.makeRegion();
const {width, height} = SVGShapes.renderBoxedText(label, {
x, x,
y, y,
padding: config.padding, padding: config.padding,
boxAttrs: config.boxAttrs, boxAttrs: config.boxAttrs,
labelAttrs: config.labelAttrs, labelAttrs: config.labelAttrs,
boxLayer: env.shapeLayer, boxLayer: env.shapeLayer,
labelLayer: env.labelLayer, labelLayer: clickable,
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
clickable.insertBefore(svg.make('rect', {
'x': x - width / 2,
'y': y,
'width': width,
'height': height,
'fill': 'transparent',
}), clickable.firstChild);
return { return {
lineTop: 0, lineTop: 0,

View File

@ -47,6 +47,7 @@ define(() => {
textSizer, textSizer,
SVGTextBlockClass, SVGTextBlockClass,
addDef, addDef,
makeRegion,
state, state,
}*/) { }*/) {
} }

View File

@ -135,6 +135,7 @@ define([
} }
renderSelfConnect({label, agentNames, options}, env) { renderSelfConnect({label, agentNames, options}, env) {
/* jshint -W071 */ // TODO: find appropriate abstractions
const config = env.theme.connect; const config = env.theme.connect;
const from = env.agentInfos.get(agentNames[0]); const from = env.agentInfos.get(agentNames[0]);
@ -155,6 +156,8 @@ define([
(label ? config.label.padding : 0) (label ? config.label.padding : 0)
); );
const clickable = env.makeRegion();
const renderedText = SVGShapes.renderBoxedText(label, { const renderedText = SVGShapes.renderBoxedText(label, {
x: x0 - config.mask.padding.left, x: x0 - config.mask.padding.left,
y: y0 - height + config.label.margin.top, y: y0 - height + config.label.margin.top,
@ -162,7 +165,7 @@ define([
boxAttrs: {'fill': '#000000'}, boxAttrs: {'fill': '#000000'},
labelAttrs: config.label.loopbackAttrs, labelAttrs: config.label.loopbackAttrs,
boxLayer: env.maskLayer, boxLayer: env.maskLayer,
labelLayer: env.labelLayer, labelLayer: clickable,
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
const labelW = (label ? ( const labelW = (label ? (
@ -190,7 +193,17 @@ define([
lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1}); lArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y0, dir: 1});
rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1}); rArrow.render(env.shapeLayer, env.theme, {x: lineX, y: y1, dir: 1});
return y1 + rArrow.height(env.theme) / 2 + env.theme.actionMargin; const arrowDip = rArrow.height(env.theme) / 2;
clickable.insertBefore(svg.make('rect', {
'x': lineX,
'y': y0 - height,
'width': x1 + r - lineX,
'height': height + r * 2 + arrowDip,
'fill': 'transparent',
}), clickable.firstChild);
return y1 + arrowDip + env.theme.actionMargin;
} }
renderSimpleConnect({label, agentNames, options}, env) { renderSimpleConnect({label, agentNames, options}, env) {
@ -211,7 +224,9 @@ define([
const x0 = from.x + from.currentMaxRad * dir; const x0 = from.x + from.currentMaxRad * dir;
const x1 = to.x - to.currentMaxRad * dir; const x1 = to.x - to.currentMaxRad * dir;
let y = env.primaryY; const y = env.primaryY;
const clickable = env.makeRegion();
SVGShapes.renderBoxedText(label, { SVGShapes.renderBoxedText(label, {
x: (x0 + x1) / 2, x: (x0 + x1) / 2,
@ -220,7 +235,7 @@ define([
boxAttrs: {'fill': '#000000'}, boxAttrs: {'fill': '#000000'},
labelAttrs: config.label.attrs, labelAttrs: config.label.attrs,
boxLayer: env.maskLayer, boxLayer: env.maskLayer,
labelLayer: env.labelLayer, labelLayer: clickable,
SVGTextBlockClass: env.SVGTextBlockClass, SVGTextBlockClass: env.SVGTextBlockClass,
}); });
@ -235,14 +250,20 @@ define([
lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir}); lArrow.render(env.shapeLayer, env.theme, {x: x0, y, dir});
rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir}); rArrow.render(env.shapeLayer, env.theme, {x: x1, y, dir: -dir});
return ( const arrowDip = Math.max(
y +
Math.max(
lArrow.height(env.theme), lArrow.height(env.theme),
rArrow.height(env.theme) rArrow.height(env.theme)
) / 2 + ) / 2;
env.theme.actionMargin
); clickable.insertBefore(svg.make('rect', {
'x': Math.min(x0, x1),
'y': y - height,
'width': Math.abs(x1 - x0),
'height': height + arrowDip,
'fill': 'transparent',
}), clickable.firstChild);
return y + arrowDip + env.theme.actionMargin;
} }
renderPre({label, agentNames, options}, env) { renderPre({label, agentNames, options}, env) {

View File

@ -1,4 +1,4 @@
define(['./BaseComponent'], (BaseComponent) => { define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
'use strict'; 'use strict';
function findExtremes(agentInfos, agentNames) { function findExtremes(agentInfos, agentNames) {
@ -34,8 +34,10 @@ define(['./BaseComponent'], (BaseComponent) => {
}, env) { }, env) {
const config = env.theme.note[mode]; const config = env.theme.note[mode];
const clickable = env.makeRegion();
const y = env.topY + config.margin.top + config.padding.top; const y = env.topY + config.margin.top + config.padding.top;
const labelNode = new env.SVGTextBlockClass(env.labelLayer, { const labelNode = new env.SVGTextBlockClass(clickable, {
attrs: config.labelAttrs, attrs: config.labelAttrs,
text: label, text: label,
y, y,
@ -84,6 +86,14 @@ define(['./BaseComponent'], (BaseComponent) => {
height: fullH, height: fullH,
})); }));
clickable.insertBefore(svg.make('rect', {
'x': x0,
'y': env.topY + config.margin.top,
'width': x1 - x0,
'height': fullH,
'fill': 'transparent',
}), clickable.firstChild);
return ( return (
env.topY + env.topY +
config.margin.top + config.margin.top +

View File

@ -1,4 +1,4 @@
/* jshint -W072 */ /* jshint -W072 */ // Allow several required modules
defineDescribe('Sequence Integration', [ defineDescribe('Sequence Integration', [
'./Parser', './Parser',
'./Generator', './Generator',
@ -12,6 +12,7 @@ defineDescribe('Sequence Integration', [
BasicTheme, BasicTheme,
SVGTextBlock SVGTextBlock
) => { ) => {
/* jshint +W072 */
'use strict'; 'use strict';
let parser = null; let parser = null;

View File

@ -1,5 +1,6 @@
define([ define([
'core/ArrayUtilities_spec', 'core/ArrayUtilities_spec',
'core/EventObject_spec',
'svg/SVGUtilities_spec', 'svg/SVGUtilities_spec',
'svg/SVGTextBlock_spec', 'svg/SVGTextBlock_spec',
'svg/SVGShapes_spec', 'svg/SVGShapes_spec',

View File

@ -34,6 +34,10 @@ html, body {
background: rgba(255, 0, 0, 0.2); background: rgba(255, 0, 0, 0.2);
} }
.hover {
background: #FFFF00;
}
.pick-virtual { .pick-virtual {
color: #777777; color: #777777;
} }