Add lost message arrow style [#38]

This commit is contained in:
David Evans 2018-01-15 22:37:27 +00:00
parent 0c988e8658
commit afd505b8fe
16 changed files with 222 additions and 22 deletions

View File

@ -60,6 +60,8 @@ Bar -> Bar: Bar talks to itself
Foo -> +Bar: Foo asks Bar Foo -> +Bar: Foo asks Bar
-Bar --> Foo: and Bar replies -Bar --> Foo: and Bar replies
Bar -x Baz: Lost message
# Arrows leaving on the left and right of the diagram # Arrows leaving on the left and right of the diagram
[ -> Foo: From the left [ -> Foo: From the left
[ <- Foo: To the left [ <- Foo: To the left

View File

@ -1053,8 +1053,11 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
}); });
} }
_tokenEndFound(stream, state, block) { _tokenEndFound(stream, state, block, match) {
state.currentType = -1; state.currentType = -1;
if(block.includeEnd) {
state.current += match[0];
}
if(block.omit) { if(block.omit) {
return 'comment'; return 'comment';
} }
@ -1077,8 +1080,13 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
while(true) { while(true) {
const block = this.tokenDefinitions[state.currentType]; const block = this.tokenDefinitions[state.currentType];
this._tokenCheckEscape(stream, state, block); this._tokenCheckEscape(stream, state, block);
if(!block.end || this._matchPattern(stream, block.end, true)) { if(!block.end) {
return this._tokenEndFound(stream, state, block); return this._tokenEndFound(stream, state, block, null);
} else {
const match = this._matchPattern(stream, block.end, true);
if(match) {
return this._tokenEndFound(stream, state, block, match);
}
} }
if(stream.eol()) { if(stream.eol()) {
return this._tokenEOLFound(stream, state, block); return this._tokenEOLFound(stream, state, block);
@ -1143,7 +1151,11 @@ define('sequence/Tokeniser',['./CodeMirrorMode'], (CMMode) => {
baseToken: {q: true}, baseToken: {q: true},
}, },
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y}, {start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
{start: /(?=[\-~<>])/y, end: /(?=[^\-~<>])|$/y}, {
start: /(?=[\-~<])/y,
end: /(?=[^\-~<>x])|[\-~]x|[<>](?=x)|$/y,
includeEnd: true,
},
{start: /,/y, baseToken: {v: ','}}, {start: /,/y, baseToken: {v: ','}},
{start: /:/y, baseToken: {v: ':'}}, {start: /:/y, baseToken: {v: ':'}},
{start: /!/y, baseToken: {v: '!'}}, {start: /!/y, baseToken: {v: '!'}},
@ -1194,7 +1206,7 @@ define('sequence/Tokeniser',['./CodeMirrorMode'], (CMMode) => {
newBlock: null, newBlock: null,
end: true, end: true,
appendSpace: '', appendSpace: '',
appendValue: '', appendValue: block.includeEnd ? match[0] : '',
skip: match[0].length, skip: match[0].length,
}; };
} }
@ -1565,6 +1577,7 @@ define('sequence/Parser',[
{tok: '', type: 0}, {tok: '', type: 0},
{tok: '>', type: 1}, {tok: '>', type: 1},
{tok: '>>', type: 2}, {tok: '>>', type: 2},
{tok: 'x', type: 3},
]; ];
const arrows = (array.combine([lTypes, mTypes, rTypes]) const arrows = (array.combine([lTypes, mTypes, rTypes])
.filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0)) .filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0))
@ -4264,6 +4277,34 @@ define('sequence/components/Connect',[
} }
} }
class Arrowcross {
getConfig(theme) {
return theme.connect.arrow.cross;
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
layer.appendChild(config.render({
x: pt.x + config.short * dir,
y: pt.y,
radius: config.radius,
}));
}
width(theme) {
const config = this.getConfig(theme);
return config.short + config.radius;
}
height(theme) {
return this.getConfig(theme).radius * 2;
}
lineGap(theme) {
return this.getConfig(theme).short;
}
}
const ARROWHEADS = [ const ARROWHEADS = [
{ {
render: () => {}, render: () => {},
@ -4273,6 +4314,7 @@ define('sequence/components/Connect',[
}, },
new Arrowhead('single'), new Arrowhead('single'),
new Arrowhead('double'), new Arrowhead('double'),
new Arrowcross(),
]; ];
class Connect extends BaseComponent { class Connect extends BaseComponent {
@ -5839,6 +5881,15 @@ define('sequence/themes/Basic',[
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
'cross': {
short: 7,
radius: 3,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
}),
},
}, },
label: { label: {
padding: 6, padding: 6,
@ -6151,6 +6202,15 @@ define('sequence/themes/Monospace',[
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
'cross': {
short: 8,
radius: 4,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
}),
},
}, },
label: { label: {
padding: 4, padding: 4,
@ -6437,7 +6497,7 @@ define('sequence/themes/Chunky',[
}, },
}, },
arrow: { arrow: {
single: { 'single': {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead, render: BaseTheme.renderHorizArrowHead,
@ -6448,7 +6508,7 @@ define('sequence/themes/Chunky',[
'stroke-linejoin': 'round', 'stroke-linejoin': 'round',
}, },
}, },
double: { 'double': {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead, render: BaseTheme.renderHorizArrowHead,
@ -6460,6 +6520,17 @@ define('sequence/themes/Chunky',[
'stroke-linecap': 'round', 'stroke-linecap': 'round',
}, },
}, },
'cross': {
short: 10,
radius: 5,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
}),
},
}, },
label: { label: {
padding: 7, padding: 7,
@ -7195,6 +7266,11 @@ define('sequence/themes/Sketch',[
}, PENCIL), }, PENCIL),
render: null, render: null,
}, },
'cross': {
short: 5,
radius: 3,
render: null,
},
}, },
label: { label: {
padding: 6, padding: 6,
@ -7432,6 +7508,7 @@ define('sequence/themes/Sketch',[
this.connect.arrow.single.render = this.renderArrowHead; this.connect.arrow.single.render = this.renderArrowHead;
this.connect.arrow.double.render = this.renderArrowHead; this.connect.arrow.double.render = this.renderArrowHead;
this.connect.arrow.cross.render = this.renderCross.bind(this);
this.connect.line.solid.renderFlat = this.renderFlatConnector; this.connect.line.solid.renderFlat = this.renderFlatConnector;
this.connect.line.solid.renderRev = this.renderRevConnector; this.connect.line.solid.renderRev = this.renderRevConnector;

File diff suppressed because one or more lines are too long

View File

@ -174,6 +174,8 @@ Bar -> Bar: Bar talks to itself
Foo -> +Bar: Foo asks Bar Foo -> +Bar: Foo asks Bar
-Bar --> Foo: and Bar replies -Bar --> Foo: and Bar replies
Bar -x Baz: Lost message
# Arrows leaving on the left and right of the diagram # Arrows leaving on the left and right of the diagram
[ -> Foo: From the left [ -> Foo: From the left
[ <- Foo: To the left [ <- Foo: To the left

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -33,17 +33,21 @@
); );
const library = [ const library = [
{ {
title: 'Simple arrow', title: 'Simple arrow (synchronous)',
code: '{Agent1} -> {Agent2}: {Message}', code: '{Agent1} -> {Agent2}: {Message}',
}, },
{ {
title: 'Arrow with dotted line', title: 'Arrow with dotted line (response)',
code: '{Agent1} --> {Agent2}: {Message}', code: '{Agent1} --> {Agent2}: {Message}',
}, },
{ {
title: 'Open arrow', title: 'Open arrow (asynchronous)',
code: '{Agent1} ->> {Agent2}: {Message}', code: '{Agent1} ->> {Agent2}: {Message}',
}, },
{
title: 'Lost message',
code: '{Agent1} -x {Agent2}: {Message}',
},
{ {
title: 'Wavy line', title: 'Wavy line',
code: '{Agent1} ~> {Agent2}: {Message}', code: '{Agent1} ~> {Agent2}: {Message}',
@ -114,7 +118,7 @@
), ),
}, },
{ {
title: 'Repeated blocks', title: 'Repeated block',
code: ( code: (
'repeat {Condition}\n' + 'repeat {Condition}\n' +
' {Agent1} -> {Agent2}\n' + ' {Agent1} -> {Agent2}\n' +
@ -128,7 +132,7 @@
), ),
}, },
{ {
title: 'References', title: 'Reference',
code: ( code: (
'begin reference: {Label} as {Name}\n' + 'begin reference: {Label} as {Name}\n' +
'{Agent1} -> {Name}\n' + '{Agent1} -> {Name}\n' +
@ -143,7 +147,7 @@
), ),
}, },
{ {
title: 'References over agents', title: 'Reference over agents',
code: ( code: (
'begin reference over {Covered}: {Label} as {Name}\n' + 'begin reference over {Covered}: {Label} as {Name}\n' +
'{Agent1} -> {Name}\n' + '{Agent1} -> {Name}\n' +

View File

@ -450,8 +450,11 @@ define(['core/ArrayUtilities'], (array) => {
}); });
} }
_tokenEndFound(stream, state, block) { _tokenEndFound(stream, state, block, match) {
state.currentType = -1; state.currentType = -1;
if(block.includeEnd) {
state.current += match[0];
}
if(block.omit) { if(block.omit) {
return 'comment'; return 'comment';
} }
@ -474,8 +477,13 @@ define(['core/ArrayUtilities'], (array) => {
while(true) { while(true) {
const block = this.tokenDefinitions[state.currentType]; const block = this.tokenDefinitions[state.currentType];
this._tokenCheckEscape(stream, state, block); this._tokenCheckEscape(stream, state, block);
if(!block.end || this._matchPattern(stream, block.end, true)) { if(!block.end) {
return this._tokenEndFound(stream, state, block); return this._tokenEndFound(stream, state, block, null);
} else {
const match = this._matchPattern(stream, block.end, true);
if(match) {
return this._tokenEndFound(stream, state, block, match);
}
} }
if(stream.eol()) { if(stream.eol()) {
return this._tokenEOLFound(stream, state, block); return this._tokenEOLFound(stream, state, block);

View File

@ -47,6 +47,7 @@ define([
{tok: '', type: 0}, {tok: '', type: 0},
{tok: '>', type: 1}, {tok: '>', type: 1},
{tok: '>>', type: 2}, {tok: '>>', type: 2},
{tok: 'x', type: 3},
]; ];
const arrows = (array.combine([lTypes, mTypes, rTypes]) const arrows = (array.combine([lTypes, mTypes, rTypes])
.filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0)) .filter((arrow) => (arrow[0].type !== 0 || arrow[2].type !== 0))

View File

@ -219,6 +219,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
'A<<-B\n' + 'A<<-B\n' +
'A<<->B\n' + 'A<<->B\n' +
'A<<->>B\n' + 'A<<->>B\n' +
'A-xB\n' +
'A-->B\n' + 'A-->B\n' +
'A-->>B\n' + 'A-->>B\n' +
'A<--B\n' + 'A<--B\n' +
@ -227,6 +228,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
'A<<--B\n' + 'A<<--B\n' +
'A<<-->B\n' + 'A<<-->B\n' +
'A<<-->>B\n' + 'A<<-->>B\n' +
'A--xB\n' +
'A~>B\n' + 'A~>B\n' +
'A~>>B\n' + 'A~>>B\n' +
'A<~B\n' + 'A<~B\n' +
@ -234,7 +236,8 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
'A<~>>B\n' + 'A<~>>B\n' +
'A<<~B\n' + 'A<<~B\n' +
'A<<~>B\n' + 'A<<~>B\n' +
'A<<~>>B\n' 'A<<~>>B\n' +
'A~xB\n'
); );
expect(parsed.stages).toEqual([ expect(parsed.stages).toEqual([
PARSED.connect(['A', 'B'], { PARSED.connect(['A', 'B'], {
@ -250,6 +253,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 0}), PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 0}),
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 1}), PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 1}),
PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 2}), PARSED.connect(['A', 'B'], {line: 'solid', left: 2, right: 2}),
PARSED.connect(['A', 'B'], {line: 'solid', left: 0, right: 3}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 1}), PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 1}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 2}), PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 2}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 0}), PARSED.connect(['A', 'B'], {line: 'dash', left: 1, right: 0}),
@ -258,6 +262,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 0}), PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 0}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 1}), PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 1}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 2}), PARSED.connect(['A', 'B'], {line: 'dash', left: 2, right: 2}),
PARSED.connect(['A', 'B'], {line: 'dash', left: 0, right: 3}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 1}), PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 1}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 2}), PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 2}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 1, right: 0}), PARSED.connect(['A', 'B'], {line: 'wave', left: 1, right: 0}),
@ -266,6 +271,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 0}), PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 0}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 1}), PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 1}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 2}), PARSED.connect(['A', 'B'], {line: 'wave', left: 2, right: 2}),
PARSED.connect(['A', 'B'], {line: 'wave', left: 0, right: 3}),
]); ]);
}); });

