Select code in editor when elements are clicked [#16]
This commit is contained in:
parent
b562120c33
commit
25ffd6a904
|
@ -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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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']);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
BasicTheme,
|
BasicTheme,
|
||||||
ChunkyTheme
|
ChunkyTheme
|
||||||
) => {
|
) => {
|
||||||
|
/* jshint +W072 */
|
||||||
const defaultCode = (
|
const defaultCode = (
|
||||||
'title Labyrinth\n' +
|
'title Labyrinth\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -47,6 +47,7 @@ define(() => {
|
||||||
textSizer,
|
textSizer,
|
||||||
SVGTextBlockClass,
|
SVGTextBlockClass,
|
||||||
addDef,
|
addDef,
|
||||||
|
makeRegion,
|
||||||
state,
|
state,
|
||||||
}*/) {
|
}*/) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 +
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue