Add simple agent options (red, database) [#36]

This commit is contained in:
David Evans 2018-02-14 00:43:00 +00:00
parent ab3d67f313
commit 8397810c12
20 changed files with 1063 additions and 353 deletions

View File

@ -604,7 +604,15 @@ define('core/ArrayUtilities',[],() => {
define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => { define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
'use strict'; 'use strict';
const CM_ERROR = {type: 'error line-error', then: {'': 0}}; const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}};
function textTo(exit, suggest = false) {
return {
type: 'string',
suggest,
then: Object.assign({'': 0}, exit),
};
}
function suggestionsEqual(a, b) { function suggestionsEqual(a, b) {
return ( return (
@ -615,6 +623,11 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
); );
} }
const AGENT_INFO_TYPES = [
'database',
'red',
];
const makeCommands = ((() => { const makeCommands = ((() => {
// The order of commands inside "then" blocks directly influences the // The order of commands inside "then" blocks directly influences the
// order they are displayed to the user in autocomplete menus. // order they are displayed to the user in autocomplete menus.
@ -623,36 +636,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
// to use Map objects instead for strict compliance, at the cost of // to use Map objects instead for strict compliance, at the cost of
// extra syntax. // extra syntax.
const end = {type: '', suggest: '\n', then: {}}; function agentListTo(exit, next = 1) {
const hiddenEnd = {type: '', then: {}};
function textTo(exit, suggest) {
return {
type: 'string',
suggest,
then: Object.assign({'': 0}, exit),
};
}
const textToEnd = textTo({'\n': end});
const aliasListToEnd = {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
'\n': end,
',': {type: 'operator', suggest: true, then: {'': 1}},
'as': {type: 'keyword', suggest: true, then: {
'': {type: 'variable', suggest: {known: 'Agent'}, then: {
'': 0,
',': {type: 'operator', suggest: true, then: {'': 3}},
'\n': end,
}},
}},
},
};
function agentListTo(exit) {
return { return {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
@ -660,46 +644,37 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
exit, exit,
{ {
'': 0, '': 0,
',': {type: 'operator', suggest: true, then: {'': 1}}, ',': {type: 'operator', then: {'': next}},
} }
), ),
}; };
} }
const end = {type: '', suggest: '\n', then: {}};
const hiddenEnd = {type: '', suggest: false, then: {}};
const textToEnd = textTo({'\n': end});
const colonTextToEnd = { const colonTextToEnd = {
type: 'operator', type: 'operator',
suggest: true,
then: {'': textToEnd, '\n': hiddenEnd}, then: {'': textToEnd, '\n': hiddenEnd},
}; };
const agentListToText = agentListTo({ const aliasListToEnd = agentListTo({
':': colonTextToEnd, '\n': end,
}); 'as': {type: 'keyword', then: {
const agentList2ToText = { '': {type: 'variable', suggest: {known: 'Agent'}, then: {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0, '': 0,
',': {type: 'operator', suggest: true, then: { ',': {type: 'operator', then: {'': 3}},
'': agentListToText, '\n': end,
}}, }},
':': CM_ERROR, }},
}, });
}; const agentListToText = agentListTo({':': colonTextToEnd});
const singleAgentToText = {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
',': CM_ERROR,
':': colonTextToEnd,
},
};
const agentToOptText = { const agentToOptText = {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
then: { then: {
'': 0, '': 0,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
@ -707,9 +682,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}, },
}; };
const referenceName = { const referenceName = {
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textTo({ '': textTo({
'as': {type: 'keyword', suggest: true, then: { 'as': {type: 'keyword', then: {
'': { '': {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
@ -722,23 +697,23 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}), }),
}}, }},
}; };
const refDef = {type: 'keyword', suggest: true, then: Object.assign({ const refDef = {type: 'keyword', then: Object.assign({
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': agentListTo(referenceName), '': agentListTo(referenceName),
}}, }},
}, referenceName)}; }, referenceName)};
const divider = { const divider = {
'\n': end, '\n': end,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
'with': {type: 'keyword', suggest: ['with height '], then: { 'with': {type: 'keyword', suggest: ['with height '], then: {
'height': {type: 'keyword', suggest: true, then: { 'height': {type: 'keyword', then: {
'': {type: 'number', suggest: ['6 ', '30 '], then: { '': {type: 'number', suggest: ['6 ', '30 '], then: {
'\n': end, '\n': end,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
@ -747,15 +722,41 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}}, }},
}; };
function simpleList(type, keywords, exit) {
const first = {};
const recur = Object.assign({}, exit);
keywords.forEach((keyword) => {
first[keyword] = {type, then: recur};
recur[keyword] = 0;
});
return first;
}
function optionalKeywords(type, keywords, then) {
const result = Object.assign({}, then);
keywords.forEach((keyword) => {
result[keyword] = {type, then};
});
return result;
}
const agentInfoList = optionalKeywords(
'keyword',
['a', 'an'],
simpleList('keyword', AGENT_INFO_TYPES, {'\n': end})
);
function makeSideNote(side) { function makeSideNote(side) {
return { return {
type: 'keyword', type: 'keyword',
suggest: [side + ' of ', side + ': '], suggest: [side + ' of ', side + ': '],
then: { then: {
'of': {type: 'keyword', suggest: true, then: { 'of': {type: 'keyword', then: {
'': agentListToText, '': agentListToText,
}}, }},
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
'': agentListToText, '': agentListToText,
@ -763,8 +764,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}; };
} }
function makeOpBlock(exit, sourceExit) { function makeOpBlock({exit, sourceExit, blankExit}) {
const op = {type: 'operator', suggest: true, then: { const op = {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': CM_ERROR, '*': CM_ERROR,
@ -772,14 +773,14 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
'': exit, '': exit,
}}; }};
return { return {
'+': {type: 'operator', suggest: true, then: { '+': {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': op, '*': op,
'!': CM_ERROR, '!': CM_ERROR,
'': exit, '': exit,
}}, }},
'-': {type: 'operator', suggest: true, then: { '-': {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': op, '*': op,
@ -792,27 +793,29 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}}, }},
'': exit, '': exit,
}}, }},
'*': {type: 'operator', suggest: true, then: Object.assign({ '*': {type: 'operator', then: Object.assign({
'+': op, '+': op,
'-': op, '-': op,
'*': CM_ERROR, '*': CM_ERROR,
'!': CM_ERROR, '!': CM_ERROR,
'': exit, '': exit,
}, sourceExit)}, }, sourceExit || exit)},
'!': op, '!': op,
'': exit, '': blankExit || exit,
}; };
} }
function makeCMConnect(arrows) { function makeCMConnect(arrows) {
const connect = { const connect = {
type: 'keyword', type: 'keyword',
suggest: true, then: Object.assign({}, makeOpBlock({
then: Object.assign({}, makeOpBlock(agentToOptText, { exit: agentToOptText,
sourceExit: {
':': colonTextToEnd, ':': colonTextToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
},
}), { }), {
'...': {type: 'operator', suggest: true, then: { '...': {type: 'operator', then: {
'': { '': {
type: 'variable', type: 'variable',
suggest: {known: 'DelayedAgent'}, suggest: {known: 'DelayedAgent'},
@ -831,7 +834,6 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
const labelIndicator = { const labelIndicator = {
type: 'operator', type: 'operator',
suggest: true,
override: 'Label', override: 'Label',
then: {}, then: {},
}; };
@ -848,8 +850,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
then: Object.assign({ then: Object.assign({
'': 0, '': 0,
}, connectors, {
':': labelIndicator, ':': labelIndicator,
}, connectors), }),
}; };
const firstAgentDelayed = { const firstAgentDelayed = {
@ -861,29 +864,39 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}, connectors), }, connectors),
}; };
const firstAgentNoFlags = Object.assign({}, firstAgent, {
then: Object.assign({}, firstAgent.then, {
'is': {type: 'keyword', then: agentInfoList},
}),
});
return Object.assign({ return Object.assign({
'...': {type: 'operator', suggest: true, then: { '...': {type: 'operator', then: {
'': firstAgentDelayed, '': firstAgentDelayed,
}}, }},
}, makeOpBlock(firstAgent, Object.assign({ }, makeOpBlock({
exit: firstAgent,
sourceExit: Object.assign({
'': firstAgent, '': firstAgent,
':': hiddenLabelIndicator, ':': hiddenLabelIndicator,
}, connectors))); }, connectors),
blankExit: firstAgentNoFlags,
}));
} }
const group = {type: 'keyword', suggest: true, then: { const group = {type: 'keyword', then: {
'': textToEnd, '': textToEnd,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
'\n': end, '\n': end,
}}; }};
const BASE_THEN = { const BASE_THEN = {
'title': {type: 'keyword', suggest: true, then: { 'title': {type: 'keyword', then: {
'': textToEnd, '': textToEnd,
}}, }},
'theme': {type: 'keyword', suggest: true, then: { 'theme': {type: 'keyword', then: {
'': { '': {
type: 'string', type: 'string',
suggest: { suggest: {
@ -896,36 +909,36 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}, },
}, },
}}, }},
'headers': {type: 'keyword', suggest: true, then: { 'headers': {type: 'keyword', then: {
'none': {type: 'keyword', suggest: true, then: {}}, 'none': {type: 'keyword', then: {}},
'cross': {type: 'keyword', suggest: true, then: {}}, 'cross': {type: 'keyword', then: {}},
'box': {type: 'keyword', suggest: true, then: {}}, 'box': {type: 'keyword', then: {}},
'fade': {type: 'keyword', suggest: true, then: {}}, 'fade': {type: 'keyword', then: {}},
'bar': {type: 'keyword', suggest: true, then: {}}, 'bar': {type: 'keyword', then: {}},
}}, }},
'terminators': {type: 'keyword', suggest: true, then: { 'terminators': {type: 'keyword', then: {
'none': {type: 'keyword', suggest: true, then: {}}, 'none': {type: 'keyword', then: {}},
'cross': {type: 'keyword', suggest: true, then: {}}, 'cross': {type: 'keyword', then: {}},
'box': {type: 'keyword', suggest: true, then: {}}, 'box': {type: 'keyword', then: {}},
'fade': {type: 'keyword', suggest: true, then: {}}, 'fade': {type: 'keyword', then: {}},
'bar': {type: 'keyword', suggest: true, then: {}}, 'bar': {type: 'keyword', then: {}},
}}, }},
'divider': {type: 'keyword', suggest: true, then: Object.assign({ 'divider': {type: 'keyword', then: Object.assign({
'line': {type: 'keyword', suggest: true, then: divider}, 'line': {type: 'keyword', then: divider},
'space': {type: 'keyword', suggest: true, then: divider}, 'space': {type: 'keyword', then: divider},
'delay': {type: 'keyword', suggest: true, then: divider}, 'delay': {type: 'keyword', then: divider},
'tear': {type: 'keyword', suggest: true, then: divider}, 'tear': {type: 'keyword', then: divider},
}, divider)}, }, divider)},
'define': {type: 'keyword', suggest: true, then: { 'define': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'as': CM_ERROR, 'as': CM_ERROR,
}}, }},
'begin': {type: 'keyword', suggest: true, then: { 'begin': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'reference': refDef, 'reference': refDef,
'as': CM_ERROR, 'as': CM_ERROR,
}}, }},
'end': {type: 'keyword', suggest: true, then: { 'end': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'as': CM_ERROR, 'as': CM_ERROR,
'\n': end, '\n': end,
@ -934,7 +947,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: 'if: ', then: { 'if': {type: 'keyword', suggest: 'if: ', then: {
'': textToEnd, '': textToEnd,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
}}, }},
@ -942,39 +955,47 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}}, }},
'repeat': group, 'repeat': group,
'group': group, 'group': group,
'note': {type: 'keyword', suggest: true, then: { 'note': {type: 'keyword', then: {
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': agentListToText, '': agentListToText,
}}, }},
'left': makeSideNote('left'), 'left': makeSideNote('left'),
'right': makeSideNote('right'), 'right': makeSideNote('right'),
'between': {type: 'keyword', suggest: true, then: { 'between': {type: 'keyword', then: {
'': agentList2ToText, '': agentListTo({':': CM_ERROR}, agentListToText),
}}, }},
}}, }},
'state': {type: 'keyword', suggest: 'state over ', then: { 'state': {type: 'keyword', suggest: 'state over ', then: {
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': singleAgentToText, '': {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
',': CM_ERROR,
':': colonTextToEnd,
},
},
}}, }},
}}, }},
'text': {type: 'keyword', suggest: true, then: { 'text': {type: 'keyword', then: {
'left': makeSideNote('left'), 'left': makeSideNote('left'),
'right': makeSideNote('right'), 'right': makeSideNote('right'),
}}, }},
'autolabel': {type: 'keyword', suggest: true, then: { 'autolabel': {type: 'keyword', then: {
'off': {type: 'keyword', suggest: true, then: {}}, 'off': {type: 'keyword', then: {}},
'': textTo({'\n': end}, [ '': textTo({'\n': end}, [
{v: '<label>', suffix: '\n', q: true}, {v: '<label>', suffix: '\n', q: true},
{v: '[<inc>] <label>', suffix: '\n', q: true}, {v: '[<inc>] <label>', suffix: '\n', q: true},
{v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true}, {v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true},
]), ]),
}}, }},
'simultaneously': {type: 'keyword', suggest: true, then: { 'simultaneously': {type: 'keyword', then: {
':': {type: 'operator', suggest: true, then: {}}, ':': {type: 'operator', then: {}},
'with': {type: 'keyword', suggest: true, then: { 'with': {type: 'keyword', then: {
'': {type: 'variable', suggest: {known: 'Label'}, then: { '': {type: 'variable', suggest: {known: 'Label'}, then: {
'': 0, '': 0,
':': {type: 'operator', suggest: true, then: {}}, ':': {type: 'operator', then: {}},
}}, }},
}}, }},
}}, }},
@ -1003,8 +1024,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
} }
return array.flatMap(suggestions, (suggest) => { return array.flatMap(suggestions, (suggest) => {
if(suggest === true) { if(suggest === false) {
return [cmCappedToken(token, current)]; return [];
} else if(typeof suggest === 'object') { } else if(typeof suggest === 'object') {
if(suggest.known) { if(suggest.known) {
return state['known' + suggest.known] || []; return state['known' + suggest.known] || [];
@ -1014,7 +1035,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
} else if(typeof suggest === 'string' && suggest) { } else if(typeof suggest === 'string' && suggest) {
return [{v: suggest, q: (token === '')}]; return [{v: suggest, q: (token === '')}];
} else { } else {
return []; return [cmCappedToken(token, current)];
} }
}); });
} }
@ -2213,6 +2234,31 @@ define('sequence/Parser',[
name: joinLabel(line, 0, line.length - 1), name: joinLabel(line, 0, line.length - 1),
}; };
}, },
(line) => { // options
const sepPos = findToken(line, 'is');
if(sepPos < 1) {
return null;
}
const indefiniteArticles = ['a', 'an'];
let optionsBegin = sepPos + 1;
if(indefiniteArticles.includes(tokenKeyword(line[optionsBegin]))) {
++ optionsBegin;
}
if(optionsBegin === line.length) {
throw makeError('Empty agent options', {b: array.last(line).e});
}
const agent = readAgent(line, 0, sepPos);
const options = [];
for(let i = optionsBegin; i < line.length; ++ i) {
options.push(line[i].v);
}
return {
type: 'agent options',
agent,
options,
};
},
]; ];
function parseLine(line, {meta, stages}) { function parseLine(line, {meta, stages}) {
@ -2309,7 +2355,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
return a.id === b.id; return a.id === b.id;
}, },
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => { make: (id, {anchorRight = false, isVirtualSource = false} = {}) => {
return {id, anchorRight, isVirtualSource}; return {id, anchorRight, isVirtualSource, options: []};
}, },
indexOf: (list, gAgent) => { indexOf: (list, gAgent) => {
return array.indexOf(list, gAgent, GAgent.equals); return array.indexOf(list, gAgent, GAgent.equals);
@ -2523,6 +2569,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
'mark': this.handleMark.bind(this), 'mark': this.handleMark.bind(this),
'async': this.handleAsync.bind(this), 'async': this.handleAsync.bind(this),
'agent define': this.handleAgentDefine.bind(this), 'agent define': this.handleAgentDefine.bind(this),
'agent options': this.handleAgentOptions.bind(this),
'agent begin': this.handleAgentBegin.bind(this), 'agent begin': this.handleAgentBegin.bind(this),
'agent end': this.handleAgentEnd.bind(this), 'agent end': this.handleAgentEnd.bind(this),
'divider': this.handleDivider.bind(this), 'divider': this.handleDivider.bind(this),
@ -2626,23 +2673,23 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
validateGAgents(gAgents, { validateGAgents(gAgents, {
allowGrouped = false, allowGrouped = false,
rejectGrouped = false, allowCovered = false,
allowVirtual = false, allowVirtual = false,
} = {}) { } = {}) {
/* jshint -W074 */ // agent validity checking requires several steps
gAgents.forEach((gAgent) => { gAgents.forEach((gAgent) => {
const state = this.getGAgentState(gAgent); const state = this.getGAgentState(gAgent);
if(state.covered) { if(state.blocked && state.group === null) {
// used to be a group alias; can never be reused
throw new Error('Duplicate agent name: ' + gAgent.id);
}
if(!allowCovered && state.covered) {
throw new Error( throw new Error(
'Agent ' + gAgent.id + ' is hidden behind group' 'Agent ' + gAgent.id + ' is hidden behind group'
); );
} }
if(rejectGrouped && state.group !== null) { if(!allowGrouped && state.group !== null) {
throw new Error('Agent ' + gAgent.id + ' is in a group'); throw new Error('Agent ' + gAgent.id + ' is in a group');
} }
if(state.blocked && (!allowGrouped || state.group === null)) {
throw new Error('Duplicate agent name: ' + gAgent.id);
}
if(!allowVirtual && gAgent.isVirtualSource) { if(!allowVirtual && gAgent.isVirtualSource) {
throw new Error('cannot use message source here'); throw new Error('cannot use message source here');
} }
@ -2833,7 +2880,7 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
makeGroupDetails(pAgents, alias) { makeGroupDetails(pAgents, alias) {
const gAgents = pAgents.map(this.toGAgent); const gAgents = pAgents.map(this.toGAgent);
this.validateGAgents(gAgents, {rejectGrouped: true}); this.validateGAgents(gAgents);
if(this.agentStates.has(alias)) { if(this.agentStates.has(alias)) {
throw new Error('Duplicate agent name: ' + alias); throw new Error('Duplicate agent name: ' + alias);
} }
@ -3264,8 +3311,27 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
handleAgentDefine({agents}) { handleAgentDefine({agents}) {
const gAgents = agents.map(this.toGAgent); const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents); this.validateGAgents(gAgents, {
this.defineGAgents(gAgents); allowGrouped: true,
allowCovered: true,
});
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
}
handleAgentOptions({agent, options}) {
const gAgent = this.toGAgent(agent);
const gAgents = [gAgent];
this.validateGAgents(gAgents, {
allowGrouped: true,
allowCovered: true,
});
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
this.gAgents
.filter(({id}) => (id === gAgent.id))
.forEach((storedGAgent) => {
array.mergeSets(storedGAgent.options, options);
});
} }
handleAgentBegin({agents, mode}) { handleAgentBegin({agents, mode}) {
@ -4433,8 +4499,16 @@ define('sequence/components/AgentCap',[
'use strict'; 'use strict';
class CapBox { class CapBox {
separation({formattedLabel}, env) { getConfig(options, env) {
const config = env.theme.agentCap.box; let config = null;
if(options.includes('database')) {
config = env.theme.agentCap.database;
}
return config || env.theme.agentCap.box;
}
separation({formattedLabel, options}, env) {
const config = this.getConfig(options, env);
const width = ( const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width + env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left + config.padding.left +
@ -4448,8 +4522,8 @@ define('sequence/components/AgentCap',[
}; };
} }
topShift({formattedLabel}, env) { topShift({formattedLabel, options}, env) {
const config = env.theme.agentCap.box; const config = this.getConfig(options, env);
const height = ( const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) + env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top + config.padding.top +
@ -4458,8 +4532,8 @@ define('sequence/components/AgentCap',[
return Math.max(0, height - config.arrowBottom); return Math.max(0, height - config.arrowBottom);
} }
render(y, {x, formattedLabel}, env) { render(y, {x, formattedLabel, options}, env) {
const config = env.theme.agentCap.box; const config = this.getConfig(options, env);
const clickable = env.makeRegion(); const clickable = env.makeRegion();
const text = SVGShapes.renderBoxedText(formattedLabel, { const text = SVGShapes.renderBoxedText(formattedLabel, {
x, x,
@ -4505,13 +4579,18 @@ define('sequence/components/AgentCap',[
return config.size / 2; return config.size / 2;
} }
render(y, {x}, env) { render(y, {x, options}, env) {
const config = env.theme.agentCap.cross; const config = env.theme.agentCap.cross;
const d = config.size / 2; const d = config.size / 2;
const clickable = env.makeRegion(); const clickable = env.makeRegion();
clickable.appendChild(config.render({x, y: y + d, radius: d})); clickable.appendChild(config.render({
x,
y: y + d,
radius: d,
options,
}));
clickable.appendChild(svg.make('rect', { clickable.appendChild(svg.make('rect', {
'x': x - d, 'x': x - d,
'y': y, 'y': y,
@ -4550,7 +4629,7 @@ define('sequence/components/AgentCap',[
return config.height / 2; return config.height / 2;
} }
render(y, {x, formattedLabel}, env) { render(y, {x, formattedLabel, options}, env) {
const boxCfg = env.theme.agentCap.box; const boxCfg = env.theme.agentCap.box;
const barCfg = env.theme.agentCap.bar; const barCfg = env.theme.agentCap.bar;
const width = ( const width = (
@ -4566,6 +4645,7 @@ define('sequence/components/AgentCap',[
y, y,
width, width,
height, height,
options,
})); }));
clickable.appendChild(svg.make('rect', { clickable.appendChild(svg.make('rect', {
'x': x - width / 2, 'x': x - width / 2,
@ -4838,7 +4918,7 @@ define('sequence/components/Connect',[
const arrow = this.getConfig(theme); const arrow = this.getConfig(theme);
const join = arrow.attrs['stroke-linejoin'] || 'miter'; const join = arrow.attrs['stroke-linejoin'] || 'miter';
const t = arrow.attrs['stroke-width'] * 0.5; const t = arrow.attrs['stroke-width'] * 0.5;
const lineStroke = theme.agentLineAttrs['stroke-width'] * 0.5; const lineStroke = theme.agentLineAttrs['']['stroke-width'] * 0.5;
if(join === 'round') { if(join === 'round') {
return lineStroke + t; return lineStroke + t;
} else { } else {
@ -6057,6 +6137,7 @@ define('sequence/Renderer',[
y1: toY, y1: toY,
width: agentInfo.currentRad * 2, width: agentInfo.currentRad * 2,
className: 'agent-' + agentInfo.index + '-line', className: 'agent-' + agentInfo.index + '-line',
options: agentInfo.options,
})); }));
} }
@ -6193,6 +6274,7 @@ define('sequence/Renderer',[
formattedLabel: agent.formattedLabel, formattedLabel: agent.formattedLabel,
anchorRight: agent.anchorRight, anchorRight: agent.anchorRight,
isVirtualSource: agent.isVirtualSource, isVirtualSource: agent.isVirtualSource,
options: agent.options,
index, index,
x: null, x: null,
latestYStart: null, latestYStart: null,
@ -6784,6 +6866,14 @@ define('sequence/themes/BaseTheme',[
return r; return r;
} }
function optionsAttributes(attributes, options) {
let attrs = Object.assign({}, attributes['']);
options.forEach((opt) => {
Object.assign(attrs, attributes[opt] || {});
});
return attrs;
}
class BaseTheme { class BaseTheme {
constructor({name, settings, blocks, notes, dividers}) { constructor({name, settings, blocks, notes, dividers}) {
this.name = name; this.name = name;
@ -6811,7 +6901,12 @@ define('sequence/themes/BaseTheme',[
return this.dividers[type] || this.dividers['']; return this.dividers[type] || this.dividers[''];
} }
renderAgentLine({x, y0, y1, width, className}) { optionsAttributes(attributes, options) {
return optionsAttributes(attributes, options);
}
renderAgentLine({x, y0, y1, width, className, options}) {
const attrs = this.optionsAttributes(this.agentLineAttrs, options);
if(width > 0) { if(width > 0) {
return svg.make('rect', Object.assign({ return svg.make('rect', Object.assign({
'x': x - width / 2, 'x': x - width / 2,
@ -6819,7 +6914,7 @@ define('sequence/themes/BaseTheme',[
'width': width, 'width': width,
'height': y1 - y0, 'height': y1 - y0,
'class': className, 'class': className,
}, this.agentLineAttrs)); }, attrs));
} else { } else {
return svg.make('line', Object.assign({ return svg.make('line', Object.assign({
'x1': x, 'x1': x,
@ -6827,7 +6922,7 @@ define('sequence/themes/BaseTheme',[
'x2': x, 'x2': x,
'y2': y1, 'y2': y1,
'class': className, 'class': className,
}, this.agentLineAttrs)); }, attrs));
} }
} }
} }
@ -6877,6 +6972,27 @@ define('sequence/themes/BaseTheme',[
return g; return g;
}; };
BaseTheme.renderDB = (attrs, {x, y, width, height}) => {
const z = attrs['db-z'];
return svg.make('g', {}, [
svg.make('rect', Object.assign({
'x': x,
'y': y,
'width': width,
'height': height,
'rx': width / 2,
'ry': z,
}, attrs)),
svg.make('path', Object.assign({
'd': (
'M' + x + ' ' + (y + z) +
'a' + (width / 2) + ' ' + z +
' 0 0 0 ' + width + ' 0'
),
}, attrs, {'fill': 'none'})),
]);
};
BaseTheme.renderCross = (attrs, {x, y, radius}) => { BaseTheme.renderCross = (attrs, {x, y, radius}) => {
return svg.make('path', Object.assign({ return svg.make('path', Object.assign({
'd': ( 'd': (
@ -7165,6 +7281,27 @@ define('sequence/themes/Basic',[
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 15,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'db-z': 5,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 20, size: 20,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -7304,10 +7441,15 @@ define('sequence/themes/Basic',[
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
'red': {
'stroke': '#CC0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {
@ -7563,6 +7705,27 @@ define('sequence/themes/Monospace',[
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 12,
left: 8,
right: 8,
bottom: 4,
},
arrowBottom: 4 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'db-z': 4,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 16, size: 16,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -7700,10 +7863,15 @@ define('sequence/themes/Monospace',[
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
'red': {
'stroke': '#AA0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {
@ -7947,6 +8115,28 @@ define('sequence/themes/Chunky',[
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 5,
left: 3,
right: 3,
bottom: 1,
},
arrowBottom: 1 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 3,
'db-z': 2,
}),
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 20, size: 20,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -8094,10 +8284,15 @@ define('sequence/themes/Chunky',[
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
}, },
'red': {
'stroke': '#DD0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {
@ -8797,6 +8992,25 @@ define('sequence/themes/Sketch',[
}, },
boxRenderer: null, boxRenderer: null,
}, },
database: {
padding: {
top: 15,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, Object.assign({
'fill': '#FFFFFF',
'db-z': 5,
}, PENCIL.normal)),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 15, size: 15,
render: null, render: null,
@ -8913,9 +9127,12 @@ define('sequence/themes/Sketch',[
}, },
agentLineAttrs: { agentLineAttrs: {
'': Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000', }, PENCIL.normal),
'stroke-width': 1, 'red': {
'stroke': 'rgba(200,40,0,0.8)',
},
}, },
}; };
@ -9287,7 +9504,9 @@ define('sequence/themes/Sketch',[
'd': line.nodes, 'd': line.nodes,
'fill': 'none', 'fill': 'none',
'stroke-dasharray': lineOptions.dash ? '6, 5' : 'none', 'stroke-dasharray': lineOptions.dash ? '6, 5' : 'none',
}, lineOptions.thick ? PENCIL.thick : PENCIL.normal)); }, lineOptions.attrs || (
lineOptions.thick ? PENCIL.thick : PENCIL.normal
)));
return shape; return shape;
} }
@ -9316,11 +9535,11 @@ define('sequence/themes/Sketch',[
return lT.nodes + lR.nodes + lB.nodes + lL.nodes; return lT.nodes + lR.nodes + lB.nodes + lL.nodes;
} }
renderBox(position, {fill = null, thick = false} = {}) { renderBox(position, {fill = null, thick = false, attrs = null} = {}) {
return svg.make('path', Object.assign({ return svg.make('path', Object.assign({
'd': this.boxNodes(position), 'd': this.boxNodes(position),
'fill': fill || '#FFFFFF', 'fill': fill || '#FFFFFF',
}, thick ? PENCIL.thick : PENCIL.normal)); }, attrs || (thick ? PENCIL.thick : PENCIL.normal)));
} }
renderNote({x, y, width, height}) { renderNote({x, y, width, height}) {
@ -9646,21 +9865,22 @@ define('sequence/themes/Sketch',[
}, PENCIL.normal)); }, PENCIL.normal));
} }
renderAgentLine({x, y0, y1, width, className}) { renderAgentLine({x, y0, y1, width, className, options}) {
const attrs = this.optionsAttributes(this.agentLineAttrs, options);
if(width > 0) { if(width > 0) {
const shape = this.renderBox({ const shape = this.renderBox({
x: x - width / 2, x: x - width / 2,
y: y0, y: y0,
width, width,
height: y1 - y0, height: y1 - y0,
}, {fill: 'none'}); }, {fill: 'none', attrs});
shape.setAttribute('class', className); shape.setAttribute('class', className);
return shape; return shape;
} else { } else {
const shape = this.renderLine( const shape = this.renderLine(
{x, y: y0}, {x, y: y0},
{x, y: y1}, {x, y: y1},
{varY: 0.3} {varY: 0.3, attrs}
); );
shape.setAttribute('class', className); shape.setAttribute('class', className);
return shape; return shape;

File diff suppressed because one or more lines are too long

View File

@ -249,6 +249,16 @@ define([], [
code: '`{text}`', code: '`{text}`',
preview: 'A -> B: `mono`', preview: 'A -> B: `mono`',
}, },
{
title: 'Red agent line',
code: '{Agent} is red',
preview: 'headers box\nA is red\nbegin A',
},
{
title: 'Database indicator',
code: '{Agent} is a database',
preview: 'headers box\nA is a database\nbegin A',
},
{ {
title: 'Monospace theme', title: 'Monospace theme',
code: 'theme monospace', code: 'theme monospace',

View File

@ -1,7 +1,15 @@
define(['core/ArrayUtilities'], (array) => { define(['core/ArrayUtilities'], (array) => {
'use strict'; 'use strict';
const CM_ERROR = {type: 'error line-error', then: {'': 0}}; const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}};
function textTo(exit, suggest = false) {
return {
type: 'string',
suggest,
then: Object.assign({'': 0}, exit),
};
}
function suggestionsEqual(a, b) { function suggestionsEqual(a, b) {
return ( return (
@ -12,6 +20,11 @@ define(['core/ArrayUtilities'], (array) => {
); );
} }
const AGENT_INFO_TYPES = [
'database',
'red',
];
const makeCommands = ((() => { const makeCommands = ((() => {
// The order of commands inside "then" blocks directly influences the // The order of commands inside "then" blocks directly influences the
// order they are displayed to the user in autocomplete menus. // order they are displayed to the user in autocomplete menus.
@ -20,36 +33,7 @@ define(['core/ArrayUtilities'], (array) => {
// to use Map objects instead for strict compliance, at the cost of // to use Map objects instead for strict compliance, at the cost of
// extra syntax. // extra syntax.
const end = {type: '', suggest: '\n', then: {}}; function agentListTo(exit, next = 1) {
const hiddenEnd = {type: '', then: {}};
function textTo(exit, suggest) {
return {
type: 'string',
suggest,
then: Object.assign({'': 0}, exit),
};
}
const textToEnd = textTo({'\n': end});
const aliasListToEnd = {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
'\n': end,
',': {type: 'operator', suggest: true, then: {'': 1}},
'as': {type: 'keyword', suggest: true, then: {
'': {type: 'variable', suggest: {known: 'Agent'}, then: {
'': 0,
',': {type: 'operator', suggest: true, then: {'': 3}},
'\n': end,
}},
}},
},
};
function agentListTo(exit) {
return { return {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
@ -57,46 +41,37 @@ define(['core/ArrayUtilities'], (array) => {
exit, exit,
{ {
'': 0, '': 0,
',': {type: 'operator', suggest: true, then: {'': 1}}, ',': {type: 'operator', then: {'': next}},
} }
), ),
}; };
} }
const end = {type: '', suggest: '\n', then: {}};
const hiddenEnd = {type: '', suggest: false, then: {}};
const textToEnd = textTo({'\n': end});
const colonTextToEnd = { const colonTextToEnd = {
type: 'operator', type: 'operator',
suggest: true,
then: {'': textToEnd, '\n': hiddenEnd}, then: {'': textToEnd, '\n': hiddenEnd},
}; };
const agentListToText = agentListTo({ const aliasListToEnd = agentListTo({
':': colonTextToEnd, '\n': end,
}); 'as': {type: 'keyword', then: {
const agentList2ToText = { '': {type: 'variable', suggest: {known: 'Agent'}, then: {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0, '': 0,
',': {type: 'operator', suggest: true, then: { ',': {type: 'operator', then: {'': 3}},
'': agentListToText, '\n': end,
}}, }},
':': CM_ERROR, }},
}, });
}; const agentListToText = agentListTo({':': colonTextToEnd});
const singleAgentToText = {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
',': CM_ERROR,
':': colonTextToEnd,
},
};
const agentToOptText = { const agentToOptText = {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
then: { then: {
'': 0, '': 0,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
@ -104,9 +79,9 @@ define(['core/ArrayUtilities'], (array) => {
}, },
}; };
const referenceName = { const referenceName = {
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textTo({ '': textTo({
'as': {type: 'keyword', suggest: true, then: { 'as': {type: 'keyword', then: {
'': { '': {
type: 'variable', type: 'variable',
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
@ -119,23 +94,23 @@ define(['core/ArrayUtilities'], (array) => {
}), }),
}}, }},
}; };
const refDef = {type: 'keyword', suggest: true, then: Object.assign({ const refDef = {type: 'keyword', then: Object.assign({
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': agentListTo(referenceName), '': agentListTo(referenceName),
}}, }},
}, referenceName)}; }, referenceName)};
const divider = { const divider = {
'\n': end, '\n': end,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
'with': {type: 'keyword', suggest: ['with height '], then: { 'with': {type: 'keyword', suggest: ['with height '], then: {
'height': {type: 'keyword', suggest: true, then: { 'height': {type: 'keyword', then: {
'': {type: 'number', suggest: ['6 ', '30 '], then: { '': {type: 'number', suggest: ['6 ', '30 '], then: {
'\n': end, '\n': end,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
}}, }},
@ -144,15 +119,41 @@ define(['core/ArrayUtilities'], (array) => {
}}, }},
}; };
function simpleList(type, keywords, exit) {
const first = {};
const recur = Object.assign({}, exit);
keywords.forEach((keyword) => {
first[keyword] = {type, then: recur};
recur[keyword] = 0;
});
return first;
}
function optionalKeywords(type, keywords, then) {
const result = Object.assign({}, then);
keywords.forEach((keyword) => {
result[keyword] = {type, then};
});
return result;
}
const agentInfoList = optionalKeywords(
'keyword',
['a', 'an'],
simpleList('keyword', AGENT_INFO_TYPES, {'\n': end})
);
function makeSideNote(side) { function makeSideNote(side) {
return { return {
type: 'keyword', type: 'keyword',
suggest: [side + ' of ', side + ': '], suggest: [side + ' of ', side + ': '],
then: { then: {
'of': {type: 'keyword', suggest: true, then: { 'of': {type: 'keyword', then: {
'': agentListToText, '': agentListToText,
}}, }},
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
'': agentListToText, '': agentListToText,
@ -160,8 +161,8 @@ define(['core/ArrayUtilities'], (array) => {
}; };
} }
function makeOpBlock(exit, sourceExit) { function makeOpBlock({exit, sourceExit, blankExit}) {
const op = {type: 'operator', suggest: true, then: { const op = {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': CM_ERROR, '*': CM_ERROR,
@ -169,14 +170,14 @@ define(['core/ArrayUtilities'], (array) => {
'': exit, '': exit,
}}; }};
return { return {
'+': {type: 'operator', suggest: true, then: { '+': {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': op, '*': op,
'!': CM_ERROR, '!': CM_ERROR,
'': exit, '': exit,
}}, }},
'-': {type: 'operator', suggest: true, then: { '-': {type: 'operator', then: {
'+': CM_ERROR, '+': CM_ERROR,
'-': CM_ERROR, '-': CM_ERROR,
'*': op, '*': op,
@ -189,27 +190,29 @@ define(['core/ArrayUtilities'], (array) => {
}}, }},
'': exit, '': exit,
}}, }},
'*': {type: 'operator', suggest: true, then: Object.assign({ '*': {type: 'operator', then: Object.assign({
'+': op, '+': op,
'-': op, '-': op,
'*': CM_ERROR, '*': CM_ERROR,
'!': CM_ERROR, '!': CM_ERROR,
'': exit, '': exit,
}, sourceExit)}, }, sourceExit || exit)},
'!': op, '!': op,
'': exit, '': blankExit || exit,
}; };
} }
function makeCMConnect(arrows) { function makeCMConnect(arrows) {
const connect = { const connect = {
type: 'keyword', type: 'keyword',
suggest: true, then: Object.assign({}, makeOpBlock({
then: Object.assign({}, makeOpBlock(agentToOptText, { exit: agentToOptText,
sourceExit: {
':': colonTextToEnd, ':': colonTextToEnd,
'\n': hiddenEnd, '\n': hiddenEnd,
},
}), { }), {
'...': {type: 'operator', suggest: true, then: { '...': {type: 'operator', then: {
'': { '': {
type: 'variable', type: 'variable',
suggest: {known: 'DelayedAgent'}, suggest: {known: 'DelayedAgent'},
@ -228,7 +231,6 @@ define(['core/ArrayUtilities'], (array) => {
const labelIndicator = { const labelIndicator = {
type: 'operator', type: 'operator',
suggest: true,
override: 'Label', override: 'Label',
then: {}, then: {},
}; };
@ -245,8 +247,9 @@ define(['core/ArrayUtilities'], (array) => {
suggest: {known: 'Agent'}, suggest: {known: 'Agent'},
then: Object.assign({ then: Object.assign({
'': 0, '': 0,
}, connectors, {
':': labelIndicator, ':': labelIndicator,
}, connectors), }),
}; };
const firstAgentDelayed = { const firstAgentDelayed = {
@ -258,29 +261,39 @@ define(['core/ArrayUtilities'], (array) => {
}, connectors), }, connectors),
}; };
const firstAgentNoFlags = Object.assign({}, firstAgent, {
then: Object.assign({}, firstAgent.then, {
'is': {type: 'keyword', then: agentInfoList},
}),
});
return Object.assign({ return Object.assign({
'...': {type: 'operator', suggest: true, then: { '...': {type: 'operator', then: {
'': firstAgentDelayed, '': firstAgentDelayed,
}}, }},
}, makeOpBlock(firstAgent, Object.assign({ }, makeOpBlock({
exit: firstAgent,
sourceExit: Object.assign({
'': firstAgent, '': firstAgent,
':': hiddenLabelIndicator, ':': hiddenLabelIndicator,
}, connectors))); }, connectors),
blankExit: firstAgentNoFlags,
}));
} }
const group = {type: 'keyword', suggest: true, then: { const group = {type: 'keyword', then: {
'': textToEnd, '': textToEnd,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
'\n': end, '\n': end,
}}; }};
const BASE_THEN = { const BASE_THEN = {
'title': {type: 'keyword', suggest: true, then: { 'title': {type: 'keyword', then: {
'': textToEnd, '': textToEnd,
}}, }},
'theme': {type: 'keyword', suggest: true, then: { 'theme': {type: 'keyword', then: {
'': { '': {
type: 'string', type: 'string',
suggest: { suggest: {
@ -293,36 +306,36 @@ define(['core/ArrayUtilities'], (array) => {
}, },
}, },
}}, }},
'headers': {type: 'keyword', suggest: true, then: { 'headers': {type: 'keyword', then: {
'none': {type: 'keyword', suggest: true, then: {}}, 'none': {type: 'keyword', then: {}},
'cross': {type: 'keyword', suggest: true, then: {}}, 'cross': {type: 'keyword', then: {}},
'box': {type: 'keyword', suggest: true, then: {}}, 'box': {type: 'keyword', then: {}},
'fade': {type: 'keyword', suggest: true, then: {}}, 'fade': {type: 'keyword', then: {}},
'bar': {type: 'keyword', suggest: true, then: {}}, 'bar': {type: 'keyword', then: {}},
}}, }},
'terminators': {type: 'keyword', suggest: true, then: { 'terminators': {type: 'keyword', then: {
'none': {type: 'keyword', suggest: true, then: {}}, 'none': {type: 'keyword', then: {}},
'cross': {type: 'keyword', suggest: true, then: {}}, 'cross': {type: 'keyword', then: {}},
'box': {type: 'keyword', suggest: true, then: {}}, 'box': {type: 'keyword', then: {}},
'fade': {type: 'keyword', suggest: true, then: {}}, 'fade': {type: 'keyword', then: {}},
'bar': {type: 'keyword', suggest: true, then: {}}, 'bar': {type: 'keyword', then: {}},
}}, }},
'divider': {type: 'keyword', suggest: true, then: Object.assign({ 'divider': {type: 'keyword', then: Object.assign({
'line': {type: 'keyword', suggest: true, then: divider}, 'line': {type: 'keyword', then: divider},
'space': {type: 'keyword', suggest: true, then: divider}, 'space': {type: 'keyword', then: divider},
'delay': {type: 'keyword', suggest: true, then: divider}, 'delay': {type: 'keyword', then: divider},
'tear': {type: 'keyword', suggest: true, then: divider}, 'tear': {type: 'keyword', then: divider},
}, divider)}, }, divider)},
'define': {type: 'keyword', suggest: true, then: { 'define': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'as': CM_ERROR, 'as': CM_ERROR,
}}, }},
'begin': {type: 'keyword', suggest: true, then: { 'begin': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'reference': refDef, 'reference': refDef,
'as': CM_ERROR, 'as': CM_ERROR,
}}, }},
'end': {type: 'keyword', suggest: true, then: { 'end': {type: 'keyword', then: {
'': aliasListToEnd, '': aliasListToEnd,
'as': CM_ERROR, 'as': CM_ERROR,
'\n': end, '\n': end,
@ -331,7 +344,7 @@ define(['core/ArrayUtilities'], (array) => {
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: 'if: ', then: { 'if': {type: 'keyword', suggest: 'if: ', then: {
'': textToEnd, '': textToEnd,
':': {type: 'operator', suggest: true, then: { ':': {type: 'operator', then: {
'': textToEnd, '': textToEnd,
}}, }},
}}, }},
@ -339,39 +352,47 @@ define(['core/ArrayUtilities'], (array) => {
}}, }},
'repeat': group, 'repeat': group,
'group': group, 'group': group,
'note': {type: 'keyword', suggest: true, then: { 'note': {type: 'keyword', then: {
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': agentListToText, '': agentListToText,
}}, }},
'left': makeSideNote('left'), 'left': makeSideNote('left'),
'right': makeSideNote('right'), 'right': makeSideNote('right'),
'between': {type: 'keyword', suggest: true, then: { 'between': {type: 'keyword', then: {
'': agentList2ToText, '': agentListTo({':': CM_ERROR}, agentListToText),
}}, }},
}}, }},
'state': {type: 'keyword', suggest: 'state over ', then: { 'state': {type: 'keyword', suggest: 'state over ', then: {
'over': {type: 'keyword', suggest: true, then: { 'over': {type: 'keyword', then: {
'': singleAgentToText, '': {
type: 'variable',
suggest: {known: 'Agent'},
then: {
'': 0,
',': CM_ERROR,
':': colonTextToEnd,
},
},
}}, }},
}}, }},
'text': {type: 'keyword', suggest: true, then: { 'text': {type: 'keyword', then: {
'left': makeSideNote('left'), 'left': makeSideNote('left'),
'right': makeSideNote('right'), 'right': makeSideNote('right'),
}}, }},
'autolabel': {type: 'keyword', suggest: true, then: { 'autolabel': {type: 'keyword', then: {
'off': {type: 'keyword', suggest: true, then: {}}, 'off': {type: 'keyword', then: {}},
'': textTo({'\n': end}, [ '': textTo({'\n': end}, [
{v: '<label>', suffix: '\n', q: true}, {v: '<label>', suffix: '\n', q: true},
{v: '[<inc>] <label>', suffix: '\n', q: true}, {v: '[<inc>] <label>', suffix: '\n', q: true},
{v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true}, {v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true},
]), ]),
}}, }},
'simultaneously': {type: 'keyword', suggest: true, then: { 'simultaneously': {type: 'keyword', then: {
':': {type: 'operator', suggest: true, then: {}}, ':': {type: 'operator', then: {}},
'with': {type: 'keyword', suggest: true, then: { 'with': {type: 'keyword', then: {
'': {type: 'variable', suggest: {known: 'Label'}, then: { '': {type: 'variable', suggest: {known: 'Label'}, then: {
'': 0, '': 0,
':': {type: 'operator', suggest: true, then: {}}, ':': {type: 'operator', then: {}},
}}, }},
}}, }},
}}, }},
@ -400,8 +421,8 @@ define(['core/ArrayUtilities'], (array) => {
} }
return array.flatMap(suggestions, (suggest) => { return array.flatMap(suggestions, (suggest) => {
if(suggest === true) { if(suggest === false) {
return [cmCappedToken(token, current)]; return [];
} else if(typeof suggest === 'object') { } else if(typeof suggest === 'object') {
if(suggest.known) { if(suggest.known) {
return state['known' + suggest.known] || []; return state['known' + suggest.known] || [];
@ -411,7 +432,7 @@ define(['core/ArrayUtilities'], (array) => {
} else if(typeof suggest === 'string' && suggest) { } else if(typeof suggest === 'string' && suggest) {
return [{v: suggest, q: (token === '')}]; return [{v: suggest, q: (token === '')}];
} else { } else {
return []; return [cmCappedToken(token, current)];
} }
}); });
} }

View File

@ -405,6 +405,22 @@ defineDescribe('Code Mirror Mode', [
{v: ' stuff', type: 'string'}, {v: ' stuff', type: 'string'},
]); ]);
}); });
it('highlights agent info statements', () => {
cm.getDoc().setValue('A is a red database');
expect(getTokens(0)).toEqual([
{v: 'A', type: 'variable'},
{v: ' is', type: 'keyword'},
{v: ' a', type: 'keyword'},
{v: ' red', type: 'keyword'},
{v: ' database', type: 'keyword'},
]);
});
it('rejects unknown info statements', () => {
cm.getDoc().setValue('A is a foobar');
expect(getTokens(0)[3].type).toContain('line-error');
});
}); });
describe('autocomplete', () => { describe('autocomplete', () => {
@ -619,5 +635,31 @@ defineDescribe('Code Mirror Mode', [
const hints = getHintTexts({line: 1, ch: 4}); const hints = getHintTexts({line: 1, ch: 4});
expect(hints).toEqual(['woo ']); expect(hints).toEqual(['woo ']);
}); });
it('suggests agent properties', () => {
cm.getDoc().setValue('A is a ');
const hints = getHintTexts({line: 0, ch: 7});
expect(hints).toContain('database ');
expect(hints).toContain('red ');
expect(hints).not.toContain('\n');
});
it('suggests indefinite articles for agent properties', () => {
cm.getDoc().setValue('A is ');
const hints = getHintTexts({line: 0, ch: 5});
expect(hints).toContain('database ');
expect(hints).toContain('a ');
expect(hints).toContain('an ');
expect(hints).not.toContain('\n');
});
it('suggests more agent properties after the first', () => {
cm.getDoc().setValue('A is a red ');
const hints = getHintTexts({line: 0, ch: 11});
expect(hints).toContain('database ');
expect(hints).toContain('\n');
expect(hints).not.toContain('a ');
expect(hints).not.toContain('an ');
});
}); });
}); });

View File

@ -37,7 +37,7 @@ define(['core/ArrayUtilities'], (array) => {
return a.id === b.id; return a.id === b.id;
}, },
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => { make: (id, {anchorRight = false, isVirtualSource = false} = {}) => {
return {id, anchorRight, isVirtualSource}; return {id, anchorRight, isVirtualSource, options: []};
}, },
indexOf: (list, gAgent) => { indexOf: (list, gAgent) => {
return array.indexOf(list, gAgent, GAgent.equals); return array.indexOf(list, gAgent, GAgent.equals);
@ -251,6 +251,7 @@ define(['core/ArrayUtilities'], (array) => {
'mark': this.handleMark.bind(this), 'mark': this.handleMark.bind(this),
'async': this.handleAsync.bind(this), 'async': this.handleAsync.bind(this),
'agent define': this.handleAgentDefine.bind(this), 'agent define': this.handleAgentDefine.bind(this),
'agent options': this.handleAgentOptions.bind(this),
'agent begin': this.handleAgentBegin.bind(this), 'agent begin': this.handleAgentBegin.bind(this),
'agent end': this.handleAgentEnd.bind(this), 'agent end': this.handleAgentEnd.bind(this),
'divider': this.handleDivider.bind(this), 'divider': this.handleDivider.bind(this),
@ -354,23 +355,23 @@ define(['core/ArrayUtilities'], (array) => {
validateGAgents(gAgents, { validateGAgents(gAgents, {
allowGrouped = false, allowGrouped = false,
rejectGrouped = false, allowCovered = false,
allowVirtual = false, allowVirtual = false,
} = {}) { } = {}) {
/* jshint -W074 */ // agent validity checking requires several steps
gAgents.forEach((gAgent) => { gAgents.forEach((gAgent) => {
const state = this.getGAgentState(gAgent); const state = this.getGAgentState(gAgent);
if(state.covered) { if(state.blocked && state.group === null) {
// used to be a group alias; can never be reused
throw new Error('Duplicate agent name: ' + gAgent.id);
}
if(!allowCovered && state.covered) {
throw new Error( throw new Error(
'Agent ' + gAgent.id + ' is hidden behind group' 'Agent ' + gAgent.id + ' is hidden behind group'
); );
} }
if(rejectGrouped && state.group !== null) { if(!allowGrouped && state.group !== null) {
throw new Error('Agent ' + gAgent.id + ' is in a group'); throw new Error('Agent ' + gAgent.id + ' is in a group');
} }
if(state.blocked && (!allowGrouped || state.group === null)) {
throw new Error('Duplicate agent name: ' + gAgent.id);
}
if(!allowVirtual && gAgent.isVirtualSource) { if(!allowVirtual && gAgent.isVirtualSource) {
throw new Error('cannot use message source here'); throw new Error('cannot use message source here');
} }
@ -561,7 +562,7 @@ define(['core/ArrayUtilities'], (array) => {
makeGroupDetails(pAgents, alias) { makeGroupDetails(pAgents, alias) {
const gAgents = pAgents.map(this.toGAgent); const gAgents = pAgents.map(this.toGAgent);
this.validateGAgents(gAgents, {rejectGrouped: true}); this.validateGAgents(gAgents);
if(this.agentStates.has(alias)) { if(this.agentStates.has(alias)) {
throw new Error('Duplicate agent name: ' + alias); throw new Error('Duplicate agent name: ' + alias);
} }
@ -992,8 +993,27 @@ define(['core/ArrayUtilities'], (array) => {
handleAgentDefine({agents}) { handleAgentDefine({agents}) {
const gAgents = agents.map(this.toGAgent); const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents); this.validateGAgents(gAgents, {
this.defineGAgents(gAgents); allowGrouped: true,
allowCovered: true,
});
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
}
handleAgentOptions({agent, options}) {
const gAgent = this.toGAgent(agent);
const gAgents = [gAgent];
this.validateGAgents(gAgents, {
allowGrouped: true,
allowCovered: true,
});
array.mergeSets(this.gAgents, gAgents, GAgent.equals);
this.gAgents
.filter(({id}) => (id === gAgent.id))
.forEach((storedGAgent) => {
array.mergeSets(storedGAgent.options, options);
});
} }
handleAgentBegin({agents, mode}) { handleAgentBegin({agents, mode}) {

View File

@ -58,6 +58,15 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
}; };
}, },
agentOptions: (agentID, options, {ln = 0} = {}) => {
return {
type: 'agent options',
agent: makeParsedAgents([agentID])[0],
options,
ln,
};
},
beginAgents: (agentIDs, {mode = 'box', ln = 0} = {}) => { beginAgents: (agentIDs, {mode = 'box', ln = 0} = {}) => {
return { return {
type: 'agent begin', type: 'agent begin',
@ -159,8 +168,9 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
formattedLabel = any(), formattedLabel = any(),
anchorRight = any(), anchorRight = any(),
isVirtualSource = any(), isVirtualSource = any(),
options = any(),
} = {}) => { } = {}) => {
return {id, formattedLabel, anchorRight, isVirtualSource}; return {id, formattedLabel, anchorRight, isVirtualSource, options};
}, },
beginAgents: (agentIDs, { beginAgents: (agentIDs, {
@ -452,6 +462,31 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]); ]);
}); });
it('applies options to agents', () => {
const sequence = invoke([
PARSED.agentOptions('A', ['foo']),
]);
expect(sequence.agents).toEqual([
any(),
GENERATED.agent('A', {options: ['foo']}),
any(),
]);
});
it('combines agent options', () => {
const sequence = invoke([
PARSED.agentOptions('A', ['foo', 'bar']),
PARSED.agentOptions('B', ['zig']),
PARSED.agentOptions('A', ['zag', 'bar']),
]);
expect(sequence.agents).toEqual([
any(),
GENERATED.agent('A', {options: ['foo', 'bar', 'zag']}),
GENERATED.agent('B', {options: ['zig']}),
any(),
]);
});
it('converts aliases', () => { it('converts aliases', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.defineAgents([{name: 'Baz', alias: 'B', flags: []}]), PARSED.defineAgents([{name: 'Baz', alias: 'B', flags: []}]),
@ -1268,6 +1303,24 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]); ]);
}); });
it('ignores defines when setting block bounds', () => {
const sequence = invoke([
PARSED.blockBegin('if', 'abc'),
PARSED.connect(['A', 'B']),
PARSED.defineAgents(['C']),
PARSED.blockEnd(),
]);
expect(sequence.agents).toEqual([
GENERATED.agent('['),
GENERATED.agent('__BLOCK0['),
GENERATED.agent('A'),
GENERATED.agent('B'),
GENERATED.agent('__BLOCK0]'),
GENERATED.agent('C'),
GENERATED.agent(']'),
]);
});
it('ignores side agents when calculating block bounds', () => { it('ignores side agents when calculating block bounds', () => {
const sequence = invoke([ const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C']), PARSED.beginAgents(['A', 'B', 'C']),
@ -1755,6 +1808,24 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]); ]);
}); });
it('rejects interactions with agents involved in references', () => {
expect(() => invoke([
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.groupBegin('Bar', ['A', 'C']),
PARSED.endAgents(['A']),
PARSED.endAgents(['Bar']),
])).toThrow(new Error('Agent A is in a group at line 1'));
});
it('rejects flags on agents involved in references', () => {
expect(() => invoke([
PARSED.beginAgents(['A', 'B', 'C', 'D']),
PARSED.groupBegin('Bar', ['A', 'C']),
PARSED.connect([{name: 'A', alias: '', flags: ['start']}, 'D']),
PARSED.endAgents(['Bar']),
])).toThrow(new Error('Agent A is in a group at line 1'));
});
it('rejects interactions with agents hidden beneath references', () => { it('rejects interactions with agents hidden beneath references', () => {
expect(() => invoke([ expect(() => invoke([
PARSED.beginAgents(['A', 'B', 'C', 'D']), PARSED.beginAgents(['A', 'B', 'C', 'D']),
@ -1762,6 +1833,20 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
PARSED.connect(['B', 'D']), PARSED.connect(['B', 'D']),
PARSED.endAgents(['AC']), PARSED.endAgents(['AC']),
])).toThrow(new Error('Agent B is hidden behind group at line 1')); ])).toThrow(new Error('Agent B is hidden behind group at line 1'));
expect(() => invoke([
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.groupBegin('Bar', ['A', 'C']),
PARSED.endAgents(['B']),
PARSED.endAgents(['Bar']),
])).toThrow(new Error('Agent B is hidden behind group at line 1'));
expect(() => invoke([
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.groupBegin('Bar', ['A', 'C']),
PARSED.note('note over', ['B']),
PARSED.endAgents(['Bar']),
])).toThrow(new Error('Agent B is hidden behind group at line 1'));
}); });
it('encompasses entire reference boxes in block statements', () => { it('encompasses entire reference boxes in block statements', () => {

View File

@ -562,6 +562,31 @@ define([
name: joinLabel(line, 0, line.length - 1), name: joinLabel(line, 0, line.length - 1),
}; };
}, },
(line) => { // options
const sepPos = findToken(line, 'is');
if(sepPos < 1) {
return null;
}
const indefiniteArticles = ['a', 'an'];
let optionsBegin = sepPos + 1;
if(indefiniteArticles.includes(tokenKeyword(line[optionsBegin]))) {
++ optionsBegin;
}
if(optionsBegin === line.length) {
throw makeError('Empty agent options', {b: array.last(line).e});
}
const agent = readAgent(line, 0, sepPos);
const options = [];
for(let i = optionsBegin; i < line.length; ++ i) {
options.push(line[i].v);
}
return {
type: 'agent options',
agent,
options,
};
},
]; ];
function parseLine(line, {meta, stages}) { function parseLine(line, {meta, stages}) {

View File

@ -142,6 +142,57 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
]); ]);
}); });
it('propagates agent options', () => {
const parsed = parser.parse('Foo bar is zig zag');
expect(parsed.stages).toEqual([
{
type: 'agent options',
ln: jasmine.anything(),
agent: {
name: 'Foo bar',
alias: '',
flags: [],
},
options: ['zig', 'zag'],
},
]);
});
it('ignores indefinite articles in agent options', () => {
const parsed = parser.parse('Foo is a zig\nBar is an oom');
expect(parsed.stages).toEqual([
{
type: 'agent options',
ln: jasmine.anything(),
agent: {
name: 'Foo',
alias: '',
flags: [],
},
options: ['zig'],
},
{
type: 'agent options',
ln: jasmine.anything(),
agent: {
name: 'Bar',
alias: '',
flags: [],
},
options: ['oom'],
},
]);
});
it('rejects empty agent options', () => {
expect(() => parser.parse('Foo is')).toThrow(new Error(
'Empty agent options at line 1, character 6'
));
expect(() => parser.parse('Foo is a')).toThrow(new Error(
'Empty agent options at line 1, character 8'
));
});
it('respects spacing within agent names', () => { it('respects spacing within agent names', () => {
const parsed = parser.parse('A+B -> C D'); const parsed = parser.parse('A+B -> C D');
expect(parsed.stages).toEqual([ expect(parsed.stages).toEqual([

View File

@ -338,6 +338,7 @@ define([
y1: toY, y1: toY,
width: agentInfo.currentRad * 2, width: agentInfo.currentRad * 2,
className: 'agent-' + agentInfo.index + '-line', className: 'agent-' + agentInfo.index + '-line',
options: agentInfo.options,
})); }));
} }
@ -474,6 +475,7 @@ define([
formattedLabel: agent.formattedLabel, formattedLabel: agent.formattedLabel,
anchorRight: agent.anchorRight, anchorRight: agent.anchorRight,
isVirtualSource: agent.isVirtualSource, isVirtualSource: agent.isVirtualSource,
options: agent.options,
index, index,
x: null, x: null,
latestYStart: null, latestYStart: null,

View File

@ -56,18 +56,22 @@ defineDescribe('Sequence Renderer', [
id: '[', id: '[',
formattedLabel: null, formattedLabel: null,
anchorRight: true, anchorRight: true,
options: [],
}, { }, {
id: 'Col 1', id: 'Col 1',
formattedLabel: format('Col 1!'), formattedLabel: format('Col 1!'),
anchorRight: false, anchorRight: false,
options: [],
}, { }, {
id: 'Col 2', id: 'Col 2',
formattedLabel: format('Col 2!'), formattedLabel: format('Col 2!'),
anchorRight: false, anchorRight: false,
options: [],
}, { }, {
id: ']', id: ']',
formattedLabel: null, formattedLabel: null,
anchorRight: false, anchorRight: false,
options: [],
}, },
], ],
stages: [], stages: [],
@ -81,8 +85,17 @@ defineDescribe('Sequence Renderer', [
renderer.render({ renderer.render({
meta: {title: [], code: 'hello'}, meta: {title: [], code: 'hello'},
agents: [ agents: [
{id: '[', formattedLabel: null, anchorRight: true}, {
{id: ']', formattedLabel: null, anchorRight: false}, id: '[',
formattedLabel: null,
anchorRight: true,
options: [],
}, {
id: ']',
formattedLabel: null,
anchorRight: false,
options: [],
},
], ],
stages: [], stages: [],
}); });
@ -99,10 +112,27 @@ defineDescribe('Sequence Renderer', [
renderer.render({ renderer.render({
meta: {title: []}, meta: {title: []},
agents: [ agents: [
{id: '[', formattedLabel: null, anchorRight: true}, {
{id: 'A', formattedLabel: format('A!'), anchorRight: false}, id: '[',
{id: 'B', formattedLabel: format('B!'), anchorRight: false}, formattedLabel: null,
{id: ']', formattedLabel: null, anchorRight: false}, anchorRight: true,
options: [],
}, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
anchorRight: false,
options: [],
},
], ],
stages: [ stages: [
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'}, {type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},
@ -129,11 +159,32 @@ defineDescribe('Sequence Renderer', [
renderer.render({ renderer.render({
meta: {title: []}, meta: {title: []},
agents: [ agents: [
{id: '[', formattedLabel: null, anchorRight: true}, {
{id: 'A', formattedLabel: format('A!'), anchorRight: false}, id: '[',
{id: 'B', formattedLabel: format('B!'), anchorRight: false}, formattedLabel: null,
{id: 'C', formattedLabel: format('C!'), anchorRight: false}, anchorRight: true,
{id: ']', formattedLabel: null, anchorRight: false}, options: [],
}, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: 'C',
formattedLabel: format('C!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
anchorRight: false,
options: [],
},
], ],
stages: [ stages: [
{ {
@ -178,12 +229,37 @@ defineDescribe('Sequence Renderer', [
renderer.render({ renderer.render({
meta: {title: []}, meta: {title: []},
agents: [ agents: [
{id: '[', formattedLabel: null, anchorRight: true}, {
{id: 'A', formattedLabel: format('A!'), anchorRight: false}, id: '[',
{id: 'B', formattedLabel: format('B!'), anchorRight: false}, formattedLabel: null,
{id: 'C', formattedLabel: format('C!'), anchorRight: false}, anchorRight: true,
{id: 'D', formattedLabel: format('D!'), anchorRight: false}, options: [],
{id: ']', formattedLabel: null, anchorRight: false}, }, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: 'C',
formattedLabel: format('C!'),
anchorRight: false,
options: [],
}, {
id: 'D',
formattedLabel: format('D!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
anchorRight: false,
options: [],
},
], ],
stages: [ stages: [
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'}, {type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},

View File

@ -12,8 +12,16 @@ define([
'use strict'; 'use strict';
class CapBox { class CapBox {
separation({formattedLabel}, env) { getConfig(options, env) {
const config = env.theme.agentCap.box; let config = null;
if(options.includes('database')) {
config = env.theme.agentCap.database;
}
return config || env.theme.agentCap.box;
}
separation({formattedLabel, options}, env) {
const config = this.getConfig(options, env);
const width = ( const width = (
env.textSizer.measure(config.labelAttrs, formattedLabel).width + env.textSizer.measure(config.labelAttrs, formattedLabel).width +
config.padding.left + config.padding.left +
@ -27,8 +35,8 @@ define([
}; };
} }
topShift({formattedLabel}, env) { topShift({formattedLabel, options}, env) {
const config = env.theme.agentCap.box; const config = this.getConfig(options, env);
const height = ( const height = (
env.textSizer.measureHeight(config.labelAttrs, formattedLabel) + env.textSizer.measureHeight(config.labelAttrs, formattedLabel) +
config.padding.top + config.padding.top +
@ -37,8 +45,8 @@ define([
return Math.max(0, height - config.arrowBottom); return Math.max(0, height - config.arrowBottom);
} }
render(y, {x, formattedLabel}, env) { render(y, {x, formattedLabel, options}, env) {
const config = env.theme.agentCap.box; const config = this.getConfig(options, env);
const clickable = env.makeRegion(); const clickable = env.makeRegion();
const text = SVGShapes.renderBoxedText(formattedLabel, { const text = SVGShapes.renderBoxedText(formattedLabel, {
x, x,
@ -84,13 +92,18 @@ define([
return config.size / 2; return config.size / 2;
} }
render(y, {x}, env) { render(y, {x, options}, env) {
const config = env.theme.agentCap.cross; const config = env.theme.agentCap.cross;
const d = config.size / 2; const d = config.size / 2;
const clickable = env.makeRegion(); const clickable = env.makeRegion();
clickable.appendChild(config.render({x, y: y + d, radius: d})); clickable.appendChild(config.render({
x,
y: y + d,
radius: d,
options,
}));
clickable.appendChild(svg.make('rect', { clickable.appendChild(svg.make('rect', {
'x': x - d, 'x': x - d,
'y': y, 'y': y,
@ -129,7 +142,7 @@ define([
return config.height / 2; return config.height / 2;
} }
render(y, {x, formattedLabel}, env) { render(y, {x, formattedLabel, options}, env) {
const boxCfg = env.theme.agentCap.box; const boxCfg = env.theme.agentCap.box;
const barCfg = env.theme.agentCap.bar; const barCfg = env.theme.agentCap.bar;
const width = ( const width = (
@ -145,6 +158,7 @@ define([
y, y,
width, width,
height, height,
options,
})); }));
clickable.appendChild(svg.make('rect', { clickable.appendChild(svg.make('rect', {
'x': x - width / 2, 'x': x - width / 2,

View File

@ -24,7 +24,7 @@ define([
const arrow = this.getConfig(theme); const arrow = this.getConfig(theme);
const join = arrow.attrs['stroke-linejoin'] || 'miter'; const join = arrow.attrs['stroke-linejoin'] || 'miter';
const t = arrow.attrs['stroke-width'] * 0.5; const t = arrow.attrs['stroke-width'] * 0.5;
const lineStroke = theme.agentLineAttrs['stroke-width'] * 0.5; const lineStroke = theme.agentLineAttrs['']['stroke-width'] * 0.5;
if(join === 'round') { if(join === 'round') {
return lineStroke + t; return lineStroke + t;
} else { } else {

View File

@ -0,0 +1,5 @@
<svg width="134.673828125" height="65.6" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5 -5 134.673828125 65.6"><metadata>begin A, B, C
A is a database
B is red
C is a red database
</metadata><defs></defs><defs><mask id="R0FullMask" maskUnits="userSpaceOnUse"><rect fill="#FFFFFF" x="-5" y="-5" width="134.673828125" height="65.6"></rect></mask><mask id="R0LineMask" maskUnits="userSpaceOnUse"><rect fill="#FFFFFF" x="-5" y="-5" width="134.673828125" height="65.6"></rect></mask></defs><g></g><g mask="url(#R0FullMask)"><g mask="url(#R0LineMask)"><line x1="24.001953125" y1="35.6" x2="24.001953125" y2="55.6" class="agent-1-line" fill="none" stroke="#000000" stroke-width="1"></line><line x1="62.005859375" y1="35.6" x2="62.005859375" y2="55.6" class="agent-2-line" fill="none" stroke="#CC0000" stroke-width="1"></line><line x1="100.3408203125" y1="35.6" x2="100.3408203125" y2="55.6" class="agent-3-line" fill="none" stroke="#CC0000" stroke-width="1"></line></g><g></g><g><g class="region"><g><rect x="10" y="0" width="28.00390625" height="35.6" rx="14.001953125" ry="5" fill="#FFFFFF" stroke="#000000" stroke-width="1" db-z="5"></rect><path d="M10 5a14.001953125 5 0 0 0 28.00390625 0" fill="none" stroke="#000000" stroke-width="1" db-z="5"></path></g><rect x="10" y="0" width="28.00390625" height="35.6" fill="transparent" class="outline"></rect><text x="24.001953125" font-family="sans-serif" font-size="12" line-height="1.3" text-anchor="middle" y="27">A</text></g><g class="region"><rect x="48.00390625" y="10" width="28.00390625" height="25.6" fill="#FFFFFF" stroke="#000000" stroke-width="1"></rect><rect x="48.00390625" y="10" width="28.00390625" height="25.6" fill="transparent" class="outline"></rect><text x="62.005859375" font-family="sans-serif" font-size="12" line-height="1.3" text-anchor="middle" y="27">B</text></g><g class="region"><g><rect x="86.0078125" y="0" width="28.666015625" height="35.6" rx="14.3330078125" ry="5" fill="#FFFFFF" stroke="#000000" stroke-width="1" db-z="5"></rect><path d="M86.0078125 5a14.3330078125 5 0 0 0 28.666015625 0" fill="none" stroke="#000000" stroke-width="1" db-z="5"></path></g><rect x="86.0078125" y="0" width="28.666015625" height="35.6" fill="transparent" class="outline"></rect><text x="100.3408203125" font-family="sans-serif" font-size="12" line-height="1.3" text-anchor="middle" y="27">C</text></g><g class="region"><rect x="19.001953125" y="45.6" width="10" height="10" fill="transparent" class="outline"></rect></g><g class="region"><rect x="57.005859375" y="45.6" width="10" height="10" fill="transparent" class="outline"></rect></g><g class="region"><rect x="95.3408203125" y="45.6" width="10" height="10" fill="transparent" class="outline"></rect></g></g></g><g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -9,4 +9,5 @@ define([], [
'Reference.svg', 'Reference.svg',
'ReferenceLayering.svg', 'ReferenceLayering.svg',
'Markdown.svg', 'Markdown.svg',
'AgentOptions.svg',
]); ]);

View File

@ -20,6 +20,14 @@ define([
return r; return r;
} }
function optionsAttributes(attributes, options) {
let attrs = Object.assign({}, attributes['']);
options.forEach((opt) => {
Object.assign(attrs, attributes[opt] || {});
});
return attrs;
}
class BaseTheme { class BaseTheme {
constructor({name, settings, blocks, notes, dividers}) { constructor({name, settings, blocks, notes, dividers}) {
this.name = name; this.name = name;
@ -47,7 +55,12 @@ define([
return this.dividers[type] || this.dividers['']; return this.dividers[type] || this.dividers[''];
} }
renderAgentLine({x, y0, y1, width, className}) { optionsAttributes(attributes, options) {
return optionsAttributes(attributes, options);
}
renderAgentLine({x, y0, y1, width, className, options}) {
const attrs = this.optionsAttributes(this.agentLineAttrs, options);
if(width > 0) { if(width > 0) {
return svg.make('rect', Object.assign({ return svg.make('rect', Object.assign({
'x': x - width / 2, 'x': x - width / 2,
@ -55,7 +68,7 @@ define([
'width': width, 'width': width,
'height': y1 - y0, 'height': y1 - y0,
'class': className, 'class': className,
}, this.agentLineAttrs)); }, attrs));
} else { } else {
return svg.make('line', Object.assign({ return svg.make('line', Object.assign({
'x1': x, 'x1': x,
@ -63,7 +76,7 @@ define([
'x2': x, 'x2': x,
'y2': y1, 'y2': y1,
'class': className, 'class': className,
}, this.agentLineAttrs)); }, attrs));
} }
} }
} }
@ -113,6 +126,27 @@ define([
return g; return g;
}; };
BaseTheme.renderDB = (attrs, {x, y, width, height}) => {
const z = attrs['db-z'];
return svg.make('g', {}, [
svg.make('rect', Object.assign({
'x': x,
'y': y,
'width': width,
'height': height,
'rx': width / 2,
'ry': z,
}, attrs)),
svg.make('path', Object.assign({
'd': (
'M' + x + ' ' + (y + z) +
'a' + (width / 2) + ' ' + z +
' 0 0 0 ' + width + ' 0'
),
}, attrs, {'fill': 'none'})),
]);
};
BaseTheme.renderCross = (attrs, {x, y, radius}) => { BaseTheme.renderCross = (attrs, {x, y, radius}) => {
return svg.make('path', Object.assign({ return svg.make('path', Object.assign({
'd': ( 'd': (

View File

@ -43,6 +43,27 @@ define([
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 15,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'db-z': 5,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 20, size: 20,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -182,10 +203,15 @@ define([
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
'red': {
'stroke': '#CC0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {

View File

@ -46,6 +46,28 @@ define([
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 5,
left: 3,
right: 3,
bottom: 1,
},
arrowBottom: 1 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 3,
'db-z': 2,
}),
labelAttrs: {
'font-family': FONT,
'font-weight': 'bold',
'font-size': 14,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 20, size: 20,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -193,10 +215,15 @@ define([
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 3, 'stroke-width': 3,
}, },
'red': {
'stroke': '#DD0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {

View File

@ -52,6 +52,27 @@ define([
'text-anchor': 'middle', 'text-anchor': 'middle',
}, },
}, },
database: {
padding: {
top: 12,
left: 8,
right: 8,
bottom: 4,
},
arrowBottom: 4 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, {
'fill': '#FFFFFF',
'stroke': '#000000',
'stroke-width': 1,
'db-z': 4,
}),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 16, size: 16,
render: BaseTheme.renderCross.bind(null, { render: BaseTheme.renderCross.bind(null, {
@ -189,10 +210,15 @@ define([
}, },
agentLineAttrs: { agentLineAttrs: {
'': {
'fill': 'none', 'fill': 'none',
'stroke': '#000000', 'stroke': '#000000',
'stroke-width': 1, 'stroke-width': 1,
}, },
'red': {
'stroke': '#AA0000',
},
},
}; };
const SHARED_BLOCK_SECTION = { const SHARED_BLOCK_SECTION = {

View File

@ -56,6 +56,25 @@ define([
}, },
boxRenderer: null, boxRenderer: null,
}, },
database: {
padding: {
top: 15,
left: 10,
right: 10,
bottom: 5,
},
arrowBottom: 5 + 12 * 1.3 / 2,
boxRenderer: BaseTheme.renderDB.bind(null, Object.assign({
'fill': '#FFFFFF',
'db-z': 5,
}, PENCIL.normal)),
labelAttrs: {
'font-family': FONT,
'font-size': 12,
'line-height': LINE_HEIGHT,
'text-anchor': 'middle',
},
},
cross: { cross: {
size: 15, size: 15,
render: null, render: null,
@ -172,9 +191,12 @@ define([
}, },
agentLineAttrs: { agentLineAttrs: {
'': Object.assign({
'fill': 'none', 'fill': 'none',
'stroke': '#000000', }, PENCIL.normal),
'stroke-width': 1, 'red': {
'stroke': 'rgba(200,40,0,0.8)',
},
}, },
}; };
@ -546,7 +568,9 @@ define([
'd': line.nodes, 'd': line.nodes,
'fill': 'none', 'fill': 'none',
'stroke-dasharray': lineOptions.dash ? '6, 5' : 'none', 'stroke-dasharray': lineOptions.dash ? '6, 5' : 'none',
}, lineOptions.thick ? PENCIL.thick : PENCIL.normal)); }, lineOptions.attrs || (
lineOptions.thick ? PENCIL.thick : PENCIL.normal
)));
return shape; return shape;
} }
@ -575,11 +599,11 @@ define([
return lT.nodes + lR.nodes + lB.nodes + lL.nodes; return lT.nodes + lR.nodes + lB.nodes + lL.nodes;
} }
renderBox(position, {fill = null, thick = false} = {}) { renderBox(position, {fill = null, thick = false, attrs = null} = {}) {
return svg.make('path', Object.assign({ return svg.make('path', Object.assign({
'd': this.boxNodes(position), 'd': this.boxNodes(position),
'fill': fill || '#FFFFFF', 'fill': fill || '#FFFFFF',
}, thick ? PENCIL.thick : PENCIL.normal)); }, attrs || (thick ? PENCIL.thick : PENCIL.normal)));
} }
renderNote({x, y, width, height}) { renderNote({x, y, width, height}) {
@ -905,21 +929,22 @@ define([
}, PENCIL.normal)); }, PENCIL.normal));
} }
renderAgentLine({x, y0, y1, width, className}) { renderAgentLine({x, y0, y1, width, className, options}) {
const attrs = this.optionsAttributes(this.agentLineAttrs, options);
if(width > 0) { if(width > 0) {
const shape = this.renderBox({ const shape = this.renderBox({
x: x - width / 2, x: x - width / 2,
y: y0, y: y0,
width, width,
height: y1 - y0, height: y1 - y0,
}, {fill: 'none'}); }, {fill: 'none', attrs});
shape.setAttribute('class', className); shape.setAttribute('class', className);
return shape; return shape;
} else { } else {
const shape = this.renderLine( const shape = this.renderLine(
{x, y: y0}, {x, y: y0},
{x, y: y1}, {x, y: y1},
{varY: 0.3} {varY: 0.3, attrs}
); );
shape.setAttribute('class', className); shape.setAttribute('class', className);
return shape; return shape;