View File

@ -32,7 +32,11 @@ define(['./CodeMirrorMode'], (CMMode) => {
baseToken: {q: true}, baseToken: {q: true},
}, },
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y}, {start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
{start: /(?=[\-~<>])/y, end: /(?=[^\-~<>])|$/y}, {
start: /(?=[\-~<])/y,
end: /(?=[^\-~<>x])|[\-~]x|[<>](?=x)|$/y,
includeEnd: true,
},
{start: /,/y, baseToken: {v: ','}}, {start: /,/y, baseToken: {v: ','}},
{start: /:/y, baseToken: {v: ':'}}, {start: /:/y, baseToken: {v: ':'}},
{start: /!/y, baseToken: {v: '!'}}, {start: /!/y, baseToken: {v: '!'}},
@ -83,7 +87,7 @@ define(['./CodeMirrorMode'], (CMMode) => {
newBlock: null, newBlock: null,
end: true, end: true,
appendSpace: '', appendSpace: '',
appendValue: '', appendValue: block.includeEnd ? match[0] : '',
skip: match[0].length, skip: match[0].length,
}; };
} }

View File

@ -174,6 +174,38 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
'Unterminated literal (began at line 1, character 0)' 'Unterminated literal (began at line 1, character 0)'
)); ));
}); });
it('stops tokenising arrows once they become invalid', () => {
expect(tokeniser.tokenise('foo -> bar')).toEqual([
token({s: '', v: 'foo'}),
token({s: ' ', v: '->'}),
token({s: ' ', v: 'bar'}),
]);
expect(tokeniser.tokenise('foo->bar')).toEqual([
token({s: '', v: 'foo'}),
token({s: '', v: '->'}),
token({s: '', v: 'bar'}),
]);
expect(tokeniser.tokenise('foo-xbar')).toEqual([
token({s: '', v: 'foo'}),
token({s: '', v: '-x'}),
token({s: '', v: 'bar'}),
]);
expect(tokeniser.tokenise('foo-xxyz')).toEqual([
token({s: '', v: 'foo'}),
token({s: '', v: '-x'}),
token({s: '', v: 'xyz'}),
]);
expect(tokeniser.tokenise('foo->xyz')).toEqual([
token({s: '', v: 'foo'}),
token({s: '', v: '->'}),
token({s: '', v: 'xyz'}),
]);
});
}); });
describe('.splitLines', () => { describe('.splitLines', () => {

View File

@ -65,6 +65,34 @@ define([
} }
} }
class Arrowcross {
getConfig(theme) {
return theme.connect.arrow.cross;
}
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
layer.appendChild(config.render({
x: pt.x + config.short * dir,
y: pt.y,
radius: config.radius,
}));
}
width(theme) {
const config = this.getConfig(theme);
return config.short + config.radius;
}
height(theme) {
return this.getConfig(theme).radius * 2;
}
lineGap(theme) {
return this.getConfig(theme).short;
}
}
const ARROWHEADS = [ const ARROWHEADS = [
{ {
render: () => {}, render: () => {},
@ -74,6 +102,7 @@ define([
}, },
new Arrowhead('single'), new Arrowhead('single'),
new Arrowhead('double'), new Arrowhead('double'),
new Arrowcross(),
]; ];
class Connect extends BaseComponent { class Connect extends BaseComponent {

View File

@ -125,6 +125,15 @@ define([
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
'cross': {
short: 7,
radius: 3,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
}),
},
}, },
label: { label: {
padding: 6, padding: 6,

View File

@ -110,7 +110,7 @@ define([
}, },
}, },
arrow: { arrow: {
single: { 'single': {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead, render: BaseTheme.renderHorizArrowHead,
@ -121,7 +121,7 @@ define([
'stroke-linejoin': 'round', 'stroke-linejoin': 'round',
}, },
}, },
double: { 'double': {
width: 10, width: 10,
height: 12, height: 12,
render: BaseTheme.renderHorizArrowHead, render: BaseTheme.renderHorizArrowHead,
@ -133,6 +133,17 @@ define([
'stroke-linecap': 'round', 'stroke-linecap': 'round',
}, },
}, },
'cross': {
short: 10,
radius: 5,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 3,
'stroke-linejoin': 'round',
'stroke-linecap': 'round',
}),
},
}, },
label: { label: {
padding: 7, padding: 7,

View File

@ -132,6 +132,15 @@ define([
'stroke-linejoin': 'miter', 'stroke-linejoin': 'miter',
}, },
}, },
'cross': {
short: 8,
radius: 4,
render: BaseTheme.renderCross.bind(null, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': 1,
}),
},
}, },
label: { label: {
padding: 4, padding: 4,

View File

@ -118,6 +118,11 @@ define([
}, PENCIL), }, PENCIL),
render: null, render: null,
}, },
'cross': {
short: 5,
radius: 3,
render: null,
},
}, },
label: { label: {
padding: 6, padding: 6,
@ -355,6 +360,7 @@ define([
this.connect.arrow.single.render = this.renderArrowHead; this.connect.arrow.single.render = this.renderArrowHead;
this.connect.arrow.double.render = this.renderArrowHead; this.connect.arrow.double.render = this.renderArrowHead;
this.connect.arrow.cross.render = this.renderCross.bind(this);
this.connect.line.solid.renderFlat = this.renderFlatConnector; this.connect.line.solid.renderFlat = this.renderFlatConnector;
this.connect.line.solid.renderRev = this.renderRevConnector; this.connect.line.solid.renderRev = this.renderRevConnector;