Begin separating theme out of renderer
This commit is contained in:
parent
1aac54cefc
commit
b0ba84b4eb
|
@ -3,16 +3,19 @@
|
|||
|
||||
requirejs.config(window.getRequirejsCDN());
|
||||
|
||||
/* jshint -W072 */
|
||||
requirejs([
|
||||
'interface/Interface',
|
||||
'sequence/Parser',
|
||||
'sequence/Generator',
|
||||
'sequence/Renderer',
|
||||
'sequence/themes/Basic',
|
||||
], (
|
||||
Interface,
|
||||
Parser,
|
||||
Generator,
|
||||
Renderer
|
||||
Renderer,
|
||||
Theme
|
||||
) => {
|
||||
const defaultCode = (
|
||||
'title Labyrinth\n' +
|
||||
|
@ -36,7 +39,7 @@
|
|||
defaultCode,
|
||||
parser: new Parser(),
|
||||
generator: new Generator(),
|
||||
renderer: new Renderer(),
|
||||
renderer: new Renderer(new Theme()),
|
||||
});
|
||||
ui.build(document.body);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(['./ArrayUtilities'], (array) => {
|
||||
define(['core/ArrayUtilities'], (array) => {
|
||||
'use strict';
|
||||
|
||||
class AgentState {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
define([
|
||||
'./ArrayUtilities',
|
||||
'./SVGUtilities',
|
||||
'./SVGTextBlock',
|
||||
'./SVGShapes',
|
||||
'core/ArrayUtilities',
|
||||
'svg/SVGUtilities',
|
||||
'svg/SVGTextBlock',
|
||||
'svg/SVGShapes',
|
||||
], (
|
||||
array,
|
||||
svg,
|
||||
|
@ -13,219 +13,6 @@ define([
|
|||
|
||||
const SEP_ZERO = {left: 0, right: 0};
|
||||
|
||||
const LINE_HEIGHT = 1.3;
|
||||
const TITLE_MARGIN = 10;
|
||||
const OUTER_MARGIN = 5;
|
||||
const AGENT_MARGIN = 10;
|
||||
const ACTION_MARGIN = 5;
|
||||
|
||||
const AGENT_CAP = {
|
||||
box: {
|
||||
padding: {
|
||||
top: 5,
|
||||
left: 10,
|
||||
right: 10,
|
||||
bottom: 5,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 12,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
},
|
||||
},
|
||||
cross: {
|
||||
size: 20,
|
||||
attrs: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
attrs: {
|
||||
'fill': '#000000',
|
||||
'height': 5,
|
||||
},
|
||||
},
|
||||
none: {
|
||||
height: 10,
|
||||
},
|
||||
};
|
||||
|
||||
const CONNECT = {
|
||||
lineAttrs: {
|
||||
'solid': {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
'dash': {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'stroke-dasharray': '4, 2',
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
width: 4,
|
||||
height: 8,
|
||||
attrs: {
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'stroke-linejoin': 'miter',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
padding: 6,
|
||||
margin: {top: 2, bottom: 1},
|
||||
attrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
left: 3,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
maskAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const BLOCK = {
|
||||
margin: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1.5,
|
||||
'rx': 2,
|
||||
'ry': 2,
|
||||
},
|
||||
section: {
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 2,
|
||||
},
|
||||
mode: {
|
||||
padding: {
|
||||
top: 1,
|
||||
left: 3,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'rx': 2,
|
||||
'ry': 2,
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-weight': 'bold',
|
||||
'font-size': 9,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'left',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
padding: {
|
||||
top: 1,
|
||||
left: 5,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
maskAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'left',
|
||||
},
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
attrs: {
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1.5,
|
||||
'stroke-dasharray': '4, 2',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const NOTE = {
|
||||
'note': {
|
||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
||||
overlap: {left: 10, right: 10},
|
||||
boxRenderer: SVGShapes.renderNote.bind(null, {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
}, {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
}),
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
'state': {
|
||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
||||
overlap: {left: 10, right: 10},
|
||||
boxRenderer: SVGShapes.renderBox.bind(null, {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'rx': 10,
|
||||
'ry': 10,
|
||||
}),
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const ATTRS = {
|
||||
TITLE: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 20,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
'class': 'title',
|
||||
},
|
||||
|
||||
AGENT_LINE: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
};
|
||||
|
||||
function drawHorizontalArrowHead(container, {x, y, dx, dy, attrs}) {
|
||||
container.appendChild(svg.make(
|
||||
attrs.fill === 'none' ? 'polyline' : 'polygon',
|
||||
|
@ -265,7 +52,7 @@ define([
|
|||
}
|
||||
|
||||
return class Renderer {
|
||||
constructor() {
|
||||
constructor(theme) {
|
||||
this.separationAgentCap = {
|
||||
'box': this.separationAgentCapBox.bind(this),
|
||||
'cross': this.separationAgentCapCross.bind(this),
|
||||
|
@ -317,6 +104,8 @@ define([
|
|||
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.theme = theme;
|
||||
this.currentSequence = null;
|
||||
this.buildStaticElements();
|
||||
}
|
||||
|
||||
|
@ -338,7 +127,7 @@ define([
|
|||
this.base.appendChild(this.sections);
|
||||
this.base.appendChild(this.actionShapes);
|
||||
this.base.appendChild(this.actionLabels);
|
||||
this.title = new SVGTextBlock(this.base, ATTRS.TITLE);
|
||||
this.title = new SVGTextBlock(this.base);
|
||||
|
||||
this.sizer = new SVGTextBlock.SizeTester(this.base);
|
||||
}
|
||||
|
@ -385,17 +174,18 @@ define([
|
|||
this.addSeparation(
|
||||
agentR,
|
||||
agentL,
|
||||
sepR.left + sepL.right + AGENT_MARGIN
|
||||
sepR.left + sepL.right + this.theme.agentMargin
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
separationAgentCapBox({label}) {
|
||||
const config = this.theme.agentCap.box;
|
||||
const width = (
|
||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||
AGENT_CAP.box.padding.left +
|
||||
AGENT_CAP.box.padding.right
|
||||
this.sizer.measure(config.labelAttrs, label).width +
|
||||
config.padding.left +
|
||||
config.padding.right
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -405,17 +195,19 @@ define([
|
|||
}
|
||||
|
||||
separationAgentCapCross() {
|
||||
const config = this.theme.agentCap.cross;
|
||||
return {
|
||||
left: AGENT_CAP.cross.size / 2,
|
||||
right: AGENT_CAP.cross.size / 2,
|
||||
left: config.size / 2,
|
||||
right: config.size / 2,
|
||||
};
|
||||
}
|
||||
|
||||
separationAgentCapBar({label}) {
|
||||
const config = this.theme.agentCap.box;
|
||||
const width = (
|
||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||
AGENT_CAP.box.padding.left +
|
||||
AGENT_CAP.box.padding.right
|
||||
this.sizer.measure(config.labelAttrs, label).width +
|
||||
config.padding.left +
|
||||
config.padding.right
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -447,19 +239,21 @@ define([
|
|||
}
|
||||
|
||||
separationConnection({agents, label}) {
|
||||
const config = this.theme.connect;
|
||||
|
||||
this.addSeparation(
|
||||
agents[0],
|
||||
agents[1],
|
||||
|
||||
this.sizer.measure(CONNECT.label.attrs, label).width +
|
||||
CONNECT.arrow.width * 2 +
|
||||
CONNECT.label.padding * 2 +
|
||||
ATTRS.AGENT_LINE['stroke-width']
|
||||
this.sizer.measure(config.label.attrs, label).width +
|
||||
config.arrow.width * 2 +
|
||||
config.label.padding * 2 +
|
||||
this.theme.agentLineAttrs['stroke-width']
|
||||
);
|
||||
}
|
||||
|
||||
separationNoteOver({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
const width = (
|
||||
this.sizer.measure(config.labelAttrs, label).width +
|
||||
config.padding.left +
|
||||
|
@ -491,7 +285,7 @@ define([
|
|||
}
|
||||
|
||||
separationNoteLeft({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
const {left} = this.findExtremes(agents);
|
||||
|
||||
const agentSpaces = new Map();
|
||||
|
@ -509,7 +303,7 @@ define([
|
|||
}
|
||||
|
||||
separationNoteRight({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
const {right} = this.findExtremes(agents);
|
||||
|
||||
const agentSpaces = new Map();
|
||||
|
@ -527,7 +321,7 @@ define([
|
|||
}
|
||||
|
||||
separationNoteBetween({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
|
||||
this.addSeparation(
|
||||
|
@ -548,7 +342,7 @@ define([
|
|||
}
|
||||
|
||||
separationSectionBegin(scope, {left, right}, {mode, label}) {
|
||||
const config = BLOCK.section;
|
||||
const config = this.theme.block.section;
|
||||
const width = (
|
||||
this.sizer.measure(config.mode.labelAttrs, mode).width +
|
||||
config.mode.padding.left +
|
||||
|
@ -569,12 +363,13 @@ define([
|
|||
}
|
||||
|
||||
renderAgentCapBox({x, label}) {
|
||||
const config = this.theme.agentCap.box;
|
||||
const {height} = SVGShapes.renderBoxedText(label, {
|
||||
x,
|
||||
y: this.currentY,
|
||||
padding: AGENT_CAP.box.padding,
|
||||
boxAttrs: AGENT_CAP.box.boxAttrs,
|
||||
labelAttrs: AGENT_CAP.box.labelAttrs,
|
||||
padding: config.padding,
|
||||
boxAttrs: config.boxAttrs,
|
||||
labelAttrs: config.labelAttrs,
|
||||
boxLayer: this.actionShapes,
|
||||
labelLayer: this.actionLabels,
|
||||
});
|
||||
|
@ -587,8 +382,9 @@ define([
|
|||
}
|
||||
|
||||
renderAgentCapCross({x}) {
|
||||
const config = this.theme.agentCap.cross;
|
||||
const y = this.currentY;
|
||||
const d = AGENT_CAP.cross.size / 2;
|
||||
const d = config.size / 2;
|
||||
|
||||
this.actionShapes.appendChild(svg.make('path', Object.assign({
|
||||
'd': (
|
||||
|
@ -597,7 +393,7 @@ define([
|
|||
' M ' + (x + d) + ' ' + y +
|
||||
' L ' + (x - d) + ' ' + (y + d * 2)
|
||||
),
|
||||
}, AGENT_CAP.cross.attrs)));
|
||||
}, config.attrs)));
|
||||
|
||||
return {
|
||||
lineTop: d,
|
||||
|
@ -607,30 +403,33 @@ define([
|
|||
}
|
||||
|
||||
renderAgentCapBar({x, label}) {
|
||||
const configB = this.theme.agentCap.box;
|
||||
const config = this.theme.agentCap.bar;
|
||||
const width = (
|
||||
this.sizer.measure(AGENT_CAP.box.labelAttrs, label).width +
|
||||
AGENT_CAP.box.padding.left +
|
||||
AGENT_CAP.box.padding.right
|
||||
this.sizer.measure(configB.labelAttrs, label).width +
|
||||
configB.padding.left +
|
||||
configB.padding.right
|
||||
);
|
||||
|
||||
this.actionShapes.appendChild(svg.make('rect', Object.assign({
|
||||
'x': x - width / 2,
|
||||
'y': this.currentY,
|
||||
'width': width,
|
||||
}, AGENT_CAP.bar.attrs)));
|
||||
}, config.attrs)));
|
||||
|
||||
return {
|
||||
lineTop: 0,
|
||||
lineBottom: AGENT_CAP.bar.attrs.height,
|
||||
height: AGENT_CAP.bar.attrs.height,
|
||||
lineBottom: config.attrs.height,
|
||||
height: config.attrs.height,
|
||||
};
|
||||
}
|
||||
|
||||
renderAgentCapNone() {
|
||||
const config = this.theme.agentCap.none;
|
||||
return {
|
||||
lineTop: AGENT_CAP.none.height,
|
||||
lineTop: config.height,
|
||||
lineBottom: 0,
|
||||
height: AGENT_CAP.none.height,
|
||||
height: config.height,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -642,7 +441,7 @@ define([
|
|||
maxHeight = Math.max(maxHeight, shifts.height);
|
||||
agentInfo.latestYStart = this.currentY + shifts.lineBottom;
|
||||
});
|
||||
this.currentY += maxHeight + ACTION_MARGIN;
|
||||
this.currentY += maxHeight + this.theme.actionMargin;
|
||||
}
|
||||
|
||||
renderAgentEnd({mode, agents}) {
|
||||
|
@ -658,34 +457,35 @@ define([
|
|||
'x2': x,
|
||||
'y2': this.currentY + shifts.lineTop,
|
||||
'class': 'agent-' + agentInfo.index + '-line',
|
||||
}, ATTRS.AGENT_LINE)));
|
||||
}, this.theme.agentLineAttrs)));
|
||||
agentInfo.latestYStart = null;
|
||||
});
|
||||
this.currentY += maxHeight + ACTION_MARGIN;
|
||||
this.currentY += maxHeight + this.theme.actionMargin;
|
||||
}
|
||||
|
||||
renderConnection({label, agents, line, left, right}) {
|
||||
const config = this.theme.connect;
|
||||
const from = this.agentInfos.get(agents[0]);
|
||||
const to = this.agentInfos.get(agents[1]);
|
||||
|
||||
const dy = CONNECT.arrow.height / 2;
|
||||
const dy = config.arrow.height / 2;
|
||||
const dir = (from.x < to.x) ? 1 : -1;
|
||||
const short = ATTRS.AGENT_LINE['stroke-width'];
|
||||
const short = this.theme.agentLineAttrs['stroke-width'];
|
||||
|
||||
const height = (
|
||||
this.sizer.measureHeight(CONNECT.label.attrs, label) +
|
||||
CONNECT.label.margin.top +
|
||||
CONNECT.label.margin.bottom
|
||||
this.sizer.measureHeight(config.label.attrs, label) +
|
||||
config.label.margin.top +
|
||||
config.label.margin.bottom
|
||||
);
|
||||
|
||||
let y = this.currentY + Math.max(dy, height);
|
||||
|
||||
SVGShapes.renderBoxedText(label, {
|
||||
x: (from.x + to.x) / 2,
|
||||
y: y - height + CONNECT.label.margin.top,
|
||||
padding: CONNECT.mask.padding,
|
||||
boxAttrs: CONNECT.mask.maskAttrs,
|
||||
labelAttrs: CONNECT.label.attrs,
|
||||
y: y - height + config.label.margin.top,
|
||||
padding: config.mask.padding,
|
||||
boxAttrs: config.mask.maskAttrs,
|
||||
labelAttrs: config.label.attrs,
|
||||
boxLayer: this.mask,
|
||||
labelLayer: this.actionLabels,
|
||||
});
|
||||
|
@ -695,15 +495,15 @@ define([
|
|||
'y1': y,
|
||||
'x2': to.x - (right ? short : 0) * dir,
|
||||
'y2': y,
|
||||
}, CONNECT.lineAttrs[line])));
|
||||
}, config.lineAttrs[line])));
|
||||
|
||||
if(left) {
|
||||
drawHorizontalArrowHead(this.actionShapes, {
|
||||
x: from.x + short * dir,
|
||||
y,
|
||||
dx: CONNECT.arrow.width * dir,
|
||||
dx: config.arrow.width * dir,
|
||||
dy,
|
||||
attrs: CONNECT.arrow.attrs,
|
||||
attrs: config.arrow.attrs,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -711,26 +511,26 @@ define([
|
|||
drawHorizontalArrowHead(this.actionShapes, {
|
||||
x: to.x - short * dir,
|
||||
y,
|
||||
dx: -CONNECT.arrow.width * dir,
|
||||
dx: -config.arrow.width * dir,
|
||||
dy,
|
||||
attrs: CONNECT.arrow.attrs,
|
||||
attrs: config.arrow.attrs,
|
||||
});
|
||||
}
|
||||
|
||||
this.currentY = y + dy + ACTION_MARGIN;
|
||||
this.currentY = y + dy + this.theme.actionMargin;
|
||||
}
|
||||
|
||||
renderNote({xMid = null, x0 = null, x1 = null}, anchor, mode, label) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
this.currentY += config.margin.top;
|
||||
|
||||
const y = this.currentY + config.padding.top;
|
||||
const labelNode = new SVGTextBlock(
|
||||
this.actionLabels,
|
||||
config.labelAttrs,
|
||||
{text: label, y}
|
||||
);
|
||||
const labelNode = new SVGTextBlock(this.actionLabels, {
|
||||
attrs: config.labelAttrs,
|
||||
text: label,
|
||||
y,
|
||||
});
|
||||
|
||||
const fullW = (
|
||||
labelNode.width +
|
||||
|
@ -752,16 +552,19 @@ define([
|
|||
}
|
||||
switch(config.labelAttrs['text-anchor']) {
|
||||
case 'middle':
|
||||
labelNode.reanchor((
|
||||
x0 + config.padding.left +
|
||||
x1 - config.padding.right
|
||||
) / 2, y);
|
||||
labelNode.set({
|
||||
x: (
|
||||
x0 + config.padding.left +
|
||||
x1 - config.padding.right
|
||||
) / 2,
|
||||
y,
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
labelNode.reanchor(x1 - config.padding.right, y);
|
||||
labelNode.set({x: x1 - config.padding.right, y});
|
||||
break;
|
||||
default:
|
||||
labelNode.reanchor(x0 + config.padding.left, y);
|
||||
labelNode.set({x: x0 + config.padding.left, y});
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -775,12 +578,12 @@ define([
|
|||
this.currentY += (
|
||||
fullH +
|
||||
config.margin.bottom +
|
||||
ACTION_MARGIN
|
||||
this.theme.actionMargin
|
||||
);
|
||||
}
|
||||
|
||||
renderNoteOver({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
if(agents.length > 1) {
|
||||
const {left, right} = this.findExtremes(agents);
|
||||
|
@ -795,7 +598,7 @@ define([
|
|||
}
|
||||
|
||||
renderNoteLeft({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
const {left} = this.findExtremes(agents);
|
||||
const x1 = this.agentInfos.get(left).x - config.margin.right;
|
||||
|
@ -803,7 +606,7 @@ define([
|
|||
}
|
||||
|
||||
renderNoteRight({agents, mode, label}) {
|
||||
const config = NOTE[mode];
|
||||
const config = this.theme.note[mode];
|
||||
|
||||
const {right} = this.findExtremes(agents);
|
||||
const x0 = this.agentInfos.get(right).x + config.margin.left;
|
||||
|
@ -821,34 +624,35 @@ define([
|
|||
}
|
||||
|
||||
renderBlockBegin(scope) {
|
||||
this.currentY += BLOCK.margin.top;
|
||||
this.currentY += this.theme.block.margin.top;
|
||||
|
||||
scope.y = this.currentY;
|
||||
scope.first = true;
|
||||
}
|
||||
|
||||
renderSectionBegin(scope, {left, right}, {mode, label}) {
|
||||
const config = this.theme.block;
|
||||
const agentInfoL = this.agentInfos.get(left);
|
||||
const agentInfoR = this.agentInfos.get(right);
|
||||
|
||||
if(scope.first) {
|
||||
scope.first = false;
|
||||
} else {
|
||||
this.currentY += BLOCK.section.padding.bottom;
|
||||
this.currentY += config.section.padding.bottom;
|
||||
this.sections.appendChild(svg.make('line', Object.assign({
|
||||
'x1': agentInfoL.x,
|
||||
'y1': this.currentY,
|
||||
'x2': agentInfoR.x,
|
||||
'y2': this.currentY,
|
||||
}, BLOCK.separator.attrs)));
|
||||
}, config.separator.attrs)));
|
||||
}
|
||||
|
||||
const modeRender = SVGShapes.renderBoxedText(mode, {
|
||||
x: agentInfoL.x,
|
||||
y: this.currentY,
|
||||
padding: BLOCK.section.mode.padding,
|
||||
boxAttrs: BLOCK.section.mode.boxAttrs,
|
||||
labelAttrs: BLOCK.section.mode.labelAttrs,
|
||||
padding: config.section.mode.padding,
|
||||
boxAttrs: config.section.mode.boxAttrs,
|
||||
labelAttrs: config.section.mode.labelAttrs,
|
||||
boxLayer: this.blocks,
|
||||
labelLayer: this.actionLabels,
|
||||
});
|
||||
|
@ -856,16 +660,16 @@ define([
|
|||
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||
x: agentInfoL.x + modeRender.width,
|
||||
y: this.currentY,
|
||||
padding: BLOCK.section.label.padding,
|
||||
boxAttrs: BLOCK.section.label.maskAttrs,
|
||||
labelAttrs: BLOCK.section.label.labelAttrs,
|
||||
padding: config.section.label.padding,
|
||||
boxAttrs: config.section.label.maskAttrs,
|
||||
labelAttrs: config.section.label.labelAttrs,
|
||||
boxLayer: this.mask,
|
||||
labelLayer: this.actionLabels,
|
||||
});
|
||||
|
||||
this.currentY += (
|
||||
Math.max(modeRender.height, labelRender.height) +
|
||||
BLOCK.section.padding.top
|
||||
config.section.padding.top
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -873,7 +677,8 @@ define([
|
|||
}
|
||||
|
||||
renderBlockEnd(scope, {left, right}) {
|
||||
this.currentY += BLOCK.section.padding.bottom;
|
||||
const config = this.theme.block;
|
||||
this.currentY += config.section.padding.bottom;
|
||||
|
||||
const agentInfoL = this.agentInfos.get(left);
|
||||
const agentInfoR = this.agentInfos.get(right);
|
||||
|
@ -882,9 +687,9 @@ define([
|
|||
'y': scope.y,
|
||||
'width': agentInfoR.x - agentInfoL.x,
|
||||
'height': this.currentY - scope.y,
|
||||
}, BLOCK.boxAttrs)));
|
||||
}, config.boxAttrs)));
|
||||
|
||||
this.currentY += BLOCK.margin.bottom + ACTION_MARGIN;
|
||||
this.currentY += config.margin.bottom + this.theme.actionMargin;
|
||||
}
|
||||
|
||||
addAction(stage) {
|
||||
|
@ -924,15 +729,16 @@ define([
|
|||
updateBounds(stagesHeight) {
|
||||
const cx = (this.minX + this.maxX) / 2;
|
||||
const titleY = ((this.title.height > 0) ?
|
||||
(-TITLE_MARGIN - this.title.height) : 0
|
||||
(-this.theme.titleMargin - this.title.height) : 0
|
||||
);
|
||||
this.title.reanchor(cx, titleY);
|
||||
this.title.set({x: cx, y: titleY});
|
||||
|
||||
const halfTitleWidth = this.title.width / 2;
|
||||
const x0 = Math.min(this.minX, cx - halfTitleWidth) - OUTER_MARGIN;
|
||||
const x1 = Math.max(this.maxX, cx + halfTitleWidth) + OUTER_MARGIN;
|
||||
const y0 = titleY - OUTER_MARGIN;
|
||||
const y1 = stagesHeight + OUTER_MARGIN;
|
||||
const margin = this.theme.outerMargin;
|
||||
const x0 = Math.min(this.minX, cx - halfTitleWidth) - margin;
|
||||
const x1 = Math.max(this.maxX, cx + halfTitleWidth) + margin;
|
||||
const y0 = titleY - margin;
|
||||
const y1 = stagesHeight + margin;
|
||||
|
||||
this.base.setAttribute('viewBox', (
|
||||
x0 + ' ' + y0 + ' ' +
|
||||
|
@ -942,7 +748,17 @@ define([
|
|||
this.height = (y1 - y0);
|
||||
}
|
||||
|
||||
render({meta, agents, stages}) {
|
||||
setTheme(theme) {
|
||||
if(this.theme === theme) {
|
||||
return;
|
||||
}
|
||||
this.theme = theme;
|
||||
if(this.currentSequence) {
|
||||
this.render(this.currentSequence);
|
||||
}
|
||||
}
|
||||
|
||||
render(sequence) {
|
||||
svg.empty(this.agentLines);
|
||||
svg.empty(this.mask);
|
||||
svg.empty(this.blocks);
|
||||
|
@ -950,20 +766,27 @@ define([
|
|||
svg.empty(this.actionShapes);
|
||||
svg.empty(this.actionLabels);
|
||||
|
||||
this.title.setText(meta.title);
|
||||
this.title.set({
|
||||
attrs: this.theme.titleAttrs,
|
||||
text: sequence.meta.title,
|
||||
});
|
||||
|
||||
this.minX = 0;
|
||||
this.maxX = 0;
|
||||
this.buildAgentInfos(agents, stages);
|
||||
this.buildAgentInfos(sequence.agents, sequence.stages);
|
||||
|
||||
this.currentY = 0;
|
||||
traverse(stages, this.renderTraversalFns);
|
||||
traverse(sequence.stages, this.renderTraversalFns);
|
||||
|
||||
const stagesHeight = Math.max(this.currentY - ACTION_MARGIN, 0);
|
||||
const stagesHeight = Math.max(
|
||||
this.currentY - this.theme.actionMargin,
|
||||
0
|
||||
);
|
||||
this.updateBounds(stagesHeight);
|
||||
|
||||
this.sizer.resetCache();
|
||||
this.sizer.detach();
|
||||
this.currentSequence = sequence;
|
||||
}
|
||||
|
||||
getAgentX(name) {
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
defineDescribe('Sequence Renderer', ['./Renderer'], (Renderer) => {
|
||||
defineDescribe('Sequence Renderer', [
|
||||
'./Renderer',
|
||||
'./themes/Basic',
|
||||
], (
|
||||
Renderer,
|
||||
Theme
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
let renderer = null;
|
||||
|
||||
beforeEach(() => {
|
||||
renderer = new Renderer();
|
||||
renderer = new Renderer(new Theme());
|
||||
document.body.appendChild(renderer.svg());
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
define([
|
||||
'core/ArrayUtilities',
|
||||
'svg/SVGUtilities',
|
||||
'svg/SVGTextBlock',
|
||||
'svg/SVGShapes',
|
||||
], (
|
||||
array,
|
||||
svg,
|
||||
SVGTextBlock,
|
||||
SVGShapes
|
||||
) => {
|
||||
'use strict';
|
||||
|
||||
const LINE_HEIGHT = 1.3;
|
||||
|
||||
const SETTINGS = {
|
||||
titleMargin: 10,
|
||||
outerMargin: 5,
|
||||
agentMargin: 10,
|
||||
actionMargin: 5,
|
||||
|
||||
agentCap: {
|
||||
box: {
|
||||
padding: {
|
||||
top: 5,
|
||||
left: 10,
|
||||
right: 10,
|
||||
bottom: 5,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 12,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
},
|
||||
},
|
||||
cross: {
|
||||
size: 20,
|
||||
attrs: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
attrs: {
|
||||
'fill': '#000000',
|
||||
'height': 5,
|
||||
},
|
||||
},
|
||||
none: {
|
||||
height: 10,
|
||||
},
|
||||
},
|
||||
|
||||
connect: {
|
||||
lineAttrs: {
|
||||
'solid': {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
'dash': {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'stroke-dasharray': '4, 2',
|
||||
},
|
||||
},
|
||||
arrow: {
|
||||
width: 4,
|
||||
height: 8,
|
||||
attrs: {
|
||||
'fill': '#000000',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'stroke-linejoin': 'miter',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
padding: 6,
|
||||
margin: {top: 2, bottom: 1},
|
||||
attrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
},
|
||||
},
|
||||
mask: {
|
||||
padding: {
|
||||
top: 0,
|
||||
left: 3,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
maskAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
block: {
|
||||
margin: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1.5,
|
||||
'rx': 2,
|
||||
'ry': 2,
|
||||
},
|
||||
section: {
|
||||
padding: {
|
||||
top: 3,
|
||||
bottom: 2,
|
||||
},
|
||||
mode: {
|
||||
padding: {
|
||||
top: 1,
|
||||
left: 3,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
boxAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'rx': 2,
|
||||
'ry': 2,
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-weight': 'bold',
|
||||
'font-size': 9,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'left',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
padding: {
|
||||
top: 1,
|
||||
left: 5,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
},
|
||||
maskAttrs: {
|
||||
'fill': '#FFFFFF',
|
||||
},
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'left',
|
||||
},
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
attrs: {
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1.5,
|
||||
'stroke-dasharray': '4, 2',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
note: {
|
||||
'note': {
|
||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||
padding: {top: 5, left: 5, right: 10, bottom: 5},
|
||||
overlap: {left: 10, right: 10},
|
||||
boxRenderer: SVGShapes.renderNote.bind(null, {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
}, {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
}),
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
'state': {
|
||||
margin: {top: 0, left: 5, right: 5, bottom: 0},
|
||||
padding: {top: 7, left: 7, right: 7, bottom: 7},
|
||||
overlap: {left: 10, right: 10},
|
||||
boxRenderer: SVGShapes.renderBox.bind(null, {
|
||||
'fill': '#FFFFFF',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
'rx': 10,
|
||||
'ry': 10,
|
||||
}),
|
||||
labelAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 8,
|
||||
'line-height': LINE_HEIGHT,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
titleAttrs: {
|
||||
'font-family': 'sans-serif',
|
||||
'font-size': 20,
|
||||
'line-height': LINE_HEIGHT,
|
||||
'text-anchor': 'middle',
|
||||
'class': 'title',
|
||||
},
|
||||
|
||||
agentLineAttrs: {
|
||||
'fill': 'none',
|
||||
'stroke': '#000000',
|
||||
'stroke-width': 1,
|
||||
},
|
||||
};
|
||||
|
||||
return class Theme {
|
||||
constructor() {
|
||||
Object.assign(this, SETTINGS);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
defineDescribe('Basic Theme', ['./Basic'], (Theme) => {
|
||||
'use strict';
|
||||
|
||||
it('contains settings for the theme', () => {
|
||||
const theme = new Theme();
|
||||
expect(theme.outerMargin).toEqual(5);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,11 @@
|
|||
define([
|
||||
'core/ArrayUtilities_spec',
|
||||
'svg/SVGUtilities_spec',
|
||||
'svg/SVGTextBlock_spec',
|
||||
'svg/SVGShapes_spec',
|
||||
'interface/Interface_spec',
|
||||
'sequence/Parser_spec',
|
||||
'sequence/Generator_spec',
|
||||
'sequence/Renderer_spec',
|
||||
'sequence/ArrayUtilities_spec',
|
||||
'sequence/SVGUtilities_spec',
|
||||
'sequence/SVGTextBlock_spec',
|
||||
'sequence/SVGShapes_spec',
|
||||
'sequence/themes/Basic_spec',
|
||||
]);
|
||||
|
|
|
@ -65,7 +65,8 @@ define(['./SVGUtilities', './SVGTextBlock'], (svg, SVGTextBlock) => {
|
|||
break;
|
||||
}
|
||||
|
||||
const label = new SVGTextBlock(labelLayer, labelAttrs, {
|
||||
const label = new SVGTextBlock(labelLayer, {
|
||||
attrs: labelAttrs,
|
||||
text,
|
||||
x: anchorX,
|
||||
y: y + padding.top,
|
|
@ -65,9 +65,9 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
|||
boxLayer: o,
|
||||
labelLayer: o,
|
||||
});
|
||||
expect(rendered.label.text).toEqual('foo');
|
||||
expect(rendered.label.x).toEqual(5);
|
||||
expect(rendered.label.y).toEqual(10);
|
||||
expect(rendered.label.state.text).toEqual('foo');
|
||||
expect(rendered.label.state.x).toEqual(5);
|
||||
expect(rendered.label.state.y).toEqual(10);
|
||||
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
||||
});
|
||||
|
|
@ -10,37 +10,37 @@ define(['./SVGUtilities'], (svg) => {
|
|||
};
|
||||
}
|
||||
|
||||
function merge(state, newState) {
|
||||
for(let k in state) {
|
||||
if(state.hasOwnProperty(k)) {
|
||||
if(newState[k] !== null && newState[k] !== undefined) {
|
||||
state[k] = newState[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SVGTextBlock {
|
||||
constructor(
|
||||
container,
|
||||
attrs,
|
||||
{text = '', x = 0, y = 0} = {}
|
||||
) {
|
||||
constructor(container, initialState = {}) {
|
||||
this.container = container;
|
||||
this.attrs = attrs;
|
||||
this.text = '';
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.state = {
|
||||
attrs: {},
|
||||
text: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
this.nodes = [];
|
||||
this.setText(text);
|
||||
}
|
||||
|
||||
_updateY() {
|
||||
const {size, lineHeight} = fontDetails(this.attrs);
|
||||
this.nodes.forEach(({element}, i) => {
|
||||
element.setAttribute('y', this.y + i * lineHeight + size);
|
||||
});
|
||||
this.height = lineHeight * this.nodes.length;
|
||||
this.set(initialState);
|
||||
}
|
||||
|
||||
_rebuildNodes(count) {
|
||||
if(count === this.nodes.length) {
|
||||
return;
|
||||
}
|
||||
if(count > this.nodes.length) {
|
||||
const attrs = Object.assign({'x': this.x}, this.attrs);
|
||||
const attrs = Object.assign({
|
||||
'x': this.state.x,
|
||||
}, this.state.attrs);
|
||||
|
||||
while(this.nodes.length < count) {
|
||||
const element = svg.make('text', attrs);
|
||||
const text = svg.makeText();
|
||||
|
@ -54,29 +54,23 @@ define(['./SVGUtilities'], (svg) => {
|
|||
this.container.removeChild(element);
|
||||
}
|
||||
}
|
||||
this._updateY();
|
||||
}
|
||||
|
||||
firstLine() {
|
||||
if(this.nodes.length > 0) {
|
||||
return this.nodes[0].element;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
_reset() {
|
||||
this._rebuildNodes(0);
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
}
|
||||
|
||||
setText(newText) {
|
||||
if(newText === this.text) {
|
||||
_renderText() {
|
||||
if(!this.state.text) {
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
if(!newText) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
this.text = newText;
|
||||
const lines = this.text.split('\n');
|
||||
|
||||
const lines = this.state.text.split('\n');
|
||||
this._rebuildNodes(lines.length);
|
||||
|
||||
let maxWidth = 0;
|
||||
this.nodes.forEach(({text, element}, i) => {
|
||||
if(text.nodeValue !== lines[i]) {
|
||||
|
@ -87,25 +81,50 @@ define(['./SVGUtilities'], (svg) => {
|
|||
this.width = maxWidth;
|
||||
}
|
||||
|
||||
reanchor(newX, newY) {
|
||||
if(newX !== this.x) {
|
||||
this.x = newX;
|
||||
this.nodes.forEach(({element}) => {
|
||||
element.setAttribute('x', this.x);
|
||||
});
|
||||
}
|
||||
_updateX() {
|
||||
this.nodes.forEach(({element}) => {
|
||||
element.setAttribute('x', this.state.x);
|
||||
});
|
||||
}
|
||||
|
||||
if(newY !== this.y) {
|
||||
this.y = newY;
|
||||
this._updateY();
|
||||
_updateY() {
|
||||
const {size, lineHeight} = fontDetails(this.state.attrs);
|
||||
this.nodes.forEach(({element}, i) => {
|
||||
element.setAttribute('y', this.state.y + i * lineHeight + size);
|
||||
});
|
||||
this.height = lineHeight * this.nodes.length;
|
||||
}
|
||||
|
||||
firstLine() {
|
||||
if(this.nodes.length > 0) {
|
||||
return this.nodes[0].element;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._rebuildNodes(0);
|
||||
this.text = '';
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
set(newState) {
|
||||
const oldState = Object.assign({}, this.state);
|
||||
merge(this.state, newState);
|
||||
|
||||
if(this.state.attrs !== oldState.attrs) {
|
||||
this._reset();
|
||||
oldState.text = '';
|
||||
}
|
||||
|
||||
const oldNodes = this.nodes.length;
|
||||
|
||||
if(this.state.text !== oldState.text) {
|
||||
this._renderText();
|
||||
}
|
||||
|
||||
if(this.state.x !== oldState.x) {
|
||||
this._updateX();
|
||||
}
|
||||
|
||||
if(this.state.y !== oldState.y || this.nodes.length !== oldNodes) {
|
||||
this._updateY();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ defineDescribe('SVGTextBlock', [
|
|||
beforeEach(() => {
|
||||
hold = svg.makeContainer();
|
||||
document.body.appendChild(hold);
|
||||
block = new SVGTextBlock(hold, attrs);
|
||||
block = new SVGTextBlock(hold, {attrs});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -23,52 +23,60 @@ defineDescribe('SVGTextBlock', [
|
|||
|
||||
describe('constructor', () => {
|
||||
it('defaults to blank text at 0, 0', () => {
|
||||
expect(block.text).toEqual('');
|
||||
expect(block.x).toEqual(0);
|
||||
expect(block.y).toEqual(0);
|
||||
expect(block.state.text).toEqual('');
|
||||
expect(block.state.x).toEqual(0);
|
||||
expect(block.state.y).toEqual(0);
|
||||
expect(hold.children.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('does not explode if given no setup', () => {
|
||||
block = new SVGTextBlock(hold);
|
||||
expect(block.state.text).toEqual('');
|
||||
expect(block.state.x).toEqual(0);
|
||||
expect(block.state.y).toEqual(0);
|
||||
expect(hold.children.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('adds the given text if specified', () => {
|
||||
block = new SVGTextBlock(hold, attrs, {text: 'abc'});
|
||||
expect(block.text).toEqual('abc');
|
||||
block = new SVGTextBlock(hold, {attrs, text: 'abc'});
|
||||
expect(block.state.text).toEqual('abc');
|
||||
expect(hold.children.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('uses the given coordinates if specified', () => {
|
||||
block = new SVGTextBlock(hold, attrs, {x: 5, y: 7});
|
||||
expect(block.x).toEqual(5);
|
||||
expect(block.y).toEqual(7);
|
||||
block = new SVGTextBlock(hold, {attrs, x: 5, y: 7});
|
||||
expect(block.state.x).toEqual(5);
|
||||
expect(block.state.y).toEqual(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.setText', () => {
|
||||
describe('.set', () => {
|
||||
it('sets the text to the given content', () => {
|
||||
block.setText('foo');
|
||||
expect(block.text).toEqual('foo');
|
||||
block.set({text: 'foo'});
|
||||
expect(block.state.text).toEqual('foo');
|
||||
expect(hold.children.length).toEqual(1);
|
||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||
});
|
||||
|
||||
it('renders multiline text', () => {
|
||||
block.setText('foo\nbar');
|
||||
block.set({text: 'foo\nbar'});
|
||||
expect(hold.children.length).toEqual(2);
|
||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||
expect(hold.children[1].innerHTML).toEqual('bar');
|
||||
});
|
||||
|
||||
it('populates width and height with the size of the text', () => {
|
||||
block.setText('foo\nbar');
|
||||
block.set({text: 'foo\nbar'});
|
||||
expect(block.width).toBeGreaterThan(0);
|
||||
expect(block.height).toEqual(30);
|
||||
});
|
||||
|
||||
it('re-uses text nodes when possible, adding more if needed', () => {
|
||||
block.setText('foo\nbar');
|
||||
block.set({text: 'foo\nbar'});
|
||||
const line0 = hold.children[0];
|
||||
const line1 = hold.children[1];
|
||||
|
||||
block.setText('zig\nzag\nbaz');
|
||||
block.set({text: 'zig\nzag\nbaz'});
|
||||
|
||||
expect(hold.children.length).toEqual(3);
|
||||
expect(hold.children[0]).toEqual(line0);
|
||||
|
@ -79,10 +87,10 @@ defineDescribe('SVGTextBlock', [
|
|||
});
|
||||
|
||||
it('re-uses text nodes when possible, removing extra if needed', () => {
|
||||
block.setText('foo\nbar');
|
||||
block.set({text: 'foo\nbar'});
|
||||
const line0 = hold.children[0];
|
||||
|
||||
block.setText('zig');
|
||||
block.set({text: 'zig'});
|
||||
|
||||
expect(hold.children.length).toEqual(1);
|
||||
expect(hold.children[0]).toEqual(line0);
|
||||
|
@ -90,7 +98,7 @@ defineDescribe('SVGTextBlock', [
|
|||
});
|
||||
|
||||
it('positions text nodes and applies attributes', () => {
|
||||
block.setText('foo\nbar');
|
||||
block.set({text: 'foo\nbar'});
|
||||
expect(hold.children.length).toEqual(2);
|
||||
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
||||
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
||||
|
@ -99,25 +107,21 @@ defineDescribe('SVGTextBlock', [
|
|||
expect(hold.children[1].getAttribute('y')).toEqual('25');
|
||||
expect(hold.children[1].getAttribute('font-size')).toEqual('10');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.reanchor', () => {
|
||||
it('moves all nodes', () => {
|
||||
block.setText('foo\nbaz');
|
||||
block.reanchor(5, 7);
|
||||
block.set({text: 'foo\nbaz'});
|
||||
block.set({x: 5, y: 7});
|
||||
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
||||
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
||||
expect(hold.children[1].getAttribute('x')).toEqual('5');
|
||||
expect(hold.children[1].getAttribute('y')).toEqual('32');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.clear', () => {
|
||||
it('resets the text empty', () => {
|
||||
block.setText('foo\nbaz');
|
||||
block.setText('');
|
||||
it('clears if the text is empty', () => {
|
||||
block.set({text: 'foo\nbaz'});
|
||||
block.set({text: ''});
|
||||
expect(hold.children.length).toEqual(0);
|
||||
expect(block.text).toEqual('');
|
||||
expect(block.state.text).toEqual('');
|
||||
expect(block.width).toEqual(0);
|
||||
expect(block.height).toEqual(0);
|
||||
});
|
Loading…
Reference in New Issue