Add parts library

This commit is contained in:
David Evans 2017-11-11 20:08:00 +00:00
parent 25ffd6a904
commit 85b4f99ccd
4 changed files with 332 additions and 25 deletions

View File

@ -28,6 +28,18 @@ define([
events.forEach((event) => element.addEventListener(event, fn)); events.forEach((event) => element.addEventListener(event, fn));
} }
function simplifyPreview(code) {
code = code.replace(/\{Agent([0-9]*)\}/g, (match, num) => {
if(num === undefined) {
return 'A';
} else {
return String.fromCharCode('A'.charCodeAt(0) + Number(num) - 1);
}
});
code = code.replace(/[{}]/g, '');
return code;
}
return class Interface { return class Interface {
constructor({ constructor({
parser, parser,
@ -36,6 +48,7 @@ define([
exporter, exporter,
defaultCode = '', defaultCode = '',
localStorage = '', localStorage = '',
library = null,
}) { }) {
this.parser = parser; this.parser = parser;
this.generator = generator; this.generator = generator;
@ -43,6 +56,7 @@ define([
this.exporter = exporter; this.exporter = exporter;
this.defaultCode = defaultCode; this.defaultCode = defaultCode;
this.localStorage = localStorage; this.localStorage = localStorage;
this.library = library;
this.minScale = 1.5; this.minScale = 1.5;
this.debounced = null; this.debounced = null;
@ -133,8 +147,8 @@ define([
code.on('endCompletion', () => { code.on('endCompletion', () => {
lastKey = 0; lastKey = 0;
}); });
code.on('change', (cm) => { code.on('change', (cm, change) => {
if(cm.state.completionActive) { if(cm.state.completionActive || change.origin === 'library') {
return; return;
} }
if(lastKey === 13 || lastKey === 8) { if(lastKey === 13 || lastKey === 8) {
@ -193,12 +207,43 @@ define([
}); });
} }
build(container) { buildLibrary(container) {
const codePane = makeNode('div', {'class': 'pane-code'}); this.library.forEach((lib) => {
const viewPane = makeNode('div', {'class': 'pane-view'}); const hold = makeNode('div', {
this.errorPane = makeNode('div', {'class': 'pane-error'}); 'class': 'library-item',
});
const holdInner = makeNode('div', {
'title': lib.title || lib.code,
});
hold.appendChild(holdInner);
hold.addEventListener(
'click',
this.addCodeBlock.bind(this, lib.code)
);
container.appendChild(hold);
try {
const preview = simplifyPreview(lib.preview || lib.code);
const parsed = this.parser.parse(preview);
const generated = this.generator.generate(parsed);
const rendering = this.renderer.clone();
holdInner.appendChild(rendering.svg());
rendering.render(generated);
} catch(e) {
hold.setAttribute('class', 'library-item broken');
holdInner.appendChild(makeText(lib.code));
}
});
}
buildErrorReport() {
this.errorMsg = makeNode('div', {'class': 'msg-error'});
this.errorText = makeText(); this.errorText = makeText();
this.errorPane.appendChild(this.errorText); this.errorMsg.appendChild(this.errorText);
return this.errorMsg;
}
buildViewPane() {
const viewPane = makeNode('div', {'class': 'pane-view'});
const viewPaneScroller = makeNode('div', { const viewPaneScroller = makeNode('div', {
'class': 'pane-view-scroller', 'class': 'pane-view-scroller',
}); });
@ -210,10 +255,31 @@ define([
viewPaneScroller.appendChild(this.viewPaneInner); viewPaneScroller.appendChild(this.viewPaneInner);
viewPane.appendChild(this.buildOptionsLinks()); viewPane.appendChild(this.buildOptionsLinks());
viewPane.appendChild(this.buildOptionsDownloads()); viewPane.appendChild(this.buildOptionsDownloads());
viewPane.appendChild(this.buildErrorReport());
return viewPane;
}
build(container) {
const codePane = makeNode('div', {'class': 'pane-code'});
container.appendChild(codePane); container.appendChild(codePane);
container.appendChild(this.errorPane);
container.appendChild(viewPane); if(this.library !== null) {
const libPane = makeNode('div', {'class': 'pane-library'});
const libPaneScroller = makeNode('div', {
'class': 'pane-library-scroller',
});
const libPaneInner = makeNode('div', {
'class': 'pane-library-inner',
});
libPaneScroller.appendChild(libPaneInner);
libPane.appendChild(libPaneScroller);
container.appendChild(libPane);
codePane.setAttribute('class', 'pane-code reduced');
this.buildLibrary(libPaneInner);
}
container.appendChild(this.buildViewPane());
this.code = this.buildEditor(codePane); this.code = this.buildEditor(codePane);
this.viewPaneInner.appendChild(this.renderer.svg()); this.viewPaneInner.appendChild(this.renderer.svg());
@ -222,6 +288,20 @@ define([
this.update(); this.update();
} }
addCodeBlock(block) {
const cur = this.code.getCursor('head');
const pos = {line: cur.line + ((cur.ch > 0) ? 1 : 0), ch: 0};
const lines = block.split('\n').length;
this.code.replaceRange(
block + '\n',
pos,
null,
'library'
);
this.code.setCursor({line: pos.line + lines, ch: 0});
this.code.focus();
}
updateMinSize(width, height) { updateMinSize(width, height) {
const style = this.viewPaneInner.style; const style = this.viewPaneInner.style;
style.minWidth = Math.ceil(width * this.minScale) + 'px'; style.minWidth = Math.ceil(width * this.minScale) + 'px';
@ -265,12 +345,12 @@ define([
} else { } else {
this.errorText.nodeValue = error; this.errorText.nodeValue = error;
} }
this.errorPane.setAttribute('class', 'pane-error error'); this.errorMsg.setAttribute('class', 'msg-error error');
} }
markOK() { markOK() {
this.errorText.nodeValue = 'All OK'; this.errorText.nodeValue = '';
this.errorPane.setAttribute('class', 'pane-error ok'); this.errorMsg.setAttribute('class', 'msg-error');
} }
update(immediate = true) { update(immediate = true) {

View File

@ -40,6 +40,156 @@
'\n' + '\n' +
'terminators box\n' 'terminators box\n'
); );
const library = [
{
title: 'Simple arrow',
code: '{Agent1} -> {Agent2}: {Message}',
},
{
title: 'Arrow with dotted line',
code: '{Agent1} --> {Agent2}: {Message}',
},
{
title: 'Open arrow',
code: '{Agent1} ->> {Agent2}: {Message}',
},
{
title: 'Self-connection',
code: '{Agent1} -> {Agent1}: {Message}',
},
{
title: 'Request/response pair',
code: (
'{Agent1} -> +{Agent2}: {Request}\n' +
'{Agent1} <-- -{Agent2}: {Response}'
),
},
{
title: 'Inline agent creation / destruction',
code: (
'{Agent1} -> *{Agent2}: {Request}\n' +
'{Agent1} <-- !{Agent2}: {Response}'
),
},
{
title: 'Agent creation / destruction',
code: (
'{Agent1} -> {Agent2}: {Request}\n' +
'{Agent1} <-- {Agent2}: {Response}\n' +
'end {Agent2}'
),
preview: (
'begin A\n' +
'::\n' +
'A -> B: Request\n' +
'A <-- B: Response\n' +
'end B'
),
},
{
title: 'Conditional blocks',
code: (
'if {Condition1}\n' +
' {Agent1} -> {Agent2}\n' +
'else if {Condition2}\n' +
' {Agent1} -> {Agent2}\n' +
'else\n' +
' {Agent1} -> {Agent2}\n' +
'end'
),
preview: (
'begin A, B\n' +
'if Condition1\n' +
' A -> B\n' +
'else if Condition2\n' +
' A -> B\n' +
'else\n' +
' A -> B\n' +
'end'
),
},
{
title: 'Repeated blocks',
code: (
'repeat {Condition}\n' +
' {Agent1} -> {Agent2}\n' +
'end'
),
preview: (
'begin A, B\n' +
'repeat Condition\n' +
' A -> B\n' +
'end'
),
},
{
title: 'Note over agent',
code: 'note over {Agent1}: {Message}',
},
{
title: 'Note over multiple agents',
code: 'note over {Agent1}, {Agent2}: {Message}',
},
{
title: 'Note left of agent',
code: 'note left of {Agent1}: {Message}',
},
{
title: 'Note right of agent',
code: 'note right of {Agent1}: {Message}',
},
{
title: 'Note between agents',
code: 'note between {Agent1}, {Agent2}: {Message}',
},
{
title: 'State over agent',
code: 'state over {Agent1}: {State}',
},
{
title: 'Arrows to/from the sides',
code: '[ -> {Agent1}: {Message1}\n{Agent1} -> ]: {Message2}',
},
{
title: 'Text beside the diagram',
code: 'text right: {Message}',
preview: (
'A -> B\n' +
'simultaneously:\n' +
'text right: "Message\\non the\\nside"'
),
},
{
title: 'Title',
code: 'title {Title}',
preview: 'title Title\nA -> B',
},
{
title: 'Chunky theme',
code: 'theme chunky',
preview: 'theme chunky\nA -> B',
},
{
title: 'Cross terminators',
code: 'terminators cross',
preview: 'A -> B\nterminators cross',
},
{
title: 'Fade terminators',
code: 'terminators fade',
preview: 'A -> B\nterminators fade',
},
{
title: 'Bar terminators',
code: 'terminators bar',
preview: 'A -> B\nterminators bar',
},
{
title: 'Box terminators',
code: 'terminators box',
preview: 'A -> B\nterminators box',
},
];
const ui = new Interface({ const ui = new Interface({
defaultCode, defaultCode,
parser: new Parser(), parser: new Parser(),
@ -49,6 +199,7 @@
new ChunkyTheme(), new ChunkyTheme(),
]}), ]}),
exporter: new Exporter(), exporter: new Exporter(),
library,
localStorage: 'src', localStorage: 'src',
}); });
ui.build(document.body); ui.build(document.body);

View File

@ -135,6 +135,15 @@ define([
}); });
} }
clone({namespace = null} = {}) {
return new Renderer({
themes: this.getThemes(),
namespace,
components: this.components,
SVGTextBlockClass: this.SVGTextBlockClass,
});
}
buildStaticElements() { buildStaticElements() {
this.base = svg.makeContainer(); this.base = svg.makeContainer();
@ -625,6 +634,10 @@ define([
); );
} }
getThemes() {
return this.getThemeNames().map((name) => this.themes.get(name));
}
getAgentX(name) { getAgentX(name) {
return this.agentInfos.get(name).x; return this.agentInfos.get(name).x;
} }

View File

@ -7,12 +7,16 @@ html, body {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
bottom: 100px; bottom: 0;
width: 30%; width: 30%;
box-sizing: border-box; box-sizing: border-box;
border-right: 1px solid #808080; border-right: 1px solid #808080;
} }
.pane-code.reduced {
bottom: 200px;
}
.pane-code .CodeMirror { .pane-code .CodeMirror {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -67,29 +71,88 @@ html, body {
height: 100%; height: 100%;
} }
.pane-error { .pane-library {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 30%; width: 30%;
height: 200px;
box-sizing: border-box;
background: #EEEEEE;
border-top: 1px solid #808080;
border-right: 1px solid #808080;
user-select: none;
}
.pane-library-scroller {
width: 100%;
height: 100%;
overflow: auto;
}
.pane-library-inner {
padding: 5px;
}
.library-item {
display: inline-block;
width: 80px;
height: 80px;
}
.library-item > div {
width: 80px;
height: 80px;
border: 2px solid #EEEEEE;
background: #FFFFFF;
box-sizing: border-box;
cursor: pointer;
overflow: hidden;
transition: transform 0.2s, border-color 0.2s, background 0.2s;
}
.library-item.broken > div {
padding: 5px;
font: 6px monospace;
white-space: pre;
}
.library-item svg {
width: 100%;
height: 100%;
}
.library-item:hover > div {
border-color: #FFCC00;
background: #FFFFDD;
/* position: absolute;*/
transform: scale(1.1);
z-index: 10;
}
.msg-error {
display: none;
position: absolute;
top: 50%;
left: 20%;
margin-top: -50px;
width: 60%;
height: 100px; height: 100px;
overflow: auto; overflow: auto;
box-sizing: border-box; box-sizing: border-box;
padding: 5px 10px; padding: 5px 10px;
font-family: monospace; font-family: monospace;
background: #DDDDDD;
border-top: 1px solid #808080;
border-right: 1px solid #808080;
}
.pane-error.ok {
color: #007700;
background: #E8EEE8;
}
.pane-error.error {
color: #770000; color: #770000;
background: #EEE8E8; background: #EEE8E8;
border: 1px solid rgba(0, 0, 0, 0.5);
border-radius: 5px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
opacity: 0.95;
z-index: 4;
}
.msg-error.error {
display: block;
} }
.options { .options {