Refactor to enable formatted text everywhere, and make 'agent' dichotomy in generator clearer through naming [#29]
This commit is contained in:
parent
2b5f7eea2b
commit
0f22dc7f94
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -21,35 +21,52 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
AgentState.LOCKED = new AgentState({locked: true});
|
AgentState.LOCKED = new AgentState({locked: true});
|
||||||
AgentState.DEFAULT = new AgentState();
|
AgentState.DEFAULT = new AgentState();
|
||||||
|
|
||||||
const Agent = {
|
// Agent from Parser: {name, flags}
|
||||||
|
const PAgent = {
|
||||||
equals: (a, b) => {
|
equals: (a, b) => {
|
||||||
return a.name === b.name;
|
return a.name === b.name;
|
||||||
},
|
},
|
||||||
make: (name, {anchorRight = false} = {}) => {
|
|
||||||
return {name, anchorRight};
|
|
||||||
},
|
|
||||||
getName: (agent) => {
|
|
||||||
return agent.name;
|
|
||||||
},
|
|
||||||
hasFlag: (flag, has = true) => {
|
hasFlag: (flag, has = true) => {
|
||||||
return (agent) => (agent.flags.includes(flag) === has);
|
return (pAgent) => (pAgent.flags.includes(flag) === has);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Agent from Generator: {id, formattedLabel, anchorRight}
|
||||||
|
const GAgent = {
|
||||||
|
equals: (a, b) => {
|
||||||
|
return a.id === b.id;
|
||||||
|
},
|
||||||
|
make: (id, {anchorRight = false} = {}) => {
|
||||||
|
return {id, anchorRight};
|
||||||
|
},
|
||||||
|
indexOf: (list, gAgent) => {
|
||||||
|
return array.indexOf(list, gAgent, GAgent.equals);
|
||||||
|
},
|
||||||
|
hasIntersection: (a, b) => {
|
||||||
|
return array.hasIntersection(a, b, GAgent.equals);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const NOTE_DEFAULT_G_AGENTS = {
|
||||||
|
'note over': [GAgent.make('['), GAgent.make(']')],
|
||||||
|
'note left': [GAgent.make('[')],
|
||||||
|
'note right': [GAgent.make(']')],
|
||||||
|
};
|
||||||
|
|
||||||
const MERGABLE = {
|
const MERGABLE = {
|
||||||
'agent begin': {
|
'agent begin': {
|
||||||
check: ['mode'],
|
check: ['mode'],
|
||||||
merge: ['agentNames'],
|
merge: ['agentIDs'],
|
||||||
siblings: new Set(['agent highlight']),
|
siblings: new Set(['agent highlight']),
|
||||||
},
|
},
|
||||||
'agent end': {
|
'agent end': {
|
||||||
check: ['mode'],
|
check: ['mode'],
|
||||||
merge: ['agentNames'],
|
merge: ['agentIDs'],
|
||||||
siblings: new Set(['agent highlight']),
|
siblings: new Set(['agent highlight']),
|
||||||
},
|
},
|
||||||
'agent highlight': {
|
'agent highlight': {
|
||||||
check: ['highlighted'],
|
check: ['highlighted'],
|
||||||
merge: ['agentNames'],
|
merge: ['agentIDs'],
|
||||||
siblings: new Set(['agent begin', 'agent end']),
|
siblings: new Set(['agent begin', 'agent end']),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -183,39 +200,33 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addBounds(target, agentL, agentR, involvedAgents = null) {
|
function addBounds(allGAgents, gAgentL, gAgentR, involvedGAgents = null) {
|
||||||
array.remove(target, agentL, Agent.equals);
|
array.remove(allGAgents, gAgentL, GAgent.equals);
|
||||||
array.remove(target, agentR, Agent.equals);
|
array.remove(allGAgents, gAgentR, GAgent.equals);
|
||||||
|
|
||||||
let indexL = 0;
|
let indexL = 0;
|
||||||
let indexR = target.length;
|
let indexR = allGAgents.length;
|
||||||
if(involvedAgents) {
|
if(involvedGAgents) {
|
||||||
const found = (involvedAgents
|
const found = (involvedGAgents
|
||||||
.map((agent) => array.indexOf(target, agent, Agent.equals))
|
.map((gAgent) => GAgent.indexOf(allGAgents, gAgent))
|
||||||
.filter((p) => (p !== -1))
|
.filter((p) => (p !== -1))
|
||||||
);
|
);
|
||||||
indexL = found.reduce((a, b) => Math.min(a, b), target.length);
|
indexL = found.reduce((a, b) => Math.min(a, b), allGAgents.length);
|
||||||
indexR = found.reduce((a, b) => Math.max(a, b), indexL) + 1;
|
indexR = found.reduce((a, b) => Math.max(a, b), indexL) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
target.splice(indexL, 0, agentL);
|
allGAgents.splice(indexL, 0, gAgentL);
|
||||||
target.splice(indexR + 1, 0, agentR);
|
allGAgents.splice(indexR + 1, 0, gAgentR);
|
||||||
|
|
||||||
return {indexL, indexR: indexR + 1};
|
return {indexL, indexR: indexR + 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOTE_DEFAULT_AGENTS = {
|
|
||||||
'note over': [{name: '[', flags: []}, {name: ']', flags: []}],
|
|
||||||
'note left': [{name: '[', flags: []}],
|
|
||||||
'note right': [{name: ']', flags: []}],
|
|
||||||
};
|
|
||||||
|
|
||||||
return class Generator {
|
return class Generator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.agentStates = new Map();
|
this.agentStates = new Map();
|
||||||
this.agentAliases = new Map();
|
this.agentAliases = new Map();
|
||||||
this.activeGroups = new Map();
|
this.activeGroups = new Map();
|
||||||
this.agents = [];
|
this.gAgents = [];
|
||||||
this.labelPattern = null;
|
this.labelPattern = null;
|
||||||
this.blockCount = 0;
|
this.blockCount = 0;
|
||||||
this.nesting = [];
|
this.nesting = [];
|
||||||
|
@ -240,13 +251,13 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
'note right': this.handleNote.bind(this),
|
'note right': this.handleNote.bind(this),
|
||||||
'note between': this.handleNote.bind(this),
|
'note between': this.handleNote.bind(this),
|
||||||
};
|
};
|
||||||
this.expandGroupedAgent = this.expandGroupedAgent.bind(this);
|
this.expandGroupedGAgent = this.expandGroupedGAgent.bind(this);
|
||||||
this.handleStage = this.handleStage.bind(this);
|
this.handleStage = this.handleStage.bind(this);
|
||||||
this.convertAgent = this.convertAgent.bind(this);
|
this.toGAgent = this.toGAgent.bind(this);
|
||||||
this.endGroup = this.endGroup.bind(this);
|
this.endGroup = this.endGroup.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertAgent({alias, name}) {
|
toGAgent({alias, name}) {
|
||||||
if(alias) {
|
if(alias) {
|
||||||
if(this.agentAliases.has(name)) {
|
if(this.agentAliases.has(name)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -256,7 +267,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const old = this.agentAliases.get(alias);
|
const old = this.agentAliases.get(alias);
|
||||||
if(
|
if(
|
||||||
(old && old !== alias) ||
|
(old && old !== alias) ||
|
||||||
this.agents.some((agent) => (agent.name === alias))
|
this.gAgents.some((gAgent) => (gAgent.id === alias))
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot use ' + alias +
|
'Cannot use ' + alias +
|
||||||
|
@ -265,7 +276,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
this.agentAliases.set(alias, name);
|
this.agentAliases.set(alias, name);
|
||||||
}
|
}
|
||||||
return Agent.make(this.agentAliases.get(name) || name);
|
return GAgent.make(this.agentAliases.get(name) || name);
|
||||||
}
|
}
|
||||||
|
|
||||||
addStage(stage, isVisible = true) {
|
addStage(stage, isVisible = true) {
|
||||||
|
@ -300,138 +311,139 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
defineAgents(colAgents) {
|
defineGAgents(gAgents) {
|
||||||
array.mergeSets(this.currentNest.agents, colAgents, Agent.equals);
|
array.mergeSets(this.currentNest.gAgents, gAgents, GAgent.equals);
|
||||||
array.mergeSets(this.agents, colAgents, Agent.equals);
|
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAgentState(agent) {
|
getGAgentState(gAgent) {
|
||||||
return this.agentStates.get(agent.name) || AgentState.DEFAULT;
|
return this.agentStates.get(gAgent.id) || AgentState.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAgentState(agent, change) {
|
updateGAgentState(gAgent, change) {
|
||||||
const state = this.agentStates.get(agent.name);
|
const state = this.agentStates.get(gAgent.id);
|
||||||
if(state) {
|
if(state) {
|
||||||
Object.assign(state, change);
|
Object.assign(state, change);
|
||||||
} else {
|
} else {
|
||||||
this.agentStates.set(agent.name, new AgentState(change));
|
this.agentStates.set(gAgent.id, new AgentState(change));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAgents(agents, {
|
replaceGAgentState(gAgent, state) {
|
||||||
|
this.agentStates.set(gAgent.id, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
validateGAgents(gAgents, {
|
||||||
allowGrouped = false,
|
allowGrouped = false,
|
||||||
rejectGrouped = false,
|
rejectGrouped = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
agents.forEach((agent) => {
|
gAgents.forEach((gAgent) => {
|
||||||
const state = this.getAgentState(agent);
|
const state = this.getGAgentState(gAgent);
|
||||||
if(state.covered) {
|
if(state.covered) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Agent ' + agent.name + ' is hidden behind group'
|
'Agent ' + gAgent.id + ' is hidden behind group'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(rejectGrouped && state.group !== null) {
|
if(rejectGrouped && state.group !== null) {
|
||||||
throw new Error('Agent ' + agent.name + ' is in a group');
|
throw new Error('Agent ' + gAgent.id + ' is in a group');
|
||||||
}
|
}
|
||||||
if(state.blocked && (!allowGrouped || state.group === null)) {
|
if(state.blocked && (!allowGrouped || state.group === null)) {
|
||||||
throw new Error('Duplicate agent name: ' + agent.name);
|
throw new Error('Duplicate agent name: ' + gAgent.id);
|
||||||
}
|
}
|
||||||
if(agent.name.startsWith('__')) {
|
if(gAgent.id.startsWith('__')) {
|
||||||
throw new Error(agent.name + ' is a reserved name');
|
throw new Error(gAgent.id + ' is a reserved name');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentVis(colAgents, visible, mode, checked = false) {
|
setGAgentVis(gAgents, visible, mode, checked = false) {
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const filteredAgents = colAgents.filter((agent) => {
|
const filteredGAgents = gAgents.filter((gAgent) => {
|
||||||
if(seen.has(agent.name)) {
|
if(seen.has(gAgent.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
seen.add(agent.name);
|
seen.add(gAgent.id);
|
||||||
const state = this.getAgentState(agent);
|
const state = this.getGAgentState(gAgent);
|
||||||
if(state.locked || state.blocked) {
|
if(state.locked || state.blocked) {
|
||||||
if(checked) {
|
if(checked) {
|
||||||
throw new Error(
|
throw new Error('Cannot begin/end agent: ' + gAgent.id);
|
||||||
'Cannot begin/end agent: ' + agent.name
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state.visible !== visible;
|
return state.visible !== visible;
|
||||||
});
|
});
|
||||||
if(filteredAgents.length === 0) {
|
if(filteredGAgents.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
filteredAgents.forEach((agent) => {
|
filteredGAgents.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {visible});
|
this.updateGAgentState(gAgent, {visible});
|
||||||
});
|
});
|
||||||
this.defineAgents(filteredAgents);
|
this.defineGAgents(filteredGAgents);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: (visible ? 'agent begin' : 'agent end'),
|
type: (visible ? 'agent begin' : 'agent end'),
|
||||||
agentNames: filteredAgents.map(Agent.getName),
|
agentIDs: filteredGAgents.map((gAgent) => gAgent.id),
|
||||||
mode,
|
mode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentHighlight(colAgents, highlighted, checked = false) {
|
setGAgentHighlight(gAgents, highlighted, checked = false) {
|
||||||
const filteredAgents = colAgents.filter((agent) => {
|
const filteredGAgents = gAgents.filter((gAgent) => {
|
||||||
const state = this.getAgentState(agent);
|
const state = this.getGAgentState(gAgent);
|
||||||
if(state.locked || state.blocked) {
|
if(state.locked || state.blocked) {
|
||||||
if(checked) {
|
if(checked) {
|
||||||
throw new Error(
|
throw new Error('Cannot highlight agent: ' + gAgent.id);
|
||||||
'Cannot highlight agent: ' + agent.name
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state.visible && (state.highlighted !== highlighted);
|
return state.visible && (state.highlighted !== highlighted);
|
||||||
});
|
});
|
||||||
if(filteredAgents.length === 0) {
|
if(filteredGAgents.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
filteredAgents.forEach((agent) => {
|
filteredGAgents.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {highlighted});
|
this.updateGAgentState(gAgent, {highlighted});
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'agent highlight',
|
type: 'agent highlight',
|
||||||
agentNames: filteredAgents.map(Agent.getName),
|
agentIDs: filteredGAgents.map((gAgent) => gAgent.id),
|
||||||
highlighted,
|
highlighted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
beginNested(mode, label, name, ln) {
|
beginNested(blockType, {tag, label, name, ln}) {
|
||||||
const leftAgent = Agent.make(name + '[', {anchorRight: true});
|
const leftGAgent = GAgent.make(name + '[', {anchorRight: true});
|
||||||
const rightAgent = Agent.make(name + ']');
|
const rightGAgent = GAgent.make(name + ']');
|
||||||
const agents = [leftAgent, rightAgent];
|
const gAgents = [leftGAgent, rightGAgent];
|
||||||
const stages = [];
|
const stages = [];
|
||||||
this.currentSection = {
|
this.currentSection = {
|
||||||
header: {
|
header: {
|
||||||
type: 'block begin',
|
type: 'block begin',
|
||||||
mode,
|
blockType,
|
||||||
label,
|
tag: this.textFormatter(tag),
|
||||||
left: leftAgent.name,
|
label: this.textFormatter(label),
|
||||||
right: rightAgent.name,
|
left: leftGAgent.id,
|
||||||
|
right: rightGAgent.id,
|
||||||
ln,
|
ln,
|
||||||
},
|
},
|
||||||
stages,
|
stages,
|
||||||
};
|
};
|
||||||
this.currentNest = {
|
this.currentNest = {
|
||||||
mode,
|
blockType,
|
||||||
agents,
|
gAgents,
|
||||||
leftAgent,
|
leftGAgent,
|
||||||
rightAgent,
|
rightGAgent,
|
||||||
hasContent: false,
|
hasContent: false,
|
||||||
sections: [this.currentSection],
|
sections: [this.currentSection],
|
||||||
};
|
};
|
||||||
this.agentStates.set(leftAgent.name, AgentState.LOCKED);
|
this.replaceGAgentState(leftGAgent, AgentState.LOCKED);
|
||||||
this.agentStates.set(rightAgent.name, AgentState.LOCKED);
|
this.replaceGAgentState(rightGAgent, AgentState.LOCKED);
|
||||||
this.nesting.push(this.currentNest);
|
this.nesting.push(this.currentNest);
|
||||||
|
|
||||||
return {agents, stages};
|
return {gAgents, stages};
|
||||||
}
|
}
|
||||||
|
|
||||||
nextBlockName() {
|
nextBlockName() {
|
||||||
|
@ -440,25 +452,31 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockBegin({ln, mode, label}) {
|
handleBlockBegin({ln, blockType, tag, label}) {
|
||||||
this.beginNested(mode, label, this.nextBlockName(), ln);
|
this.beginNested(blockType, {
|
||||||
|
tag,
|
||||||
|
label,
|
||||||
|
name: this.nextBlockName(),
|
||||||
|
ln,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlockSplit({ln, mode, label}) {
|
handleBlockSplit({ln, blockType, tag, label}) {
|
||||||
if(this.currentNest.mode !== 'if') {
|
if(this.currentNest.blockType !== 'if') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Invalid block nesting ("else" inside ' +
|
'Invalid block nesting ("else" inside ' +
|
||||||
this.currentNest.mode + ')'
|
this.currentNest.blockType + ')'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
optimiseStages(this.currentSection.stages);
|
optimiseStages(this.currentSection.stages);
|
||||||
this.currentSection = {
|
this.currentSection = {
|
||||||
header: {
|
header: {
|
||||||
type: 'block split',
|
type: 'block split',
|
||||||
mode,
|
blockType,
|
||||||
label,
|
tag: this.textFormatter(tag),
|
||||||
left: this.currentNest.leftAgent.name,
|
label: this.textFormatter(label),
|
||||||
right: this.currentNest.rightAgent.name,
|
left: this.currentNest.leftGAgent.id,
|
||||||
|
right: this.currentNest.rightGAgent.id,
|
||||||
ln,
|
ln,
|
||||||
},
|
},
|
||||||
stages: [],
|
stages: [],
|
||||||
|
@ -476,12 +494,12 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
this.currentSection = array.last(this.currentNest.sections);
|
this.currentSection = array.last(this.currentNest.sections);
|
||||||
|
|
||||||
if(nested.hasContent) {
|
if(nested.hasContent) {
|
||||||
this.defineAgents(nested.agents);
|
this.defineGAgents(nested.gAgents);
|
||||||
addBounds(
|
addBounds(
|
||||||
this.agents,
|
this.gAgents,
|
||||||
nested.leftAgent,
|
nested.leftGAgent,
|
||||||
nested.rightAgent,
|
nested.rightGAgent,
|
||||||
nested.agents
|
nested.gAgents
|
||||||
);
|
);
|
||||||
nested.sections.forEach((section) => {
|
nested.sections.forEach((section) => {
|
||||||
this.currentSection.stages.push(section.header);
|
this.currentSection.stages.push(section.header);
|
||||||
|
@ -489,70 +507,71 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
});
|
});
|
||||||
this.addStage({
|
this.addStage({
|
||||||
type: 'block end',
|
type: 'block end',
|
||||||
left: nested.leftAgent.name,
|
left: nested.leftGAgent.id,
|
||||||
right: nested.rightAgent.name,
|
right: nested.rightGAgent.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Empty block');
|
throw new Error('Empty block');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeGroupDetails(agents, alias) {
|
makeGroupDetails(pAgents, alias) {
|
||||||
const colAgents = agents.map(this.convertAgent);
|
const gAgents = pAgents.map(this.toGAgent);
|
||||||
this.validateAgents(colAgents, {rejectGrouped: true});
|
this.validateGAgents(gAgents, {rejectGrouped: true});
|
||||||
if(this.agentStates.has(alias)) {
|
if(this.agentStates.has(alias)) {
|
||||||
throw new Error('Duplicate agent name: ' + alias);
|
throw new Error('Duplicate agent name: ' + alias);
|
||||||
}
|
}
|
||||||
const name = this.nextBlockName();
|
const name = this.nextBlockName();
|
||||||
const leftAgent = Agent.make(name + '[', {anchorRight: true});
|
const leftGAgent = GAgent.make(name + '[', {anchorRight: true});
|
||||||
const rightAgent = Agent.make(name + ']');
|
const rightGAgent = GAgent.make(name + ']');
|
||||||
this.agentStates.set(leftAgent.name, AgentState.LOCKED);
|
this.replaceGAgentState(leftGAgent, AgentState.LOCKED);
|
||||||
this.agentStates.set(rightAgent.name, AgentState.LOCKED);
|
this.replaceGAgentState(rightGAgent, AgentState.LOCKED);
|
||||||
this.updateAgentState(
|
this.updateGAgentState(
|
||||||
{name: alias},
|
GAgent.make(alias),
|
||||||
{blocked: true, group: alias}
|
{blocked: true, group: alias}
|
||||||
);
|
);
|
||||||
this.defineAgents(colAgents);
|
this.defineGAgents(gAgents);
|
||||||
const {indexL, indexR} = addBounds(
|
const {indexL, indexR} = addBounds(
|
||||||
this.agents,
|
this.gAgents,
|
||||||
leftAgent,
|
leftGAgent,
|
||||||
rightAgent,
|
rightGAgent,
|
||||||
colAgents
|
gAgents
|
||||||
);
|
);
|
||||||
|
|
||||||
const agentsCovered = [];
|
const gAgentsCovered = [];
|
||||||
const agentsContained = colAgents.slice();
|
const gAgentsContained = gAgents.slice();
|
||||||
for(let i = indexL + 1; i < indexR; ++ i) {
|
for(let i = indexL + 1; i < indexR; ++ i) {
|
||||||
agentsCovered.push(this.agents[i]);
|
gAgentsCovered.push(this.gAgents[i]);
|
||||||
}
|
}
|
||||||
array.removeAll(agentsCovered, agentsContained, Agent.equals);
|
array.removeAll(gAgentsCovered, gAgentsContained, GAgent.equals);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
colAgents,
|
gAgents,
|
||||||
leftAgent,
|
leftGAgent,
|
||||||
rightAgent,
|
rightGAgent,
|
||||||
agentsContained,
|
gAgentsContained,
|
||||||
agentsCovered,
|
gAgentsCovered,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGroupBegin({agents, mode, label, alias}) {
|
handleGroupBegin({agents, blockType, tag, label, alias}) {
|
||||||
const details = this.makeGroupDetails(agents, alias);
|
const details = this.makeGroupDetails(agents, alias);
|
||||||
|
|
||||||
details.agentsContained.forEach((agent) => {
|
details.gAgentsContained.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {group: alias});
|
this.updateGAgentState(gAgent, {group: alias});
|
||||||
});
|
});
|
||||||
details.agentsCovered.forEach((agent) => {
|
details.gAgentsCovered.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {covered: true});
|
this.updateGAgentState(gAgent, {covered: true});
|
||||||
});
|
});
|
||||||
this.activeGroups.set(alias, details);
|
this.activeGroups.set(alias, details);
|
||||||
this.addStage(this.setAgentVis(details.colAgents, true, 'box'));
|
this.addStage(this.setGAgentVis(details.gAgents, true, 'box'));
|
||||||
this.addStage({
|
this.addStage({
|
||||||
type: 'block begin',
|
type: 'block begin',
|
||||||
mode,
|
blockType,
|
||||||
label,
|
tag: this.textFormatter(tag),
|
||||||
left: details.leftAgent.name,
|
label: this.textFormatter(label),
|
||||||
right: details.rightAgent.name,
|
left: details.leftGAgent.id,
|
||||||
|
right: details.rightGAgent.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,18 +582,18 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
this.activeGroups.delete(name);
|
this.activeGroups.delete(name);
|
||||||
|
|
||||||
details.agentsContained.forEach((agent) => {
|
details.gAgentsContained.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {group: null});
|
this.updateGAgentState(gAgent, {group: null});
|
||||||
});
|
});
|
||||||
details.agentsCovered.forEach((agent) => {
|
details.gAgentsCovered.forEach((gAgent) => {
|
||||||
this.updateAgentState(agent, {covered: false});
|
this.updateGAgentState(gAgent, {covered: false});
|
||||||
});
|
});
|
||||||
this.updateAgentState({name}, {group: null});
|
this.updateGAgentState(GAgent.make(name), {group: null});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'block end',
|
type: 'block end',
|
||||||
left: details.leftAgent.name,
|
left: details.leftGAgent.id,
|
||||||
right: details.rightAgent.name,
|
right: details.rightGAgent.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,156 +639,156 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
expandGroupedAgent(agent) {
|
expandGroupedGAgent(gAgent) {
|
||||||
const group = this.getAgentState(agent).group;
|
const group = this.getGAgentState(gAgent).group;
|
||||||
if(!group) {
|
if(!group) {
|
||||||
return [agent];
|
return [gAgent];
|
||||||
}
|
}
|
||||||
const details = this.activeGroups.get(group);
|
const details = this.activeGroups.get(group);
|
||||||
return [details.leftAgent, details.rightAgent];
|
return [details.leftGAgent, details.rightGAgent];
|
||||||
}
|
}
|
||||||
|
|
||||||
expandGroupedAgentConnection(agents) {
|
expandGroupedGAgentConnection(gAgents) {
|
||||||
const agents1 = this.expandGroupedAgent(agents[0]);
|
const gAgents1 = this.expandGroupedGAgent(gAgents[0]);
|
||||||
const agents2 = this.expandGroupedAgent(agents[1]);
|
const gAgents2 = this.expandGroupedGAgent(gAgents[1]);
|
||||||
let ind1 = array.indexOf(this.agents, agents1[0], Agent.equals);
|
let ind1 = GAgent.indexOf(this.gAgents, gAgents1[0]);
|
||||||
let ind2 = array.indexOf(this.agents, agents2[0], Agent.equals);
|
let ind2 = GAgent.indexOf(this.gAgents, gAgents2[0]);
|
||||||
if(ind1 === -1) {
|
if(ind1 === -1) {
|
||||||
ind1 = this.agents.length;
|
ind1 = this.gAgents.length;
|
||||||
}
|
}
|
||||||
if(ind2 === -1) {
|
if(ind2 === -1) {
|
||||||
ind2 = this.agents.length;
|
ind2 = this.gAgents.length;
|
||||||
}
|
}
|
||||||
if(ind1 === ind2) {
|
if(ind1 === ind2) {
|
||||||
// Self-connection
|
// Self-connection
|
||||||
return [array.last(agents1), array.last(agents2)];
|
return [array.last(gAgents1), array.last(gAgents2)];
|
||||||
} else if(ind1 < ind2) {
|
} else if(ind1 < ind2) {
|
||||||
return [array.last(agents1), agents2[0]];
|
return [array.last(gAgents1), gAgents2[0]];
|
||||||
} else {
|
} else {
|
||||||
return [agents1[0], array.last(agents2)];
|
return [gAgents1[0], array.last(gAgents2)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterConnectFlags(agents) {
|
filterConnectFlags(pAgents) {
|
||||||
const beginAgents = (agents
|
const beginGAgents = (pAgents
|
||||||
.filter(Agent.hasFlag('begin'))
|
.filter(PAgent.hasFlag('begin'))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
const endAgents = (agents
|
const endGAgents = (pAgents
|
||||||
.filter(Agent.hasFlag('end'))
|
.filter(PAgent.hasFlag('end'))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
if(array.hasIntersection(beginAgents, endAgents, Agent.equals)) {
|
if(GAgent.hasIntersection(beginGAgents, endGAgents)) {
|
||||||
throw new Error('Cannot set agent visibility multiple times');
|
throw new Error('Cannot set agent visibility multiple times');
|
||||||
}
|
}
|
||||||
|
|
||||||
const startAgents = (agents
|
const startGAgents = (pAgents
|
||||||
.filter(Agent.hasFlag('start'))
|
.filter(PAgent.hasFlag('start'))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
const stopAgents = (agents
|
const stopGAgents = (pAgents
|
||||||
.filter(Agent.hasFlag('stop'))
|
.filter(PAgent.hasFlag('stop'))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
array.mergeSets(stopAgents, endAgents);
|
array.mergeSets(stopGAgents, endGAgents);
|
||||||
if(array.hasIntersection(startAgents, stopAgents, Agent.equals)) {
|
if(GAgent.hasIntersection(startGAgents, stopGAgents)) {
|
||||||
throw new Error('Cannot set agent highlighting multiple times');
|
throw new Error('Cannot set agent highlighting multiple times');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateAgents(beginAgents);
|
this.validateGAgents(beginGAgents);
|
||||||
this.validateAgents(endAgents);
|
this.validateGAgents(endGAgents);
|
||||||
this.validateAgents(startAgents);
|
this.validateGAgents(startGAgents);
|
||||||
this.validateAgents(stopAgents);
|
this.validateGAgents(stopGAgents);
|
||||||
|
|
||||||
return {beginAgents, endAgents, startAgents, stopAgents};
|
return {beginGAgents, endGAgents, startGAgents, stopGAgents};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConnect({agents, label, options}) {
|
handleConnect({agents, label, options}) {
|
||||||
const flags = this.filterConnectFlags(agents);
|
const flags = this.filterConnectFlags(agents);
|
||||||
|
|
||||||
let colAgents = agents.map(this.convertAgent);
|
let gAgents = agents.map(this.toGAgent);
|
||||||
this.validateAgents(colAgents, {allowGrouped: true});
|
this.validateGAgents(gAgents, {allowGrouped: true});
|
||||||
|
|
||||||
const allAgents = array.flatMap(colAgents, this.expandGroupedAgent);
|
const allGAgents = array.flatMap(gAgents, this.expandGroupedGAgent);
|
||||||
this.defineAgents(allAgents);
|
this.defineGAgents(allGAgents);
|
||||||
|
|
||||||
colAgents = this.expandGroupedAgentConnection(colAgents);
|
gAgents = this.expandGroupedGAgentConnection(gAgents);
|
||||||
const agentNames = colAgents.map(Agent.getName);
|
const agentIDs = gAgents.map((gAgent) => gAgent.id);
|
||||||
|
|
||||||
const implicitBegin = (agents
|
const implicitBeginGAgents = (agents
|
||||||
.filter(Agent.hasFlag('begin', false))
|
.filter(PAgent.hasFlag('begin', false))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
this.addStage(this.setAgentVis(implicitBegin, true, 'box'));
|
this.addStage(this.setGAgentVis(implicitBeginGAgents, true, 'box'));
|
||||||
|
|
||||||
const connectStage = {
|
const connectStage = {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
agentNames,
|
agentIDs,
|
||||||
label: this.applyLabelPattern(label),
|
label: this.textFormatter(this.applyLabelPattern(label)),
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentVis(flags.beginAgents, true, 'box', true),
|
this.setGAgentVis(flags.beginGAgents, true, 'box', true),
|
||||||
this.setAgentHighlight(flags.startAgents, true, true),
|
this.setGAgentHighlight(flags.startGAgents, true, true),
|
||||||
connectStage,
|
connectStage,
|
||||||
this.setAgentHighlight(flags.stopAgents, false, true),
|
this.setGAgentHighlight(flags.stopGAgents, false, true),
|
||||||
this.setAgentVis(flags.endAgents, false, 'cross', true),
|
this.setGAgentVis(flags.endGAgents, false, 'cross', true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNote({type, agents, mode, label}) {
|
handleNote({type, agents, mode, label}) {
|
||||||
let colAgents = null;
|
let gAgents = null;
|
||||||
if(agents.length === 0) {
|
if(agents.length === 0) {
|
||||||
colAgents = NOTE_DEFAULT_AGENTS[type] || [];
|
gAgents = NOTE_DEFAULT_G_AGENTS[type] || [];
|
||||||
} else {
|
} else {
|
||||||
colAgents = agents.map(this.convertAgent);
|
gAgents = agents.map(this.toGAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateAgents(colAgents, {allowGrouped: true});
|
this.validateGAgents(gAgents, {allowGrouped: true});
|
||||||
colAgents = array.flatMap(colAgents, this.expandGroupedAgent);
|
gAgents = array.flatMap(gAgents, this.expandGroupedGAgent);
|
||||||
const agentNames = colAgents.map(Agent.getName);
|
const agentIDs = gAgents.map((gAgent) => gAgent.id);
|
||||||
const uniqueAgents = new Set(agentNames).size;
|
const uniqueAgents = new Set(agentIDs).size;
|
||||||
if(type === 'note between' && uniqueAgents < 2) {
|
if(type === 'note between' && uniqueAgents < 2) {
|
||||||
throw new Error('note between requires at least 2 agents');
|
throw new Error('note between requires at least 2 agents');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addStage(this.setAgentVis(colAgents, true, 'box'));
|
this.addStage(this.setGAgentVis(gAgents, true, 'box'));
|
||||||
this.defineAgents(colAgents);
|
this.defineGAgents(gAgents);
|
||||||
|
|
||||||
this.addStage({
|
this.addStage({
|
||||||
type,
|
type,
|
||||||
agentNames,
|
agentIDs,
|
||||||
mode,
|
mode,
|
||||||
label,
|
label: this.textFormatter(label),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentDefine({agents}) {
|
handleAgentDefine({agents}) {
|
||||||
const colAgents = agents.map(this.convertAgent);
|
const gAgents = agents.map(this.toGAgent);
|
||||||
this.validateAgents(colAgents);
|
this.validateGAgents(gAgents);
|
||||||
this.defineAgents(colAgents);
|
this.defineGAgents(gAgents);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentBegin({agents, mode}) {
|
handleAgentBegin({agents, mode}) {
|
||||||
const colAgents = agents.map(this.convertAgent);
|
const gAgents = agents.map(this.toGAgent);
|
||||||
this.validateAgents(colAgents);
|
this.validateGAgents(gAgents);
|
||||||
this.addStage(this.setAgentVis(colAgents, true, mode, true));
|
this.addStage(this.setGAgentVis(gAgents, true, mode, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAgentEnd({agents, mode}) {
|
handleAgentEnd({agents, mode}) {
|
||||||
const groupAgents = (agents
|
const groupPAgents = (agents
|
||||||
.filter((agent) => this.activeGroups.has(agent.name))
|
.filter((pAgent) => this.activeGroups.has(pAgent.name))
|
||||||
);
|
);
|
||||||
const colAgents = (agents
|
const gAgents = (agents
|
||||||
.filter((agent) => !this.activeGroups.has(agent.name))
|
.filter((pAgent) => !this.activeGroups.has(pAgent.name))
|
||||||
.map(this.convertAgent)
|
.map(this.toGAgent)
|
||||||
);
|
);
|
||||||
this.validateAgents(colAgents);
|
this.validateGAgents(gAgents);
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentHighlight(colAgents, false),
|
this.setGAgentHighlight(gAgents, false),
|
||||||
this.setAgentVis(colAgents, false, mode, true),
|
this.setGAgentVis(gAgents, false, mode, true),
|
||||||
...groupAgents.map(this.endGroup),
|
...groupPAgents.map(this.endGroup),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -783,21 +802,46 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
handler(stage);
|
handler(stage);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(typeof e === 'object' && e.message) {
|
if(typeof e === 'object' && e.message) {
|
||||||
throw new Error(e.message + ' at line ' + (stage.ln + 1));
|
e.message += ' at line ' + (stage.ln + 1);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generate({stages, meta = {}}) {
|
_reset() {
|
||||||
this.agentStates.clear();
|
this.agentStates.clear();
|
||||||
this.markers.clear();
|
this.markers.clear();
|
||||||
this.agentAliases.clear();
|
this.agentAliases.clear();
|
||||||
this.activeGroups.clear();
|
this.activeGroups.clear();
|
||||||
this.agents.length = 0;
|
this.gAgents.length = 0;
|
||||||
this.blockCount = 0;
|
this.blockCount = 0;
|
||||||
this.nesting.length = 0;
|
this.nesting.length = 0;
|
||||||
this.labelPattern = [{token: 'label'}];
|
this.labelPattern = [{token: 'label'}];
|
||||||
const globals = this.beginNested('global', '', '', 0);
|
}
|
||||||
|
|
||||||
|
_finalise(globals) {
|
||||||
|
addBounds(
|
||||||
|
this.gAgents,
|
||||||
|
this.currentNest.leftGAgent,
|
||||||
|
this.currentNest.rightGAgent
|
||||||
|
);
|
||||||
|
optimiseStages(globals.stages);
|
||||||
|
|
||||||
|
this.gAgents.forEach((gAgent) => {
|
||||||
|
gAgent.formattedLabel = this.textFormatter(gAgent.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generate({stages, meta = {}}) {
|
||||||
|
this._reset();
|
||||||
|
|
||||||
|
this.textFormatter = meta.textFormatter;
|
||||||
|
const globals = this.beginNested('global', {
|
||||||
|
tag: '',
|
||||||
|
label: '',
|
||||||
|
name: '',
|
||||||
|
ln: 0,
|
||||||
|
});
|
||||||
|
|
||||||
stages.forEach(this.handleStage);
|
stages.forEach(this.handleStage);
|
||||||
|
|
||||||
|
@ -812,26 +856,21 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminators = meta.terminators || 'none';
|
const terminators = meta.terminators || 'none';
|
||||||
|
|
||||||
this.addParallelStages([
|
this.addParallelStages([
|
||||||
this.setAgentHighlight(this.agents, false),
|
this.setGAgentHighlight(this.gAgents, false),
|
||||||
this.setAgentVis(this.agents, false, terminators),
|
this.setGAgentVis(this.gAgents, false, terminators),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
addBounds(
|
this._finalise(globals);
|
||||||
this.agents,
|
|
||||||
this.currentNest.leftAgent,
|
|
||||||
this.currentNest.rightAgent
|
|
||||||
);
|
|
||||||
optimiseStages(globals.stages);
|
|
||||||
swapFirstBegin(globals.stages, meta.headers || 'box');
|
swapFirstBegin(globals.stages, meta.headers || 'box');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
meta: {
|
meta: {
|
||||||
title: meta.title,
|
title: this.textFormatter(meta.title),
|
||||||
theme: meta.theme,
|
theme: meta.theme,
|
||||||
},
|
},
|
||||||
agents: this.agents.slice(),
|
agents: this.gAgents.slice(),
|
||||||
stages: globals.stages,
|
stages: globals.stages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
define(() => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function parseMarkdown(text) {
|
||||||
|
if(!text) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = text.split('\n');
|
||||||
|
const result = [];
|
||||||
|
const attrs = null;
|
||||||
|
lines.forEach((line) => {
|
||||||
|
result.push([{text: line, attrs}]);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseMarkdown;
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
defineDescribe('Markdown Parser', [
|
||||||
|
'./MarkdownParser',
|
||||||
|
'svg/SVGTextBlock',
|
||||||
|
'svg/SVGUtilities',
|
||||||
|
], (
|
||||||
|
parser,
|
||||||
|
SVGTextBlock,
|
||||||
|
svg
|
||||||
|
) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
it('converts simple text', () => {
|
||||||
|
const formatted = parser('hello everybody');
|
||||||
|
expect(formatted).toEqual([[{text: 'hello everybody', attrs: null}]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces an empty array given an empty input', () => {
|
||||||
|
const formatted = parser('');
|
||||||
|
expect(formatted).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts multiline text', () => {
|
||||||
|
const formatted = parser('hello\neverybody');
|
||||||
|
expect(formatted).toEqual([
|
||||||
|
[{text: 'hello', attrs: null}],
|
||||||
|
[{text: 'everybody', attrs: null}],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SVGTextBlock interaction', () => {
|
||||||
|
let hold = null;
|
||||||
|
let block = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
hold = svg.makeContainer();
|
||||||
|
document.body.appendChild(hold);
|
||||||
|
block = new SVGTextBlock(hold, {attrs: {'font-size': 12}});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.removeChild(hold);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('produces a format compatible with SVGTextBlock', () => {
|
||||||
|
const formatted = parser('hello everybody');
|
||||||
|
block.set({formatted});
|
||||||
|
expect(hold.children.length).toEqual(1);
|
||||||
|
expect(hold.children[0].innerHTML).toEqual('hello everybody');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,20 +1,35 @@
|
||||||
define([
|
define([
|
||||||
'core/ArrayUtilities',
|
'core/ArrayUtilities',
|
||||||
'./Tokeniser',
|
'./Tokeniser',
|
||||||
|
'./MarkdownParser',
|
||||||
'./LabelPatternParser',
|
'./LabelPatternParser',
|
||||||
'./CodeMirrorHints',
|
|
||||||
], (
|
], (
|
||||||
array,
|
array,
|
||||||
Tokeniser,
|
Tokeniser,
|
||||||
labelPatternParser,
|
markdownParser,
|
||||||
CMHints
|
labelPatternParser
|
||||||
) => {
|
) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const BLOCK_TYPES = {
|
const BLOCK_TYPES = {
|
||||||
'if': {type: 'block begin', mode: 'if', skip: []},
|
'if': {
|
||||||
'else': {type: 'block split', mode: 'else', skip: ['if']},
|
type: 'block begin',
|
||||||
'repeat': {type: 'block begin', mode: 'repeat', skip: []},
|
blockType: 'if',
|
||||||
|
tag: 'if',
|
||||||
|
skip: [],
|
||||||
|
},
|
||||||
|
'else': {
|
||||||
|
type: 'block split',
|
||||||
|
blockType: 'else',
|
||||||
|
tag: 'else',
|
||||||
|
skip: ['if'],
|
||||||
|
},
|
||||||
|
'repeat': {
|
||||||
|
type: 'block begin',
|
||||||
|
blockType: 'repeat',
|
||||||
|
tag: 'repeat',
|
||||||
|
skip: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONNECT_TYPES = ((() => {
|
const CONNECT_TYPES = ((() => {
|
||||||
|
@ -314,7 +329,8 @@ define([
|
||||||
skip = skipOver(line, skip, [':']);
|
skip = skipOver(line, skip, [':']);
|
||||||
return {
|
return {
|
||||||
type: type.type,
|
type: type.type,
|
||||||
mode: type.mode,
|
blockType: type.blockType,
|
||||||
|
tag: type.tag,
|
||||||
label: joinLabel(line, skip),
|
label: joinLabel(line, skip),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -345,7 +361,8 @@ define([
|
||||||
return {
|
return {
|
||||||
type: 'group begin',
|
type: 'group begin',
|
||||||
agents,
|
agents,
|
||||||
mode: 'ref',
|
blockType: 'ref',
|
||||||
|
tag: 'ref',
|
||||||
label: def.name,
|
label: def.name,
|
||||||
alias: def.alias,
|
alias: def.alias,
|
||||||
};
|
};
|
||||||
|
@ -480,10 +497,6 @@ define([
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeMirrorHints() {
|
|
||||||
return CMHints.getHints;
|
|
||||||
}
|
|
||||||
|
|
||||||
parseLines(lines) {
|
parseLines(lines) {
|
||||||
const result = {
|
const result = {
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -491,6 +504,7 @@ define([
|
||||||
theme: '',
|
theme: '',
|
||||||
terminators: 'none',
|
terminators: 'none',
|
||||||
headers: 'box',
|
headers: 'box',
|
||||||
|
textFormatter: markdownParser,
|
||||||
},
|
},
|
||||||
stages: [],
|
stages: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,26 +6,30 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
const PARSED = {
|
const PARSED = {
|
||||||
blockBegin: ({
|
blockBegin: ({
|
||||||
ln = jasmine.anything(),
|
ln = jasmine.anything(),
|
||||||
mode = jasmine.anything(),
|
blockType = jasmine.anything(),
|
||||||
|
tag = jasmine.anything(),
|
||||||
label = jasmine.anything(),
|
label = jasmine.anything(),
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
return {
|
return {
|
||||||
type: 'block begin',
|
type: 'block begin',
|
||||||
ln,
|
ln,
|
||||||
mode,
|
blockType,
|
||||||
|
tag,
|
||||||
label,
|
label,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
blockSplit: ({
|
blockSplit: ({
|
||||||
ln = jasmine.anything(),
|
ln = jasmine.anything(),
|
||||||
mode = jasmine.anything(),
|
blockType = jasmine.anything(),
|
||||||
|
tag = jasmine.anything(),
|
||||||
label = jasmine.anything(),
|
label = jasmine.anything(),
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
return {
|
return {
|
||||||
type: 'block split',
|
type: 'block split',
|
||||||
ln,
|
ln,
|
||||||
mode,
|
blockType,
|
||||||
|
tag,
|
||||||
label,
|
label,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -73,6 +77,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
theme: '',
|
theme: '',
|
||||||
terminators: 'none',
|
terminators: 'none',
|
||||||
headers: 'box',
|
headers: 'box',
|
||||||
|
textFormatter: jasmine.anything(),
|
||||||
},
|
},
|
||||||
stages: [],
|
stages: [],
|
||||||
});
|
});
|
||||||
|
@ -98,6 +103,11 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
expect(parsed.meta.headers).toEqual('bar');
|
expect(parsed.meta.headers).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('propagates a function which can be used to format text', () => {
|
||||||
|
const parsed = parser.parse('title foo');
|
||||||
|
expect(parsed.meta.textFormatter).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
it('reads multiple tokens as one when reading values', () => {
|
it('reads multiple tokens as one when reading values', () => {
|
||||||
const parsed = parser.parse('title foo bar');
|
const parsed = parser.parse('title foo bar');
|
||||||
expect(parsed.meta.title).toEqual('foo bar');
|
expect(parsed.meta.title).toEqual('foo bar');
|
||||||
|
@ -435,7 +445,8 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
type: 'group begin',
|
type: 'group begin',
|
||||||
ln: jasmine.anything(),
|
ln: jasmine.anything(),
|
||||||
agents: [],
|
agents: [],
|
||||||
mode: 'ref',
|
blockType: 'ref',
|
||||||
|
tag: 'ref',
|
||||||
label: 'Foo bar',
|
label: 'Foo bar',
|
||||||
alias: 'baz',
|
alias: 'baz',
|
||||||
},
|
},
|
||||||
|
@ -446,7 +457,8 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
{name: 'A', alias: '', flags: []},
|
{name: 'A', alias: '', flags: []},
|
||||||
{name: 'B', alias: '', flags: []},
|
{name: 'B', alias: '', flags: []},
|
||||||
],
|
],
|
||||||
mode: 'ref',
|
blockType: 'ref',
|
||||||
|
tag: 'ref',
|
||||||
label: 'Foo bar',
|
label: 'Foo bar',
|
||||||
alias: 'baz',
|
alias: 'baz',
|
||||||
},
|
},
|
||||||
|
@ -518,12 +530,24 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
'end\n'
|
'end\n'
|
||||||
);
|
);
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
PARSED.blockBegin({mode: 'if', label: 'something happens'}),
|
PARSED.blockBegin({
|
||||||
|
blockType: 'if',
|
||||||
|
tag: 'if',
|
||||||
|
label: 'something happens',
|
||||||
|
}),
|
||||||
PARSED.connect(['A', 'B']),
|
PARSED.connect(['A', 'B']),
|
||||||
PARSED.blockSplit({mode: 'else', label: 'something else'}),
|
PARSED.blockSplit({
|
||||||
|
blockType: 'else',
|
||||||
|
tag: 'else',
|
||||||
|
label: 'something else',
|
||||||
|
}),
|
||||||
PARSED.connect(['A', 'C']),
|
PARSED.connect(['A', 'C']),
|
||||||
PARSED.connect(['C', 'B']),
|
PARSED.connect(['C', 'B']),
|
||||||
PARSED.blockSplit({mode: 'else', label: ''}),
|
PARSED.blockSplit({
|
||||||
|
blockType: 'else',
|
||||||
|
tag: 'else',
|
||||||
|
label: '',
|
||||||
|
}),
|
||||||
PARSED.connect(['A', 'D']),
|
PARSED.connect(['A', 'D']),
|
||||||
PARSED.blockEnd(),
|
PARSED.blockEnd(),
|
||||||
]);
|
]);
|
||||||
|
@ -532,7 +556,11 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
||||||
it('converts loop blocks', () => {
|
it('converts loop blocks', () => {
|
||||||
const parsed = parser.parse('repeat until something');
|
const parsed = parser.parse('repeat until something');
|
||||||
expect(parsed.stages).toEqual([
|
expect(parsed.stages).toEqual([
|
||||||
PARSED.blockBegin({mode: 'repeat', label: 'until something'}),
|
PARSED.blockBegin({
|
||||||
|
blockType: 'repeat',
|
||||||
|
tag: 'repeat',
|
||||||
|
label: 'until something',
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ define([
|
||||||
/* jshint +W072 */
|
/* jshint +W072 */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function findExtremes(agentInfos, agentNames) {
|
function findExtremes(agentInfos, agentIDs) {
|
||||||
let min = null;
|
let min = null;
|
||||||
let max = null;
|
let max = null;
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const info = agentInfos.get(name);
|
const info = agentInfos.get(id);
|
||||||
if(min === null || info.index < min.index) {
|
if(min === null || info.index < min.index) {
|
||||||
min = info;
|
min = info;
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ define([
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
left: min.label,
|
left: min.id,
|
||||||
right: max.label,
|
right: max.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,23 +158,23 @@ define([
|
||||||
return namespacedName;
|
return namespacedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
addSeparation(agentName1, agentName2, dist) {
|
addSeparation(agentID1, agentID2, dist) {
|
||||||
const info1 = this.agentInfos.get(agentName1);
|
const info1 = this.agentInfos.get(agentID1);
|
||||||
const info2 = this.agentInfos.get(agentName2);
|
const info2 = this.agentInfos.get(agentID2);
|
||||||
|
|
||||||
const d1 = info1.separations.get(agentName2) || 0;
|
const d1 = info1.separations.get(agentID2) || 0;
|
||||||
info1.separations.set(agentName2, Math.max(d1, dist));
|
info1.separations.set(agentID2, Math.max(d1, dist));
|
||||||
|
|
||||||
const d2 = info2.separations.get(agentName1) || 0;
|
const d2 = info2.separations.get(agentID1) || 0;
|
||||||
info2.separations.set(agentName1, Math.max(d2, dist));
|
info2.separations.set(agentID1, Math.max(d2, dist));
|
||||||
}
|
}
|
||||||
|
|
||||||
separationStage(stage) {
|
separationStage(stage) {
|
||||||
const agentSpaces = new Map();
|
const agentSpaces = new Map();
|
||||||
const agentNames = this.visibleAgents.slice();
|
const agentIDs = this.visibleAgentIDs.slice();
|
||||||
|
|
||||||
const addSpacing = (agentName, {left, right}) => {
|
const addSpacing = (agentID, {left, right}) => {
|
||||||
const current = agentSpaces.get(agentName);
|
const current = agentSpaces.get(agentID);
|
||||||
current.left = Math.max(current.left, left);
|
current.left = Math.max(current.left, left);
|
||||||
current.right = Math.max(current.right, right);
|
current.right = Math.max(current.right, right);
|
||||||
};
|
};
|
||||||
|
@ -182,12 +182,12 @@ define([
|
||||||
this.agentInfos.forEach((agentInfo) => {
|
this.agentInfos.forEach((agentInfo) => {
|
||||||
const rad = agentInfo.currentRad;
|
const rad = agentInfo.currentRad;
|
||||||
agentInfo.currentMaxRad = rad;
|
agentInfo.currentMaxRad = rad;
|
||||||
agentSpaces.set(agentInfo.label, {left: rad, right: rad});
|
agentSpaces.set(agentInfo.id, {left: rad, right: rad});
|
||||||
});
|
});
|
||||||
const env = {
|
const env = {
|
||||||
theme: this.theme,
|
theme: this.theme,
|
||||||
agentInfos: this.agentInfos,
|
agentInfos: this.agentInfos,
|
||||||
visibleAgents: this.visibleAgents,
|
visibleAgentIDs: this.visibleAgentIDs,
|
||||||
textSizer: this.sizer,
|
textSizer: this.sizer,
|
||||||
addSpacing,
|
addSpacing,
|
||||||
addSeparation: this.addSeparation,
|
addSeparation: this.addSeparation,
|
||||||
|
@ -200,33 +200,33 @@ define([
|
||||||
}
|
}
|
||||||
component.separationPre(stage, env);
|
component.separationPre(stage, env);
|
||||||
component.separation(stage, env);
|
component.separation(stage, env);
|
||||||
array.mergeSets(agentNames, this.visibleAgents);
|
array.mergeSets(agentIDs, this.visibleAgentIDs);
|
||||||
|
|
||||||
agentNames.forEach((agentNameR) => {
|
agentIDs.forEach((agentIDR) => {
|
||||||
const infoR = this.agentInfos.get(agentNameR);
|
const infoR = this.agentInfos.get(agentIDR);
|
||||||
const sepR = agentSpaces.get(agentNameR);
|
const sepR = agentSpaces.get(agentIDR);
|
||||||
infoR.maxRPad = Math.max(infoR.maxRPad, sepR.right);
|
infoR.maxRPad = Math.max(infoR.maxRPad, sepR.right);
|
||||||
infoR.maxLPad = Math.max(infoR.maxLPad, sepR.left);
|
infoR.maxLPad = Math.max(infoR.maxLPad, sepR.left);
|
||||||
agentNames.forEach((agentNameL) => {
|
agentIDs.forEach((agentIDL) => {
|
||||||
const infoL = this.agentInfos.get(agentNameL);
|
const infoL = this.agentInfos.get(agentIDL);
|
||||||
if(infoL.index >= infoR.index) {
|
if(infoL.index >= infoR.index) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sepL = agentSpaces.get(agentNameL);
|
const sepL = agentSpaces.get(agentIDL);
|
||||||
this.addSeparation(
|
this.addSeparation(
|
||||||
agentNameR,
|
agentIDR,
|
||||||
agentNameL,
|
agentIDL,
|
||||||
sepR.left + sepL.right + this.theme.agentMargin
|
sepR.left + sepL.right + this.theme.agentMargin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAgentRange(agentNames, topY = 0) {
|
checkAgentRange(agentIDs, topY = 0) {
|
||||||
if(agentNames.length === 0) {
|
if(agentIDs.length === 0) {
|
||||||
return topY;
|
return topY;
|
||||||
}
|
}
|
||||||
const {left, right} = findExtremes(this.agentInfos, agentNames);
|
const {left, right} = findExtremes(this.agentInfos, agentIDs);
|
||||||
const leftX = this.agentInfos.get(left).x;
|
const leftX = this.agentInfos.get(left).x;
|
||||||
const rightX = this.agentInfos.get(right).x;
|
const rightX = this.agentInfos.get(right).x;
|
||||||
let baseY = topY;
|
let baseY = topY;
|
||||||
|
@ -238,11 +238,11 @@ define([
|
||||||
return baseY;
|
return baseY;
|
||||||
}
|
}
|
||||||
|
|
||||||
markAgentRange(agentNames, y) {
|
markAgentRange(agentIDs, y) {
|
||||||
if(agentNames.length === 0) {
|
if(agentIDs.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const {left, right} = findExtremes(this.agentInfos, agentNames);
|
const {left, right} = findExtremes(this.agentInfos, agentIDs);
|
||||||
const leftX = this.agentInfos.get(left).x;
|
const leftX = this.agentInfos.get(left).x;
|
||||||
const rightX = this.agentInfos.get(right).x;
|
const rightX = this.agentInfos.get(right).x;
|
||||||
this.agentInfos.forEach((agentInfo) => {
|
this.agentInfos.forEach((agentInfo) => {
|
||||||
|
@ -293,10 +293,10 @@ define([
|
||||||
};
|
};
|
||||||
const component = this.components.get(stage.type);
|
const component = this.components.get(stage.type);
|
||||||
const result = component.renderPre(stage, envPre);
|
const result = component.renderPre(stage, envPre);
|
||||||
const {topShift, agentNames, asynchronousY} =
|
const {topShift, agentIDs, asynchronousY} =
|
||||||
BaseComponent.cleanRenderPreResult(result, this.currentY);
|
BaseComponent.cleanRenderPreResult(result, this.currentY);
|
||||||
|
|
||||||
const topY = this.checkAgentRange(agentNames, asynchronousY);
|
const topY = this.checkAgentRange(agentIDs, asynchronousY);
|
||||||
|
|
||||||
const eventOut = () => {
|
const eventOut = () => {
|
||||||
this.trigger('mouseout');
|
this.trigger('mouseout');
|
||||||
|
@ -332,8 +332,8 @@ define([
|
||||||
textSizer: this.sizer,
|
textSizer: this.sizer,
|
||||||
SVGTextBlockClass: this.SVGTextBlockClass,
|
SVGTextBlockClass: this.SVGTextBlockClass,
|
||||||
state: this.state,
|
state: this.state,
|
||||||
drawAgentLine: (agentName, toY, andStop = false) => {
|
drawAgentLine: (agentID, toY, andStop = false) => {
|
||||||
const agentInfo = this.agentInfos.get(agentName);
|
const agentInfo = this.agentInfos.get(agentID);
|
||||||
this.drawAgentLine(agentInfo, toY);
|
this.drawAgentLine(agentInfo, toY);
|
||||||
agentInfo.latestYStart = andStop ? null : toY;
|
agentInfo.latestYStart = andStop ? null : toY;
|
||||||
},
|
},
|
||||||
|
@ -343,7 +343,7 @@ define([
|
||||||
};
|
};
|
||||||
|
|
||||||
const bottomY = Math.max(topY, component.render(stage, env) || 0);
|
const bottomY = Math.max(topY, component.render(stage, env) || 0);
|
||||||
this.markAgentRange(agentNames, bottomY);
|
this.markAgentRange(agentIDs, bottomY);
|
||||||
|
|
||||||
this.currentY = bottomY;
|
this.currentY = bottomY;
|
||||||
}
|
}
|
||||||
|
@ -379,7 +379,7 @@ define([
|
||||||
agentInfo.x = currentX;
|
agentInfo.x = currentX;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.agentInfos.forEach(({label, x, maxRPad, maxLPad}) => {
|
this.agentInfos.forEach(({x, maxRPad, maxLPad}) => {
|
||||||
this.minX = Math.min(this.minX, x - maxLPad);
|
this.minX = Math.min(this.minX, x - maxLPad);
|
||||||
this.maxX = Math.max(this.maxX, x + maxRPad);
|
this.maxX = Math.max(this.maxX, x + maxRPad);
|
||||||
});
|
});
|
||||||
|
@ -388,8 +388,9 @@ define([
|
||||||
buildAgentInfos(agents, stages) {
|
buildAgentInfos(agents, stages) {
|
||||||
this.agentInfos = new Map();
|
this.agentInfos = new Map();
|
||||||
agents.forEach((agent, index) => {
|
agents.forEach((agent, index) => {
|
||||||
this.agentInfos.set(agent.name, {
|
this.agentInfos.set(agent.id, {
|
||||||
label: agent.name,
|
id: agent.id,
|
||||||
|
formattedLabel: agent.formattedLabel,
|
||||||
anchorRight: agent.anchorRight,
|
anchorRight: agent.anchorRight,
|
||||||
index,
|
index,
|
||||||
x: null,
|
x: null,
|
||||||
|
@ -403,7 +404,7 @@ define([
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.visibleAgents = ['[', ']'];
|
this.visibleAgentIDs = ['[', ']'];
|
||||||
stages.forEach(this.separationStage);
|
stages.forEach(this.separationStage);
|
||||||
|
|
||||||
this.positionAgents();
|
this.positionAgents();
|
||||||
|
@ -497,7 +498,7 @@ define([
|
||||||
|
|
||||||
this.title.set({
|
this.title.set({
|
||||||
attrs: this.theme.titleAttrs,
|
attrs: this.theme.titleAttrs,
|
||||||
text: sequence.meta.title,
|
formatted: sequence.meta.title,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.minX = 0;
|
this.minX = 0;
|
||||||
|
@ -534,8 +535,8 @@ define([
|
||||||
return this.themes.get('');
|
return this.themes.get('');
|
||||||
}
|
}
|
||||||
|
|
||||||
getAgentX(name) {
|
getAgentX(id) {
|
||||||
return this.agentInfos.get(name).x;
|
return this.agentInfos.get(id).x;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg() {
|
svg() {
|
||||||
|
|
|
@ -26,10 +26,10 @@ defineDescribe('Sequence Renderer', [
|
||||||
});
|
});
|
||||||
|
|
||||||
const GENERATED = {
|
const GENERATED = {
|
||||||
connect: (agentNames, label = '') => {
|
connect: (agentIDs, label = []) => {
|
||||||
return {
|
return {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
agentNames,
|
agentIDs,
|
||||||
label,
|
label,
|
||||||
options: {
|
options: {
|
||||||
line: 'solid',
|
line: 'solid',
|
||||||
|
@ -40,15 +40,35 @@ defineDescribe('Sequence Renderer', [
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function format(text) {
|
||||||
|
if(!text) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [[{text}]];
|
||||||
|
}
|
||||||
|
|
||||||
describe('.render', () => {
|
describe('.render', () => {
|
||||||
it('populates the SVG with content', () => {
|
it('populates the SVG with content', () => {
|
||||||
renderer.render({
|
renderer.render({
|
||||||
meta: {title: 'Title'},
|
meta: {title: format('Title')},
|
||||||
agents: [
|
agents: [
|
||||||
{name: '[', anchorRight: true},
|
{
|
||||||
{name: 'Col 1', anchorRight: false},
|
id: '[',
|
||||||
{name: 'Col 2', anchorRight: false},
|
formattedLabel: null,
|
||||||
{name: ']', anchorRight: false},
|
anchorRight: true,
|
||||||
|
}, {
|
||||||
|
id: 'Col 1',
|
||||||
|
formattedLabel: format('Col 1!'),
|
||||||
|
anchorRight: false,
|
||||||
|
}, {
|
||||||
|
id: 'Col 2',
|
||||||
|
formattedLabel: format('Col 2!'),
|
||||||
|
anchorRight: false,
|
||||||
|
}, {
|
||||||
|
id: ']',
|
||||||
|
formattedLabel: null,
|
||||||
|
anchorRight: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
stages: [],
|
stages: [],
|
||||||
});
|
});
|
||||||
|
@ -63,17 +83,17 @@ defineDescribe('Sequence Renderer', [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderer.render({
|
renderer.render({
|
||||||
meta: {title: ''},
|
meta: {title: []},
|
||||||
agents: [
|
agents: [
|
||||||
{name: '[', anchorRight: true},
|
{id: '[', formattedLabel: null, anchorRight: true},
|
||||||
{name: 'A', anchorRight: false},
|
{id: 'A', formattedLabel: format('A!'), anchorRight: false},
|
||||||
{name: 'B', anchorRight: false},
|
{id: 'B', formattedLabel: format('B!'), anchorRight: false},
|
||||||
{name: ']', anchorRight: false},
|
{id: ']', formattedLabel: null, anchorRight: false},
|
||||||
],
|
],
|
||||||
stages: [
|
stages: [
|
||||||
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
|
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},
|
||||||
GENERATED.connect(['A', 'B']),
|
GENERATED.connect(['A', 'B']),
|
||||||
{type: 'agent end', agentNames: ['A', 'B'], mode: 'none'},
|
{type: 'agent end', agentIDs: ['A', 'B'], mode: 'none'},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,18 +113,18 @@ defineDescribe('Sequence Renderer', [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderer.render({
|
renderer.render({
|
||||||
meta: {title: ''},
|
meta: {title: []},
|
||||||
agents: [
|
agents: [
|
||||||
{name: '[', anchorRight: true},
|
{id: '[', formattedLabel: null, anchorRight: true},
|
||||||
{name: 'A', anchorRight: false},
|
{id: 'A', formattedLabel: format('A!'), anchorRight: false},
|
||||||
{name: 'B', anchorRight: false},
|
{id: 'B', formattedLabel: format('B!'), anchorRight: false},
|
||||||
{name: 'C', anchorRight: false},
|
{id: 'C', formattedLabel: format('C!'), anchorRight: false},
|
||||||
{name: ']', anchorRight: false},
|
{id: ']', formattedLabel: null, anchorRight: false},
|
||||||
],
|
],
|
||||||
stages: [
|
stages: [
|
||||||
{
|
{
|
||||||
type: 'agent begin',
|
type: 'agent begin',
|
||||||
agentNames: ['A', 'B', 'C'],
|
agentIDs: ['A', 'B', 'C'],
|
||||||
mode: 'box',
|
mode: 'box',
|
||||||
},
|
},
|
||||||
GENERATED.connect(['[', 'A']),
|
GENERATED.connect(['[', 'A']),
|
||||||
|
@ -113,7 +133,7 @@ defineDescribe('Sequence Renderer', [
|
||||||
GENERATED.connect(['C', ']']),
|
GENERATED.connect(['C', ']']),
|
||||||
{
|
{
|
||||||
type: 'agent end',
|
type: 'agent end',
|
||||||
agentNames: ['A', 'B', 'C'],
|
agentIDs: ['A', 'B', 'C'],
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -142,25 +162,25 @@ defineDescribe('Sequence Renderer', [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderer.render({
|
renderer.render({
|
||||||
meta: {title: ''},
|
meta: {title: []},
|
||||||
agents: [
|
agents: [
|
||||||
{name: '[', anchorRight: true},
|
{id: '[', formattedLabel: null, anchorRight: true},
|
||||||
{name: 'A', anchorRight: false},
|
{id: 'A', formattedLabel: format('A!'), anchorRight: false},
|
||||||
{name: 'B', anchorRight: false},
|
{id: 'B', formattedLabel: format('B!'), anchorRight: false},
|
||||||
{name: 'C', anchorRight: false},
|
{id: 'C', formattedLabel: format('C!'), anchorRight: false},
|
||||||
{name: 'D', anchorRight: false},
|
{id: 'D', formattedLabel: format('D!'), anchorRight: false},
|
||||||
{name: ']', anchorRight: false},
|
{id: ']', formattedLabel: null, anchorRight: false},
|
||||||
],
|
],
|
||||||
stages: [
|
stages: [
|
||||||
{type: 'agent begin', agentNames: ['A', 'B'], mode: 'box'},
|
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},
|
||||||
GENERATED.connect(['A', 'B'], 'short'),
|
GENERATED.connect(['A', 'B'], format('short')),
|
||||||
{type: 'agent end', agentNames: ['B'], mode: 'cross'},
|
{type: 'agent end', agentIDs: ['B'], mode: 'cross'},
|
||||||
{type: 'agent begin', agentNames: ['C'], mode: 'box'},
|
{type: 'agent begin', agentIDs: ['C'], mode: 'box'},
|
||||||
GENERATED.connect(['A', 'C'], 'long description here'),
|
GENERATED.connect(['A', 'C'], format('long description')),
|
||||||
{type: 'agent end', agentNames: ['C'], mode: 'cross'},
|
{type: 'agent end', agentIDs: ['C'], mode: 'cross'},
|
||||||
{type: 'agent begin', agentNames: ['D'], mode: 'box'},
|
{type: 'agent begin', agentIDs: ['D'], mode: 'box'},
|
||||||
GENERATED.connect(['A', 'D'], 'short again'),
|
GENERATED.connect(['A', 'D'], format('short again')),
|
||||||
{type: 'agent end', agentNames: ['A', 'D'], mode: 'cross'},
|
{type: 'agent end', agentIDs: ['A', 'D'], mode: 'cross'},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ define([
|
||||||
'./Generator',
|
'./Generator',
|
||||||
'./Renderer',
|
'./Renderer',
|
||||||
'./Exporter',
|
'./Exporter',
|
||||||
|
'./CodeMirrorHints',
|
||||||
'./themes/BaseTheme',
|
'./themes/BaseTheme',
|
||||||
'./themes/Basic',
|
'./themes/Basic',
|
||||||
'./themes/Monospace',
|
'./themes/Monospace',
|
||||||
|
@ -16,6 +17,7 @@ define([
|
||||||
Generator,
|
Generator,
|
||||||
Renderer,
|
Renderer,
|
||||||
Exporter,
|
Exporter,
|
||||||
|
CMHints,
|
||||||
BaseTheme,
|
BaseTheme,
|
||||||
BasicTheme,
|
BasicTheme,
|
||||||
MonospaceTheme,
|
MonospaceTheme,
|
||||||
|
@ -36,14 +38,13 @@ define([
|
||||||
const SharedParser = new Parser();
|
const SharedParser = new Parser();
|
||||||
const SharedGenerator = new Generator();
|
const SharedGenerator = new Generator();
|
||||||
const CMMode = SharedParser.getCodeMirrorMode();
|
const CMMode = SharedParser.getCodeMirrorMode();
|
||||||
const CMHints = SharedParser.getCodeMirrorHints();
|
|
||||||
|
|
||||||
function registerCodeMirrorMode(CodeMirror, modeName = 'sequence') {
|
function registerCodeMirrorMode(CodeMirror, modeName = 'sequence') {
|
||||||
if(!CodeMirror) {
|
if(!CodeMirror) {
|
||||||
CodeMirror = window.CodeMirror;
|
CodeMirror = window.CodeMirror;
|
||||||
}
|
}
|
||||||
CodeMirror.defineMode(modeName, () => CMMode);
|
CodeMirror.defineMode(modeName, () => CMMode);
|
||||||
CodeMirror.registerHelper('hint', modeName, CMHints);
|
CodeMirror.registerHelper('hint', modeName, CMHints.getHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTheme(theme) {
|
function addTheme(theme) {
|
||||||
|
|
|
@ -12,10 +12,10 @@ define([
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class CapBox {
|
class CapBox {
|
||||||
separation({label}, env) {
|
separation({formattedLabel}, env) {
|
||||||
const config = env.theme.agentCap.box;
|
const config = env.theme.agentCap.box;
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(config.labelAttrs, label).width +
|
env.textSizer.measure(config.labelAttrs, formattedLabel).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
@ -27,20 +27,20 @@ define([
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
topShift({label}, env) {
|
topShift({formattedLabel}, env) {
|
||||||
const config = env.theme.agentCap.box;
|
const config = env.theme.agentCap.box;
|
||||||
const height = (
|
const height = (
|
||||||
env.textSizer.measureHeight(config.labelAttrs, label) +
|
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
|
||||||
config.padding.top +
|
config.padding.top +
|
||||||
config.padding.bottom
|
config.padding.bottom
|
||||||
);
|
);
|
||||||
return Math.max(0, height - config.arrowBottom);
|
return Math.max(0, height - config.arrowBottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(y, {x, label}, env) {
|
render(y, {x, formattedLabel}, env) {
|
||||||
const config = env.theme.agentCap.box;
|
const config = env.theme.agentCap.box;
|
||||||
const clickable = env.makeRegion();
|
const clickable = env.makeRegion();
|
||||||
const {width, height} = SVGShapes.renderBoxedText(label, {
|
const {width, height} = SVGShapes.renderBoxedText(formattedLabel, {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
padding: config.padding,
|
padding: config.padding,
|
||||||
|
@ -105,10 +105,10 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
class CapBar {
|
class CapBar {
|
||||||
separation({label}, env) {
|
separation({formattedLabel}, env) {
|
||||||
const config = env.theme.agentCap.box;
|
const config = env.theme.agentCap.box;
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(config.labelAttrs, label).width +
|
env.textSizer.measure(config.labelAttrs, formattedLabel).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
config.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
@ -125,17 +125,17 @@ define([
|
||||||
return config.height / 2;
|
return config.height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(y, {x, label}, env) {
|
render(y, {x, formattedLabel}, env) {
|
||||||
const configB = env.theme.agentCap.box;
|
const boxCfg = env.theme.agentCap.box;
|
||||||
const config = env.theme.agentCap.bar;
|
const barCfg = env.theme.agentCap.bar;
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(configB.labelAttrs, label).width +
|
env.textSizer.measure(boxCfg.labelAttrs, formattedLabel).width +
|
||||||
configB.padding.left +
|
boxCfg.padding.left +
|
||||||
configB.padding.right
|
boxCfg.padding.right
|
||||||
);
|
);
|
||||||
const height = config.height;
|
const height = barCfg.height;
|
||||||
|
|
||||||
env.shapeLayer.appendChild(config.render({
|
env.shapeLayer.appendChild(barCfg.render({
|
||||||
x: x - width / 2,
|
x: x - width / 2,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
|
@ -172,7 +172,7 @@ define([
|
||||||
return isBegin ? config.height : 0;
|
return isBegin ? config.height : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(y, {x, label}, env, isBegin) {
|
render(y, {x}, env, isBegin) {
|
||||||
const config = env.theme.agentCap.fade;
|
const config = env.theme.agentCap.fade;
|
||||||
const ratio = config.height / (config.height + config.extend);
|
const ratio = config.height / (config.height + config.extend);
|
||||||
|
|
||||||
|
@ -266,12 +266,12 @@ define([
|
||||||
this.begin = begin;
|
this.begin = begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
separationPre({mode, agentNames}, env) {
|
separationPre({mode, agentIDs}, env) {
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(id);
|
||||||
const cap = AGENT_CAPS[mode];
|
const cap = AGENT_CAPS[mode];
|
||||||
const sep = cap.separation(agentInfo, env, this.begin);
|
const sep = cap.separation(agentInfo, env, this.begin);
|
||||||
env.addSpacing(name, sep);
|
env.addSpacing(id, sep);
|
||||||
agentInfo.currentMaxRad = Math.max(
|
agentInfo.currentMaxRad = Math.max(
|
||||||
agentInfo.currentMaxRad,
|
agentInfo.currentMaxRad,
|
||||||
sep.radius
|
sep.radius
|
||||||
|
@ -279,18 +279,18 @@ define([
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
separation({mode, agentNames}, env) {
|
separation({mode, agentIDs}, env) {
|
||||||
if(this.begin) {
|
if(this.begin) {
|
||||||
array.mergeSets(env.visibleAgents, agentNames);
|
array.mergeSets(env.visibleAgentIDs, agentIDs);
|
||||||
} else {
|
} else {
|
||||||
array.removeAll(env.visibleAgents, agentNames);
|
array.removeAll(env.visibleAgentIDs, agentIDs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre({mode, agentNames}, env) {
|
renderPre({mode, agentIDs}, env) {
|
||||||
let maxTopShift = 0;
|
let maxTopShift = 0;
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(id);
|
||||||
const cap = AGENT_CAPS[mode];
|
const cap = AGENT_CAPS[mode];
|
||||||
const topShift = cap.topShift(agentInfo, env, this.begin);
|
const topShift = cap.topShift(agentInfo, env, this.begin);
|
||||||
maxTopShift = Math.max(maxTopShift, topShift);
|
maxTopShift = Math.max(maxTopShift, topShift);
|
||||||
|
@ -299,15 +299,15 @@ define([
|
||||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
agentNames,
|
agentIDs,
|
||||||
topShift: maxTopShift,
|
topShift: maxTopShift,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render({mode, agentNames}, env) {
|
render({mode, agentIDs}, env) {
|
||||||
let maxEnd = 0;
|
let maxEnd = 0;
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(id);
|
||||||
const cap = AGENT_CAPS[mode];
|
const cap = AGENT_CAPS[mode];
|
||||||
const topShift = cap.topShift(agentInfo, env, this.begin);
|
const topShift = cap.topShift(agentInfo, env, this.begin);
|
||||||
const y0 = env.primaryY - topShift;
|
const y0 = env.primaryY - topShift;
|
||||||
|
@ -319,9 +319,9 @@ define([
|
||||||
);
|
);
|
||||||
maxEnd = Math.max(maxEnd, y0 + shifts.height);
|
maxEnd = Math.max(maxEnd, y0 + shifts.height);
|
||||||
if(this.begin) {
|
if(this.begin) {
|
||||||
env.drawAgentLine(name, y0 + shifts.lineBottom);
|
env.drawAgentLine(id, y0 + shifts.lineBottom);
|
||||||
} else {
|
} else {
|
||||||
env.drawAgentLine(name, y0 + shifts.lineTop, true);
|
env.drawAgentLine(id, y0 + shifts.lineTop, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return maxEnd + env.theme.actionMargin;
|
return maxEnd + env.theme.actionMargin;
|
||||||
|
|
|
@ -6,28 +6,28 @@ define(['./BaseComponent'], (BaseComponent) => {
|
||||||
return highlighted ? env.theme.agentLineHighlightRadius : 0;
|
return highlighted ? env.theme.agentLineHighlightRadius : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
separationPre({agentNames, highlighted}, env) {
|
separationPre({agentIDs, highlighted}, env) {
|
||||||
const r = this.radius(highlighted, env);
|
const r = this.radius(highlighted, env);
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(id);
|
||||||
agentInfo.currentRad = r;
|
agentInfo.currentRad = r;
|
||||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre({agentNames, highlighted}, env) {
|
renderPre({agentIDs, highlighted}, env) {
|
||||||
const r = this.radius(highlighted, env);
|
const r = this.radius(highlighted, env);
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const agentInfo = env.agentInfos.get(name);
|
const agentInfo = env.agentInfos.get(id);
|
||||||
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
agentInfo.currentMaxRad = Math.max(agentInfo.currentMaxRad, r);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render({agentNames, highlighted}, env) {
|
render({agentIDs, highlighted}, env) {
|
||||||
const r = this.radius(highlighted, env);
|
const r = this.radius(highlighted, env);
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
env.drawAgentLine(name, env.primaryY);
|
env.drawAgentLine(id, env.primaryY);
|
||||||
env.agentInfos.get(name).currentRad = r;
|
env.agentInfos.get(id).currentRad = r;
|
||||||
});
|
});
|
||||||
return env.primaryY + env.theme.actionMargin;
|
return env.primaryY + env.theme.actionMargin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ defineDescribe('AgentHighlight', [
|
||||||
theme,
|
theme,
|
||||||
agentInfos,
|
agentInfos,
|
||||||
};
|
};
|
||||||
highlight.separationPre({agentNames: ['foo'], highlighted: true}, env);
|
highlight.separationPre({agentIDs: ['foo'], highlighted: true}, env);
|
||||||
expect(agentInfo.currentRad).toEqual(2);
|
expect(agentInfo.currentRad).toEqual(2);
|
||||||
expect(agentInfo.currentMaxRad).toEqual(2);
|
expect(agentInfo.currentMaxRad).toEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ defineDescribe('AgentHighlight', [
|
||||||
theme,
|
theme,
|
||||||
agentInfos,
|
agentInfos,
|
||||||
};
|
};
|
||||||
highlight.separationPre({agentNames: ['foo'], highlighted: true}, env);
|
highlight.separationPre({agentIDs: ['foo'], highlighted: true}, env);
|
||||||
expect(agentInfo.currentRad).toEqual(2);
|
expect(agentInfo.currentRad).toEqual(2);
|
||||||
expect(agentInfo.currentMaxRad).toEqual(3);
|
expect(agentInfo.currentMaxRad).toEqual(3);
|
||||||
});
|
});
|
||||||
|
@ -54,7 +54,7 @@ defineDescribe('AgentHighlight', [
|
||||||
theme,
|
theme,
|
||||||
agentInfos,
|
agentInfos,
|
||||||
};
|
};
|
||||||
highlight.separationPre({agentNames: ['foo'], highlighted: false}, env);
|
highlight.separationPre({agentIDs: ['foo'], highlighted: false}, env);
|
||||||
expect(agentInfo.currentRad).toEqual(0);
|
expect(agentInfo.currentRad).toEqual(0);
|
||||||
expect(agentInfo.currentMaxRad).toEqual(1);
|
expect(agentInfo.currentMaxRad).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ define(() => {
|
||||||
separationPre(/*stage, {
|
separationPre(/*stage, {
|
||||||
theme,
|
theme,
|
||||||
agentInfos,
|
agentInfos,
|
||||||
visibleAgents,
|
visibleAgentIDs,
|
||||||
textSizer,
|
textSizer,
|
||||||
addSpacing,
|
addSpacing,
|
||||||
addSeparation,
|
addSeparation,
|
||||||
|
@ -24,7 +24,7 @@ define(() => {
|
||||||
separation(/*stage, {
|
separation(/*stage, {
|
||||||
theme,
|
theme,
|
||||||
agentInfos,
|
agentInfos,
|
||||||
visibleAgents,
|
visibleAgentIDs,
|
||||||
textSizer,
|
textSizer,
|
||||||
addSpacing,
|
addSpacing,
|
||||||
addSeparation,
|
addSeparation,
|
||||||
|
@ -40,7 +40,7 @@ define(() => {
|
||||||
state,
|
state,
|
||||||
components,
|
components,
|
||||||
}*/) {
|
}*/) {
|
||||||
// return {topShift, agentNames, asynchronousY}
|
// return {topShift, agentIDs, asynchronousY}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(/*stage, {
|
render(/*stage, {
|
||||||
|
@ -64,12 +64,12 @@ define(() => {
|
||||||
|
|
||||||
BaseComponent.cleanRenderPreResult = ({
|
BaseComponent.cleanRenderPreResult = ({
|
||||||
topShift = 0,
|
topShift = 0,
|
||||||
agentNames = [],
|
agentIDs = [],
|
||||||
asynchronousY = null,
|
asynchronousY = null,
|
||||||
} = {}, currentY = null) => {
|
} = {}, currentY = null) => {
|
||||||
return {
|
return {
|
||||||
topShift,
|
topShift,
|
||||||
agentNames,
|
agentIDs,
|
||||||
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
|
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,13 +12,13 @@ define([
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class BlockSplit extends BaseComponent {
|
class BlockSplit extends BaseComponent {
|
||||||
separation({left, right, mode, label}, env) {
|
separation({left, right, tag, label}, env) {
|
||||||
const blockInfo = env.state.blocks.get(left);
|
const blockInfo = env.state.blocks.get(left);
|
||||||
const config = env.theme.getBlock(blockInfo.mode).section;
|
const config = env.theme.getBlock(blockInfo.type).section;
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(config.mode.labelAttrs, mode).width +
|
env.textSizer.measure(config.tag.labelAttrs, tag).width +
|
||||||
config.mode.padding.left +
|
config.tag.padding.left +
|
||||||
config.mode.padding.right +
|
config.tag.padding.right +
|
||||||
env.textSizer.measure(config.label.labelAttrs, label).width +
|
env.textSizer.measure(config.label.labelAttrs, label).width +
|
||||||
config.label.padding.left +
|
config.label.padding.left +
|
||||||
config.label.padding.right
|
config.label.padding.right
|
||||||
|
@ -28,13 +28,13 @@ define([
|
||||||
|
|
||||||
renderPre({left, right}) {
|
renderPre({left, right}) {
|
||||||
return {
|
return {
|
||||||
agentNames: [left, right],
|
agentIDs: [left, right],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render({left, right, mode, label}, env, first = false) {
|
render({left, right, tag, label}, env, first = false) {
|
||||||
const blockInfo = env.state.blocks.get(left);
|
const blockInfo = env.state.blocks.get(left);
|
||||||
const config = env.theme.getBlock(blockInfo.mode);
|
const config = env.theme.getBlock(blockInfo.type);
|
||||||
const agentInfoL = env.agentInfos.get(left);
|
const agentInfoL = env.agentInfos.get(left);
|
||||||
const agentInfoR = env.agentInfos.get(right);
|
const agentInfoR = env.agentInfos.get(right);
|
||||||
|
|
||||||
|
@ -46,20 +46,20 @@ define([
|
||||||
|
|
||||||
const clickable = env.makeRegion();
|
const clickable = env.makeRegion();
|
||||||
|
|
||||||
const modeRender = SVGShapes.renderBoxedText(mode, {
|
const tagRender = SVGShapes.renderBoxedText(tag, {
|
||||||
x: agentInfoL.x,
|
x: agentInfoL.x,
|
||||||
y,
|
y,
|
||||||
padding: config.section.mode.padding,
|
padding: config.section.tag.padding,
|
||||||
boxAttrs: config.section.mode.boxAttrs,
|
boxAttrs: config.section.tag.boxAttrs,
|
||||||
boxRenderer: config.section.mode.boxRenderer,
|
boxRenderer: config.section.tag.boxRenderer,
|
||||||
labelAttrs: config.section.mode.labelAttrs,
|
labelAttrs: config.section.tag.labelAttrs,
|
||||||
boxLayer: blockInfo.hold,
|
boxLayer: blockInfo.hold,
|
||||||
labelLayer: clickable,
|
labelLayer: clickable,
|
||||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelRender = SVGShapes.renderBoxedText(label, {
|
const labelRender = SVGShapes.renderBoxedText(label, {
|
||||||
x: agentInfoL.x + modeRender.width,
|
x: agentInfoL.x + tagRender.width,
|
||||||
y,
|
y,
|
||||||
padding: config.section.label.padding,
|
padding: config.section.label.padding,
|
||||||
boxAttrs: {'fill': '#000000'},
|
boxAttrs: {'fill': '#000000'},
|
||||||
|
@ -69,7 +69,7 @@ define([
|
||||||
SVGTextBlockClass: env.SVGTextBlockClass,
|
SVGTextBlockClass: env.SVGTextBlockClass,
|
||||||
});
|
});
|
||||||
|
|
||||||
const labelHeight = Math.max(modeRender.height, labelRender.height);
|
const labelHeight = Math.max(tagRender.height, labelRender.height);
|
||||||
|
|
||||||
clickable.insertBefore(svg.make('rect', {
|
clickable.insertBefore(svg.make('rect', {
|
||||||
'x': agentInfoL.x,
|
'x': agentInfoL.x,
|
||||||
|
@ -102,11 +102,13 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
storeBlockInfo(stage, env) {
|
storeBlockInfo(stage, env) {
|
||||||
env.state.blocks.set(stage.left, {
|
const blockInfo = {
|
||||||
mode: stage.mode,
|
type: stage.blockType,
|
||||||
hold: null,
|
hold: null,
|
||||||
startY: null,
|
startY: null,
|
||||||
});
|
};
|
||||||
|
env.state.blocks.set(stage.left, blockInfo);
|
||||||
|
return blockInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
separationPre(stage, env) {
|
separationPre(stage, env) {
|
||||||
|
@ -114,17 +116,17 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
separation(stage, env) {
|
separation(stage, env) {
|
||||||
array.mergeSets(env.visibleAgents, [stage.left, stage.right]);
|
array.mergeSets(env.visibleAgentIDs, [stage.left, stage.right]);
|
||||||
super.separation(stage, env);
|
super.separation(stage, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre(stage, env) {
|
renderPre(stage, env) {
|
||||||
this.storeBlockInfo(stage, env);
|
const blockInfo = this.storeBlockInfo(stage, env);
|
||||||
|
|
||||||
const config = env.theme.getBlock(stage.mode);
|
const config = env.theme.getBlock(blockInfo.type);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agentNames: [stage.left, stage.right],
|
agentIDs: [stage.left, stage.right],
|
||||||
topShift: config.margin.top,
|
topShift: config.margin.top,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -143,22 +145,22 @@ define([
|
||||||
|
|
||||||
class BlockEnd extends BaseComponent {
|
class BlockEnd extends BaseComponent {
|
||||||
separation({left, right}, env) {
|
separation({left, right}, env) {
|
||||||
array.removeAll(env.visibleAgents, [left, right]);
|
array.removeAll(env.visibleAgentIDs, [left, right]);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre({left, right}, env) {
|
renderPre({left, right}, env) {
|
||||||
const blockInfo = env.state.blocks.get(left);
|
const blockInfo = env.state.blocks.get(left);
|
||||||
const config = env.theme.getBlock(blockInfo.mode);
|
const config = env.theme.getBlock(blockInfo.type);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agentNames: [left, right],
|
agentIDs: [left, right],
|
||||||
topShift: config.section.padding.bottom,
|
topShift: config.section.padding.bottom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render({left, right}, env) {
|
render({left, right}, env) {
|
||||||
const blockInfo = env.state.blocks.get(left);
|
const blockInfo = env.state.blocks.get(left);
|
||||||
const config = env.theme.getBlock(blockInfo.mode);
|
const config = env.theme.getBlock(blockInfo.type);
|
||||||
const agentInfoL = env.agentInfos.get(left);
|
const agentInfoL = env.agentInfos.get(left);
|
||||||
const agentInfoR = env.agentInfos.get(right);
|
const agentInfoR = env.agentInfos.get(right);
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ define([
|
||||||
];
|
];
|
||||||
|
|
||||||
class Connect extends BaseComponent {
|
class Connect extends BaseComponent {
|
||||||
separation({label, agentNames, options}, env) {
|
separation({label, agentIDs, options}, env) {
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
|
|
||||||
const lArrow = ARROWHEADS[options.left];
|
const lArrow = ARROWHEADS[options.left];
|
||||||
|
@ -90,9 +90,9 @@ define([
|
||||||
labelWidth += config.label.padding * 2;
|
labelWidth += config.label.padding * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const info1 = env.agentInfos.get(agentNames[0]);
|
const info1 = env.agentInfos.get(agentIDs[0]);
|
||||||
if(agentNames[0] === agentNames[1]) {
|
if(agentIDs[0] === agentIDs[1]) {
|
||||||
env.addSpacing(agentNames[0], {
|
env.addSpacing(agentIDs[0], {
|
||||||
left: 0,
|
left: 0,
|
||||||
right: (
|
right: (
|
||||||
info1.currentMaxRad +
|
info1.currentMaxRad +
|
||||||
|
@ -104,10 +104,10 @@ define([
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const info2 = env.agentInfos.get(agentNames[1]);
|
const info2 = env.agentInfos.get(agentIDs[1]);
|
||||||
env.addSeparation(
|
env.addSeparation(
|
||||||
agentNames[0],
|
agentIDs[0],
|
||||||
agentNames[1],
|
agentIDs[1],
|
||||||
|
|
||||||
info1.currentMaxRad +
|
info1.currentMaxRad +
|
||||||
info2.currentMaxRad +
|
info2.currentMaxRad +
|
||||||
|
@ -120,10 +120,10 @@ define([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSelfConnect({label, agentNames, options}, env) {
|
renderSelfConnect({label, agentIDs, options}, env) {
|
||||||
/* jshint -W071 */ // TODO: find appropriate abstractions
|
/* 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(agentIDs[0]);
|
||||||
|
|
||||||
const lArrow = ARROWHEADS[options.left];
|
const lArrow = ARROWHEADS[options.left];
|
||||||
const rArrow = ARROWHEADS[options.right];
|
const rArrow = ARROWHEADS[options.right];
|
||||||
|
@ -195,10 +195,10 @@ define([
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSimpleConnect({label, agentNames, options}, env) {
|
renderSimpleConnect({label, agentIDs, options}, env) {
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
const from = env.agentInfos.get(agentNames[0]);
|
const from = env.agentInfos.get(agentIDs[0]);
|
||||||
const to = env.agentInfos.get(agentNames[1]);
|
const to = env.agentInfos.get(agentIDs[1]);
|
||||||
|
|
||||||
const lArrow = ARROWHEADS[options.left];
|
const lArrow = ARROWHEADS[options.left];
|
||||||
const rArrow = ARROWHEADS[options.right];
|
const rArrow = ARROWHEADS[options.right];
|
||||||
|
@ -260,7 +260,7 @@ define([
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPre({label, agentNames, options}, env) {
|
renderPre({label, agentIDs, options}, env) {
|
||||||
const config = env.theme.connect;
|
const config = env.theme.connect;
|
||||||
|
|
||||||
const lArrow = ARROWHEADS[options.left];
|
const lArrow = ARROWHEADS[options.left];
|
||||||
|
@ -273,18 +273,18 @@ define([
|
||||||
);
|
);
|
||||||
|
|
||||||
let arrowH = lArrow.height(env.theme);
|
let arrowH = lArrow.height(env.theme);
|
||||||
if(agentNames[0] !== agentNames[1]) {
|
if(agentIDs[0] !== agentIDs[1]) {
|
||||||
arrowH = Math.max(arrowH, rArrow.height(env.theme));
|
arrowH = Math.max(arrowH, rArrow.height(env.theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agentNames,
|
agentIDs,
|
||||||
topShift: Math.max(arrowH / 2, height),
|
topShift: Math.max(arrowH / 2, height),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
render(stage, env) {
|
render(stage, env) {
|
||||||
if(stage.agentNames[0] === stage.agentNames[1]) {
|
if(stage.agentIDs[0] === stage.agentIDs[1]) {
|
||||||
return this.renderSelfConnect(stage, env);
|
return this.renderSelfConnect(stage, env);
|
||||||
} else {
|
} else {
|
||||||
return this.renderSimpleConnect(stage, env);
|
return this.renderSimpleConnect(stage, env);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function findExtremes(agentInfos, agentNames) {
|
function findExtremes(agentInfos, agentIDs) {
|
||||||
let min = null;
|
let min = null;
|
||||||
let max = null;
|
let max = null;
|
||||||
agentNames.forEach((name) => {
|
agentIDs.forEach((id) => {
|
||||||
const info = agentInfos.get(name);
|
const info = agentInfos.get(id);
|
||||||
if(min === null || info.index < min.index) {
|
if(min === null || info.index < min.index) {
|
||||||
min = info;
|
min = info;
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,14 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
left: min.label,
|
left: min.id,
|
||||||
right: max.label,
|
right: max.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteComponent extends BaseComponent {
|
class NoteComponent extends BaseComponent {
|
||||||
renderPre({agentNames}) {
|
renderPre({agentIDs}) {
|
||||||
return {agentNames};
|
return {agentIDs};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNote({
|
renderNote({
|
||||||
|
@ -39,7 +39,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
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(clickable, {
|
const labelNode = new env.SVGTextBlockClass(clickable, {
|
||||||
attrs: config.labelAttrs,
|
attrs: config.labelAttrs,
|
||||||
text: label,
|
formatted: label,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteOver extends NoteComponent {
|
class NoteOver extends NoteComponent {
|
||||||
separation({agentNames, mode, label}, env) {
|
separation({agentIDs, mode, label}, env) {
|
||||||
const config = env.theme.getNote(mode);
|
const config = env.theme.getNote(mode);
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(config.labelAttrs, label).width +
|
env.textSizer.measure(config.labelAttrs, label).width +
|
||||||
|
@ -113,7 +113,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
config.padding.right
|
config.padding.right
|
||||||
);
|
);
|
||||||
|
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
if(infoL !== infoR) {
|
if(infoL !== infoR) {
|
||||||
|
@ -132,10 +132,10 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({agentNames, mode, label}, env) {
|
render({agentIDs, mode, label}, env) {
|
||||||
const config = env.theme.getNote(mode);
|
const config = env.theme.getNote(mode);
|
||||||
|
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
if(infoL !== infoR) {
|
if(infoL !== infoR) {
|
||||||
|
@ -164,9 +164,9 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
this.isRight = isRight;
|
this.isRight = isRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
separation({agentNames, mode, label}, env) {
|
separation({agentIDs, mode, label}, env) {
|
||||||
const config = env.theme.getNote(mode);
|
const config = env.theme.getNote(mode);
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
const width = (
|
const width = (
|
||||||
env.textSizer.measure(config.labelAttrs, label).width +
|
env.textSizer.measure(config.labelAttrs, label).width +
|
||||||
config.padding.left +
|
config.padding.left +
|
||||||
|
@ -190,9 +190,9 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({agentNames, mode, label}, env) {
|
render({agentIDs, mode, label}, env) {
|
||||||
const config = env.theme.getNote(mode);
|
const config = env.theme.getNote(mode);
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
if(this.isRight) {
|
if(this.isRight) {
|
||||||
const info = env.agentInfos.get(right);
|
const info = env.agentInfos.get(right);
|
||||||
const x0 = info.x + info.currentMaxRad + config.margin.left;
|
const x0 = info.x + info.currentMaxRad + config.margin.left;
|
||||||
|
@ -216,9 +216,9 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteBetween extends NoteComponent {
|
class NoteBetween extends NoteComponent {
|
||||||
separation({agentNames, mode, label}, env) {
|
separation({agentIDs, mode, label}, env) {
|
||||||
const config = env.theme.getNote(mode);
|
const config = env.theme.getNote(mode);
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
|
|
||||||
|
@ -236,8 +236,8 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({agentNames, mode, label}, env) {
|
render({agentIDs, mode, label}, env) {
|
||||||
const {left, right} = findExtremes(env.agentInfos, agentNames);
|
const {left, right} = findExtremes(env.agentInfos, agentIDs);
|
||||||
const infoL = env.agentInfos.get(left);
|
const infoL = env.agentInfos.get(left);
|
||||||
const infoR = env.agentInfos.get(right);
|
const infoR = env.agentInfos.get(right);
|
||||||
const xMid = (
|
const xMid = (
|
||||||
|
|
|
@ -18,10 +18,10 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeResults(a, b) {
|
function mergeResults(a, b) {
|
||||||
array.mergeSets(a.agentNames, b.agentNames);
|
array.mergeSets(a.agentIDs, b.agentIDs);
|
||||||
return {
|
return {
|
||||||
topShift: Math.max(a.topShift, b.topShift),
|
topShift: Math.max(a.topShift, b.topShift),
|
||||||
agentNames: a.agentNames,
|
agentIDs: a.agentIDs,
|
||||||
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
|
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ define([
|
||||||
renderPre(stage, env) {
|
renderPre(stage, env) {
|
||||||
const baseResults = {
|
const baseResults = {
|
||||||
topShift: 0,
|
topShift: 0,
|
||||||
agentNames: [],
|
agentIDs: [],
|
||||||
asynchronousY: null,
|
asynchronousY: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ define([
|
||||||
top: 3,
|
top: 3,
|
||||||
bottom: 2,
|
bottom: 2,
|
||||||
},
|
},
|
||||||
mode: {
|
tag: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 1,
|
top: 1,
|
||||||
left: 3,
|
left: 3,
|
||||||
|
|
|
@ -180,7 +180,7 @@ define([
|
||||||
top: 3,
|
top: 3,
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
},
|
},
|
||||||
mode: {
|
tag: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 2,
|
top: 2,
|
||||||
left: 5,
|
left: 5,
|
||||||
|
|
|
@ -178,7 +178,7 @@ define([
|
||||||
top: 3,
|
top: 3,
|
||||||
bottom: 2,
|
bottom: 2,
|
||||||
},
|
},
|
||||||
mode: {
|
tag: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 2,
|
top: 2,
|
||||||
left: 4,
|
left: 4,
|
||||||
|
|
|
@ -164,7 +164,7 @@ define([
|
||||||
top: 3,
|
top: 3,
|
||||||
bottom: 2,
|
bottom: 2,
|
||||||
},
|
},
|
||||||
mode: {
|
tag: {
|
||||||
padding: {
|
padding: {
|
||||||
top: 2,
|
top: 2,
|
||||||
left: 3,
|
left: 3,
|
||||||
|
@ -376,8 +376,8 @@ define([
|
||||||
|
|
||||||
this.blocks.ref.boxRenderer = this.renderRefBlock.bind(this);
|
this.blocks.ref.boxRenderer = this.renderRefBlock.bind(this);
|
||||||
this.blocks[''].boxRenderer = this.renderBlock.bind(this);
|
this.blocks[''].boxRenderer = this.renderBlock.bind(this);
|
||||||
this.blocks.ref.section.mode.boxRenderer = this.renderTag;
|
this.blocks.ref.section.tag.boxRenderer = this.renderTag;
|
||||||
this.blocks[''].section.mode.boxRenderer = this.renderTag;
|
this.blocks[''].section.tag.boxRenderer = this.renderTag;
|
||||||
this.blocks[''].sepRenderer = this.renderSeparator.bind(this);
|
this.blocks[''].sepRenderer = this.renderSeparator.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ define([
|
||||||
'sequence/SequenceDiagram_spec',
|
'sequence/SequenceDiagram_spec',
|
||||||
'sequence/Tokeniser_spec',
|
'sequence/Tokeniser_spec',
|
||||||
'sequence/Parser_spec',
|
'sequence/Parser_spec',
|
||||||
|
'sequence/MarkdownParser_spec',
|
||||||
'sequence/LabelPatternParser_spec',
|
'sequence/LabelPatternParser_spec',
|
||||||
'sequence/Generator_spec',
|
'sequence/Generator_spec',
|
||||||
'sequence/Renderer_spec',
|
'sequence/Renderer_spec',
|
||||||
|
|
|
@ -14,12 +14,14 @@ define(['svg/SVGUtilities'], (svg) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMPTY = [];
|
||||||
|
|
||||||
class SVGTextBlock {
|
class SVGTextBlock {
|
||||||
constructor(container, initialState = {}) {
|
constructor(container, initialState = {}) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.state = {
|
this.state = {
|
||||||
attrs: {},
|
attrs: {},
|
||||||
text: '',
|
formatted: EMPTY,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
};
|
};
|
||||||
|
@ -57,18 +59,19 @@ define(['svg/SVGUtilities'], (svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderText() {
|
_renderText() {
|
||||||
if(!this.state.text) {
|
if(!this.state.formatted) {
|
||||||
this._reset();
|
this._reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = this.state.text.split('\n');
|
const formatted = this.state.formatted;
|
||||||
this._rebuildNodes(lines.length);
|
this._rebuildNodes(formatted.length);
|
||||||
|
|
||||||
let maxWidth = 0;
|
let maxWidth = 0;
|
||||||
this.nodes.forEach(({text, element}, i) => {
|
this.nodes.forEach(({text, element}, i) => {
|
||||||
text.nodeValue = lines[i];
|
const ln = formatted[i].reduce((v, pt) => v + pt.text, '');
|
||||||
maxWidth = Math.max(maxWidth, lines[i].length);
|
text.nodeValue = ln;
|
||||||
|
maxWidth = Math.max(maxWidth, ln.length);
|
||||||
});
|
});
|
||||||
this.width = maxWidth;
|
this.width = maxWidth;
|
||||||
}
|
}
|
||||||
|
@ -100,12 +103,12 @@ define(['svg/SVGUtilities'], (svg) => {
|
||||||
|
|
||||||
if(this.state.attrs !== oldState.attrs) {
|
if(this.state.attrs !== oldState.attrs) {
|
||||||
this._reset();
|
this._reset();
|
||||||
oldState.text = '';
|
oldState.formatted = EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldNodes = this.nodes.length;
|
const oldNodes = this.nodes.length;
|
||||||
|
|
||||||
if(this.state.text !== oldState.text) {
|
if(this.state.formatted !== oldState.formatted) {
|
||||||
this._renderText();
|
this._renderText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,29 +123,29 @@ define(['svg/SVGUtilities'], (svg) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SizeTester {
|
class SizeTester {
|
||||||
measure(attrs, content) {
|
measure(attrs, formatted) {
|
||||||
if(!content) {
|
if(!formatted || !formatted.length) {
|
||||||
return {width: 0, height: 0};
|
return {width: 0, height: 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = content.split('\n');
|
|
||||||
let width = 0;
|
let width = 0;
|
||||||
lines.forEach((line) => {
|
formatted.forEach((line) => {
|
||||||
width = Math.max(width, line.length);
|
const length = line.reduce((v, pt) => v + pt.text.length, 0);
|
||||||
|
width = Math.max(width, length);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
height: lines.length,
|
height: formatted.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
measureHeight(attrs, content) {
|
measureHeight(attrs, formatted) {
|
||||||
if(!content) {
|
if(!formatted) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return content.split('\n').length;
|
return formatted.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCache() {
|
resetCache() {
|
||||||
|
|
|
@ -46,24 +46,10 @@ define([
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBoxedText(text, {
|
function calculateAnchor(x, attrs, padding) {
|
||||||
x,
|
|
||||||
y,
|
|
||||||
padding,
|
|
||||||
boxAttrs,
|
|
||||||
labelAttrs,
|
|
||||||
boxLayer,
|
|
||||||
labelLayer,
|
|
||||||
boxRenderer = null,
|
|
||||||
SVGTextBlockClass = SVGTextBlock,
|
|
||||||
}) {
|
|
||||||
if(!text) {
|
|
||||||
return {width: 0, height: 0, label: null, box: null};
|
|
||||||
}
|
|
||||||
|
|
||||||
let shift = 0;
|
let shift = 0;
|
||||||
let anchorX = x;
|
let anchorX = x;
|
||||||
switch(labelAttrs['text-anchor']) {
|
switch(attrs['text-anchor']) {
|
||||||
case 'middle':
|
case 'middle':
|
||||||
shift = 0.5;
|
shift = 0.5;
|
||||||
anchorX += (padding.left - padding.right) / 2;
|
anchorX += (padding.left - padding.right) / 2;
|
||||||
|
@ -77,10 +63,29 @@ define([
|
||||||
anchorX += padding.left;
|
anchorX += padding.left;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return {shift, anchorX};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBoxedText(formatted, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
padding,
|
||||||
|
boxAttrs,
|
||||||
|
labelAttrs,
|
||||||
|
boxLayer,
|
||||||
|
labelLayer,
|
||||||
|
boxRenderer = null,
|
||||||
|
SVGTextBlockClass = SVGTextBlock,
|
||||||
|
}) {
|
||||||
|
if(!formatted || !formatted.length) {
|
||||||
|
return {width: 0, height: 0, label: null, box: null};
|
||||||
|
}
|
||||||
|
|
||||||
|
const {shift, anchorX} = calculateAnchor(x, labelAttrs, padding);
|
||||||
|
|
||||||
const label = new SVGTextBlockClass(labelLayer, {
|
const label = new SVGTextBlockClass(labelLayer, {
|
||||||
attrs: labelAttrs,
|
attrs: labelAttrs,
|
||||||
text,
|
formatted,
|
||||||
x: anchorX,
|
x: anchorX,
|
||||||
y: y + padding.top,
|
y: y + padding.top,
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,7 +56,7 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
describe('renderBoxedText', () => {
|
describe('renderBoxedText', () => {
|
||||||
it('renders a label', () => {
|
it('renders a label', () => {
|
||||||
const o = document.createElement('p');
|
const o = document.createElement('p');
|
||||||
const rendered = SVGShapes.renderBoxedText('foo', {
|
const rendered = SVGShapes.renderBoxedText([[{text: 'foo'}]], {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 2,
|
y: 2,
|
||||||
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
@ -65,7 +65,7 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
boxLayer: o,
|
boxLayer: o,
|
||||||
labelLayer: o,
|
labelLayer: o,
|
||||||
});
|
});
|
||||||
expect(rendered.label.state.text).toEqual('foo');
|
expect(rendered.label.state.formatted).toEqual([[{text: 'foo'}]]);
|
||||||
expect(rendered.label.state.x).toEqual(5);
|
expect(rendered.label.state.x).toEqual(5);
|
||||||
expect(rendered.label.state.y).toEqual(10);
|
expect(rendered.label.state.y).toEqual(10);
|
||||||
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
expect(rendered.label.firstLine().parentNode).toEqual(o);
|
||||||
|
@ -73,7 +73,7 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
|
|
||||||
it('positions a box beneath the rendered label', () => {
|
it('positions a box beneath the rendered label', () => {
|
||||||
const o = document.createElement('p');
|
const o = document.createElement('p');
|
||||||
const rendered = SVGShapes.renderBoxedText('foo', {
|
const rendered = SVGShapes.renderBoxedText([[{text: 'foo'}]], {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 2,
|
y: 2,
|
||||||
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
@ -91,7 +91,7 @@ defineDescribe('SVGShapes', ['./SVGShapes'], (SVGShapes) => {
|
||||||
|
|
||||||
it('returns the size of the rendered box', () => {
|
it('returns the size of the rendered box', () => {
|
||||||
const o = document.createElement('p');
|
const o = document.createElement('p');
|
||||||
const rendered = SVGShapes.renderBoxedText('foo', {
|
const rendered = SVGShapes.renderBoxedText([[{text: 'foo'}]], {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 2,
|
y: 2,
|
||||||
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
padding: {left: 4, top: 8, right: 16, bottom: 32},
|
||||||
|
|
|
@ -23,84 +23,107 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function populateSvgTextLine(node, formattedLine) {
|
||||||
|
if(!Array.isArray(formattedLine)) {
|
||||||
|
throw new Error('Invalid formatted text line: ' + formattedLine);
|
||||||
|
}
|
||||||
|
formattedLine.forEach(({text, attrs}) => {
|
||||||
|
const textNode = svg.makeText(text);
|
||||||
|
if(attrs) {
|
||||||
|
const span = svg.make('tspan', attrs);
|
||||||
|
span.appendChild(textNode);
|
||||||
|
node.appendChild(span);
|
||||||
|
} else {
|
||||||
|
node.appendChild(textNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY = [];
|
||||||
|
|
||||||
class SVGTextBlock {
|
class SVGTextBlock {
|
||||||
constructor(container, initialState = {}) {
|
constructor(container, initialState = {}) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.state = {
|
this.state = {
|
||||||
attrs: {},
|
attrs: {},
|
||||||
text: '',
|
formatted: EMPTY,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
};
|
};
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
this.nodes = [];
|
this.lines = [];
|
||||||
this.set(initialState);
|
this.set(initialState);
|
||||||
}
|
}
|
||||||
|
|
||||||
_rebuildNodes(count) {
|
_rebuildLines(count) {
|
||||||
if(count > this.nodes.length) {
|
if(count > this.lines.length) {
|
||||||
const attrs = Object.assign({
|
const attrs = Object.assign({
|
||||||
'x': this.state.x,
|
'x': this.state.x,
|
||||||
}, this.state.attrs);
|
}, this.state.attrs);
|
||||||
|
|
||||||
while(this.nodes.length < count) {
|
while(this.lines.length < count) {
|
||||||
const element = svg.make('text', attrs);
|
const node = svg.make('text', attrs);
|
||||||
const text = svg.makeText();
|
this.container.appendChild(node);
|
||||||
element.appendChild(text);
|
this.lines.push({node, latest: ''});
|
||||||
this.container.appendChild(element);
|
|
||||||
this.nodes.push({element, text});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while(this.nodes.length > count) {
|
while(this.lines.length > count) {
|
||||||
const {element} = this.nodes.pop();
|
const {node} = this.lines.pop();
|
||||||
this.container.removeChild(element);
|
this.container.removeChild(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_reset() {
|
_reset() {
|
||||||
this._rebuildNodes(0);
|
this._rebuildLines(0);
|
||||||
this.width = 0;
|
this.width = 0;
|
||||||
this.height = 0;
|
this.height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderText() {
|
_renderText() {
|
||||||
if(!this.state.text) {
|
const {formatted} = this.state;
|
||||||
|
|
||||||
|
if(!formatted || !formatted.length) {
|
||||||
this._reset();
|
this._reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(!Array.isArray(formatted)) {
|
||||||
|
throw new Error('Invalid formatted text: ' + formatted);
|
||||||
|
}
|
||||||
|
|
||||||
const lines = this.state.text.split('\n');
|
this._rebuildLines(formatted.length);
|
||||||
this._rebuildNodes(lines.length);
|
|
||||||
|
|
||||||
let maxWidth = 0;
|
let maxWidth = 0;
|
||||||
this.nodes.forEach(({text, element}, i) => {
|
this.lines.forEach((ln, i) => {
|
||||||
if(text.nodeValue !== lines[i]) {
|
const id = JSON.stringify(formatted[i]);
|
||||||
text.nodeValue = lines[i];
|
if(id !== ln.latest) {
|
||||||
|
svg.empty(ln.node);
|
||||||
|
populateSvgTextLine(ln.node, formatted[i]);
|
||||||
|
ln.latest = id;
|
||||||
}
|
}
|
||||||
maxWidth = Math.max(maxWidth, element.getComputedTextLength());
|
maxWidth = Math.max(maxWidth, ln.node.getComputedTextLength());
|
||||||
});
|
});
|
||||||
this.width = maxWidth;
|
this.width = maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateX() {
|
_updateX() {
|
||||||
this.nodes.forEach(({element}) => {
|
this.lines.forEach(({node}) => {
|
||||||
element.setAttribute('x', this.state.x);
|
node.setAttribute('x', this.state.x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateY() {
|
_updateY() {
|
||||||
const {size, lineHeight} = fontDetails(this.state.attrs);
|
const {size, lineHeight} = fontDetails(this.state.attrs);
|
||||||
this.nodes.forEach(({element}, i) => {
|
this.lines.forEach(({node}, i) => {
|
||||||
element.setAttribute('y', this.state.y + i * lineHeight + size);
|
node.setAttribute('y', this.state.y + i * lineHeight + size);
|
||||||
});
|
});
|
||||||
this.height = lineHeight * this.nodes.length;
|
this.height = lineHeight * this.lines.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
firstLine() {
|
firstLine() {
|
||||||
if(this.nodes.length > 0) {
|
if(this.lines.length > 0) {
|
||||||
return this.nodes[0].element;
|
return this.lines[0].node;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -112,12 +135,12 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
|
|
||||||
if(this.state.attrs !== oldState.attrs) {
|
if(this.state.attrs !== oldState.attrs) {
|
||||||
this._reset();
|
this._reset();
|
||||||
oldState.text = '';
|
oldState.formatted = EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldNodes = this.nodes.length;
|
const oldLines = this.lines.length;
|
||||||
|
|
||||||
if(this.state.text !== oldState.text) {
|
if(this.state.formatted !== oldState.formatted) {
|
||||||
this._renderText();
|
this._renderText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +148,7 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this._updateX();
|
this._updateX();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.state.y !== oldState.y || this.nodes.length !== oldNodes) {
|
if(this.state.y !== oldState.y || this.lines.length !== oldLines) {
|
||||||
this._updateY();
|
this._updateY();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,18 +165,18 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this.cache = new Map();
|
this.cache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
measure(attrs, content) {
|
measure(attrs, formatted) {
|
||||||
if(!content) {
|
if(!formatted || !formatted.length) {
|
||||||
return {width: 0, height: 0};
|
return {width: 0, height: 0};
|
||||||
}
|
}
|
||||||
|
if(!Array.isArray(formatted)) {
|
||||||
|
throw new Error('Invalid formatted text: ' + formatted);
|
||||||
|
}
|
||||||
|
|
||||||
let tester = this.cache.get(attrs);
|
let tester = this.cache.get(attrs);
|
||||||
if(!tester) {
|
if(!tester) {
|
||||||
const text = svg.makeText();
|
tester = svg.make('text', attrs);
|
||||||
const node = svg.make('text', attrs);
|
this.testers.appendChild(tester);
|
||||||
node.appendChild(text);
|
|
||||||
this.testers.appendChild(node);
|
|
||||||
tester = {text, node};
|
|
||||||
this.cache.set(attrs, tester);
|
this.cache.set(attrs, tester);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,26 +184,28 @@ define(['./SVGUtilities'], (svg) => {
|
||||||
this.container.appendChild(this.testers);
|
this.container.appendChild(this.testers);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = content.split('\n');
|
|
||||||
let width = 0;
|
let width = 0;
|
||||||
lines.forEach((line) => {
|
formatted.forEach((line) => {
|
||||||
tester.text.nodeValue = line;
|
svg.empty(tester);
|
||||||
width = Math.max(width, tester.node.getComputedTextLength());
|
populateSvgTextLine(tester, line);
|
||||||
|
width = Math.max(width, tester.getComputedTextLength());
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
height: lines.length * fontDetails(attrs).lineHeight,
|
height: formatted.length * fontDetails(attrs).lineHeight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
measureHeight(attrs, content) {
|
measureHeight(attrs, formatted) {
|
||||||
if(!content) {
|
if(!formatted) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if(!Array.isArray(formatted)) {
|
||||||
|
throw new Error('Invalid formatted text: ' + formatted);
|
||||||
|
}
|
||||||
|
|
||||||
const lines = content.split('\n');
|
return formatted.length * fontDetails(attrs).lineHeight;
|
||||||
return lines.length * fontDetails(attrs).lineHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCache() {
|
resetCache() {
|
||||||
|
|
|
@ -23,7 +23,7 @@ defineDescribe('SVGTextBlock', [
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
it('defaults to blank text at 0, 0', () => {
|
it('defaults to blank text at 0, 0', () => {
|
||||||
expect(block.state.text).toEqual('');
|
expect(block.state.formatted).toEqual([]);
|
||||||
expect(block.state.x).toEqual(0);
|
expect(block.state.x).toEqual(0);
|
||||||
expect(block.state.y).toEqual(0);
|
expect(block.state.y).toEqual(0);
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
|
@ -31,15 +31,18 @@ defineDescribe('SVGTextBlock', [
|
||||||
|
|
||||||
it('does not explode if given no setup', () => {
|
it('does not explode if given no setup', () => {
|
||||||
block = new SVGTextBlock(hold);
|
block = new SVGTextBlock(hold);
|
||||||
expect(block.state.text).toEqual('');
|
expect(block.state.formatted).toEqual([]);
|
||||||
expect(block.state.x).toEqual(0);
|
expect(block.state.x).toEqual(0);
|
||||||
expect(block.state.y).toEqual(0);
|
expect(block.state.y).toEqual(0);
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the given text if specified', () => {
|
it('adds the given formatted text if specified', () => {
|
||||||
block = new SVGTextBlock(hold, {attrs, text: 'abc'});
|
block = new SVGTextBlock(hold, {
|
||||||
expect(block.state.text).toEqual('abc');
|
attrs,
|
||||||
|
formatted: [[{text: 'abc'}]],
|
||||||
|
});
|
||||||
|
expect(block.state.formatted).toEqual([[{text: 'abc'}]]);
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -52,31 +55,35 @@ defineDescribe('SVGTextBlock', [
|
||||||
|
|
||||||
describe('.set', () => {
|
describe('.set', () => {
|
||||||
it('sets the text to the given content', () => {
|
it('sets the text to the given content', () => {
|
||||||
block.set({text: 'foo'});
|
block.set({formatted: [[{text: 'foo'}]]});
|
||||||
expect(block.state.text).toEqual('foo');
|
expect(block.state.formatted).toEqual([[{text: 'foo'}]]);
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders multiline text', () => {
|
it('renders multiline text', () => {
|
||||||
block.set({text: 'foo\nbar'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
expect(hold.children.length).toEqual(2);
|
expect(hold.children.length).toEqual(2);
|
||||||
expect(hold.children[0].innerHTML).toEqual('foo');
|
expect(hold.children[0].innerHTML).toEqual('foo');
|
||||||
expect(hold.children[1].innerHTML).toEqual('bar');
|
expect(hold.children[1].innerHTML).toEqual('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('populates width and height with the size of the text', () => {
|
it('populates width and height with the size of the text', () => {
|
||||||
block.set({text: 'foo\nbar'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
expect(block.width).toBeGreaterThan(0);
|
expect(block.width).toBeGreaterThan(0);
|
||||||
expect(block.height).toEqual(30);
|
expect(block.height).toEqual(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('re-uses text nodes when possible, adding more if needed', () => {
|
it('re-uses text nodes when possible, adding more if needed', () => {
|
||||||
block.set({text: 'foo\nbar'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
const line0 = hold.children[0];
|
const line0 = hold.children[0];
|
||||||
const line1 = hold.children[1];
|
const line1 = hold.children[1];
|
||||||
|
|
||||||
block.set({text: 'zig\nzag\nbaz'});
|
block.set({formatted: [
|
||||||
|
[{text: 'zig'}],
|
||||||
|
[{text: 'zag'}],
|
||||||
|
[{text: 'baz'}],
|
||||||
|
]});
|
||||||
|
|
||||||
expect(hold.children.length).toEqual(3);
|
expect(hold.children.length).toEqual(3);
|
||||||
expect(hold.children[0]).toEqual(line0);
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
@ -87,10 +94,10 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('re-uses text nodes when possible, removing extra if needed', () => {
|
it('re-uses text nodes when possible, removing extra if needed', () => {
|
||||||
block.set({text: 'foo\nbar'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
const line0 = hold.children[0];
|
const line0 = hold.children[0];
|
||||||
|
|
||||||
block.set({text: 'zig'});
|
block.set({formatted: [[{text: 'zig'}]]});
|
||||||
|
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
expect(hold.children[0]).toEqual(line0);
|
expect(hold.children[0]).toEqual(line0);
|
||||||
|
@ -98,7 +105,7 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('positions text nodes and applies attributes', () => {
|
it('positions text nodes and applies attributes', () => {
|
||||||
block.set({text: 'foo\nbar'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
expect(hold.children.length).toEqual(2);
|
expect(hold.children.length).toEqual(2);
|
||||||
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
expect(hold.children[0].getAttribute('x')).toEqual('0');
|
||||||
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
expect(hold.children[0].getAttribute('y')).toEqual('10');
|
||||||
|
@ -109,7 +116,7 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves all nodes', () => {
|
it('moves all nodes', () => {
|
||||||
block.set({text: 'foo\nbaz'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
block.set({x: 5, y: 7});
|
block.set({x: 5, y: 7});
|
||||||
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
expect(hold.children[0].getAttribute('x')).toEqual('5');
|
||||||
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
expect(hold.children[0].getAttribute('y')).toEqual('17');
|
||||||
|
@ -118,10 +125,10 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears if the text is empty', () => {
|
it('clears if the text is empty', () => {
|
||||||
block.set({text: 'foo\nbaz'});
|
block.set({formatted: [[{text: 'foo'}], [{text: 'bar'}]]});
|
||||||
block.set({text: ''});
|
block.set({formatted: []});
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
expect(block.state.text).toEqual('');
|
expect(block.state.formatted).toEqual([]);
|
||||||
expect(block.width).toEqual(0);
|
expect(block.width).toEqual(0);
|
||||||
expect(block.height).toEqual(0);
|
expect(block.height).toEqual(0);
|
||||||
});
|
});
|
||||||
|
@ -135,28 +142,39 @@ defineDescribe('SVGTextBlock', [
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.measure', () => {
|
describe('.measure', () => {
|
||||||
it('calculates the size of the rendered text', () => {
|
it('calculates the size of the formatted text', () => {
|
||||||
const size = tester.measure(attrs, 'foo');
|
const size = tester.measure(attrs, [[{text: 'foo'}]]);
|
||||||
expect(size.width).toBeGreaterThan(0);
|
expect(size.width).toBeGreaterThan(0);
|
||||||
expect(size.height).toEqual(15);
|
expect(size.height).toEqual(15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('measures multiline text', () => {
|
it('measures multiline text', () => {
|
||||||
const size = tester.measure(attrs, 'foo\nbar');
|
const size = tester.measure(attrs, [
|
||||||
|
[{text: 'foo'}],
|
||||||
|
[{text: 'bar'}],
|
||||||
|
]);
|
||||||
expect(size.width).toBeGreaterThan(0);
|
expect(size.width).toBeGreaterThan(0);
|
||||||
expect(size.height).toEqual(30);
|
expect(size.height).toEqual(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 0, 0 for empty content', () => {
|
it('returns 0, 0 for empty content', () => {
|
||||||
const size = tester.measure(attrs, '');
|
const size = tester.measure(attrs, []);
|
||||||
expect(size.width).toEqual(0);
|
expect(size.width).toEqual(0);
|
||||||
expect(size.height).toEqual(0);
|
expect(size.height).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the maximum width for multiline text', () => {
|
it('returns the maximum width for multiline text', () => {
|
||||||
const size0 = tester.measure(attrs, 'foo');
|
const size0 = tester.measure(attrs, [
|
||||||
const size1 = tester.measure(attrs, 'longline');
|
[{text: 'foo'}],
|
||||||
const size = tester.measure(attrs, 'foo\nlongline\nfoo');
|
]);
|
||||||
|
const size1 = tester.measure(attrs, [
|
||||||
|
[{text: 'longline'}],
|
||||||
|
]);
|
||||||
|
const size = tester.measure(attrs, [
|
||||||
|
[{text: 'foo'}],
|
||||||
|
[{text: 'longline'}],
|
||||||
|
[{text: 'foo'}],
|
||||||
|
]);
|
||||||
expect(size1.width).toBeGreaterThan(size0.width);
|
expect(size1.width).toBeGreaterThan(size0.width);
|
||||||
expect(size.width).toEqual(size1.width);
|
expect(size.width).toEqual(size1.width);
|
||||||
});
|
});
|
||||||
|
@ -164,39 +182,44 @@ defineDescribe('SVGTextBlock', [
|
||||||
|
|
||||||
describe('.measureHeight', () => {
|
describe('.measureHeight', () => {
|
||||||
it('calculates the height of the rendered text', () => {
|
it('calculates the height of the rendered text', () => {
|
||||||
const height = tester.measureHeight(attrs, 'foo');
|
const height = tester.measureHeight(attrs, [
|
||||||
|
[{text: 'foo'}],
|
||||||
|
]);
|
||||||
expect(height).toEqual(15);
|
expect(height).toEqual(15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('measures multiline text', () => {
|
it('measures multiline text', () => {
|
||||||
const height = tester.measureHeight(attrs, 'foo\nbar');
|
const height = tester.measureHeight(attrs, [
|
||||||
|
[{text: 'foo'}],
|
||||||
|
[{text: 'bar'}],
|
||||||
|
]);
|
||||||
expect(height).toEqual(30);
|
expect(height).toEqual(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 0 for empty content', () => {
|
it('returns 0 for empty content', () => {
|
||||||
const height = tester.measureHeight(attrs, '');
|
const height = tester.measureHeight(attrs, []);
|
||||||
expect(height).toEqual(0);
|
expect(height).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not require the container', () => {
|
it('does not require the container', () => {
|
||||||
tester.measureHeight(attrs, 'foo');
|
tester.measureHeight(attrs, [[{text: 'foo'}]]);
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.detach', () => {
|
describe('.detach', () => {
|
||||||
it('removes the test node from the DOM', () => {
|
it('removes the test node from the DOM', () => {
|
||||||
tester.measure(attrs, 'foo');
|
tester.measure(attrs, [[{text: 'foo'}]]);
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
tester.detach();
|
tester.detach();
|
||||||
expect(hold.children.length).toEqual(0);
|
expect(hold.children.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not prevent using the tester again later', () => {
|
it('does not prevent using the tester again later', () => {
|
||||||
tester.measure(attrs, 'foo');
|
tester.measure(attrs, [[{text: 'foo'}]]);
|
||||||
tester.detach();
|
tester.detach();
|
||||||
|
|
||||||
const size = tester.measure(attrs, 'foo');
|
const size = tester.measure(attrs, [[{text: 'foo'}]]);
|
||||||
expect(hold.children.length).toEqual(1);
|
expect(hold.children.length).toEqual(1);
|
||||||
expect(size.width).toBeGreaterThan(0);
|
expect(size.width).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue