diff --git a/scripts/main.js b/scripts/main.js
index a39a991..28797cd 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -11,13 +11,15 @@
'sequence/Generator',
'sequence/Renderer',
'sequence/themes/Basic',
+ 'sequence/themes/Chunky',
], (
Interface,
Exporter,
Parser,
Generator,
Renderer,
- Theme
+ BasicTheme,
+ ChunkyTheme
) => {
const defaultCode = (
'title Labyrinth\n' +
@@ -41,7 +43,10 @@
defaultCode,
parser: new Parser(),
generator: new Generator(),
- renderer: new Renderer(new Theme()),
+ renderer: new Renderer({themes: [
+ new BasicTheme(),
+ new ChunkyTheme(),
+ ]}),
exporter: new Exporter(),
localStorage: 'src',
});
diff --git a/scripts/readme_images.js b/scripts/readme_images.js
index 6ab372e..85fee0a 100644
--- a/scripts/readme_images.js
+++ b/scripts/readme_images.js
@@ -17,8 +17,18 @@
return o;
}
+ const FAVICON_SRC = (
+ 'theme chunky\n' +
+ 'define ABC as A, DEF as B\n' +
+ 'A -> B\n' +
+ 'B -> ]\n' +
+ '] -> B\n' +
+ 'B -> A\n' +
+ 'terminators fade'
+ );
+
const SAMPLE_REGEX = new RegExp(
- /
]*>[\s]*```(?!shell).*\n([^]+?)```/g
+ /
]*>[\s]*```(?!shell).*\n([^]+?)```/g
);
function findSamples(content) {
@@ -34,9 +44,23 @@
code: match[2],
});
}
+ results.push({
+ file: 'favicon.png',
+ code: FAVICON_SRC,
+ height: 64,
+ });
return results;
}
+ function filename(path) {
+ const p = path.lastIndexOf('/');
+ if(p !== -1) {
+ return path.substr(p + 1);
+ } else {
+ return path;
+ }
+ }
+
const PNG_RESOLUTION = 4;
/* jshint -W072 */ // Allow several required modules
@@ -45,25 +69,30 @@
'sequence/Generator',
'sequence/Renderer',
'sequence/themes/Basic',
+ 'sequence/themes/Chunky',
'interface/Exporter',
], (
Parser,
Generator,
Renderer,
- Theme,
+ BasicTheme,
+ ChunkyTheme,
Exporter
) => {
const parser = new Parser();
const generator = new Generator();
- const theme = new Theme();
+ const themes = [
+ new BasicTheme(),
+ new ChunkyTheme(),
+ ];
const status = makeNode('div', {'class': 'status'});
const statusText = makeText('Loading\u2026');
status.appendChild(statusText);
document.body.appendChild(status);
- function renderSample({file, code}) {
- const renderer = new Renderer(theme);
+ function renderSample({file, code, height}) {
+ const renderer = new Renderer({themes});
const exporter = new Exporter();
const hold = makeNode('div', {'class': 'hold'});
@@ -78,12 +107,15 @@
hold.appendChild(raster);
hold.appendChild(makeNode('img', {
- 'src': 'screenshots/' + file,
+ 'src': file,
'class': 'original',
'title': 'original',
}));
- const downloadPNG = makeNode('a', {'href': '#', 'download': file});
+ const downloadPNG = makeNode('a', {
+ 'href': '#',
+ 'download': filename(file),
+ });
downloadPNG.appendChild(makeText('Download PNG'));
hold.appendChild(downloadPNG);
@@ -92,7 +124,11 @@
const parsed = parser.parse(code);
const sequence = generator.generate(parsed);
renderer.render(sequence);
- exporter.getPNGURL(renderer, PNG_RESOLUTION, (url) => {
+ let resolution = PNG_RESOLUTION;
+ if(height) {
+ resolution = height / renderer.height;
+ }
+ exporter.getPNGURL(renderer, resolution, (url) => {
raster.setAttribute('src', url);
downloadPNG.setAttribute('href', url);
});
diff --git a/scripts/sequence/CodeMirrorMode.js b/scripts/sequence/CodeMirrorMode.js
index 1b6f227..e897d9a 100644
--- a/scripts/sequence/CodeMirrorMode.js
+++ b/scripts/sequence/CodeMirrorMode.js
@@ -121,6 +121,9 @@ define(['core/ArrayUtilities'], (array) => {
'title': {type: 'keyword', suggest: true, then: {
'': textToEnd,
}},
+ 'theme': {type: 'keyword', suggest: true, then: {
+ '': textToEnd,
+ }},
'terminators': {type: 'keyword', suggest: true, then: {
'none': {type: 'keyword', suggest: true, then: {}},
'cross': {type: 'keyword', suggest: true, then: {}},
diff --git a/scripts/sequence/Generator.js b/scripts/sequence/Generator.js
index b1bea09..fa4c524 100644
--- a/scripts/sequence/Generator.js
+++ b/scripts/sequence/Generator.js
@@ -539,6 +539,7 @@ define(['core/ArrayUtilities'], (array) => {
return {
meta: {
title: meta.title,
+ theme: meta.theme,
},
agents: this.agents.slice(),
stages: globals.stages,
diff --git a/scripts/sequence/Generator_spec.js b/scripts/sequence/Generator_spec.js
index b5701f4..38275a2 100644
--- a/scripts/sequence/Generator_spec.js
+++ b/scripts/sequence/Generator_spec.js
@@ -148,13 +148,13 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
};
describe('.generate', () => {
- it('propagates title metadata', () => {
+ it('propagates title and theme metadata', () => {
const input = {
- meta: {title: 'bar'},
+ meta: {title: 'bar', theme: 'zig', nope: 'skip'},
stages: [],
};
const sequence = generator.generate(input);
- expect(sequence.meta).toEqual({title: 'bar'});
+ expect(sequence.meta).toEqual({title: 'bar', theme: 'zig'});
});
it('returns an empty sequence for blank input', () => {
diff --git a/scripts/sequence/Parser.js b/scripts/sequence/Parser.js
index 5334aa0..2de9327 100644
--- a/scripts/sequence/Parser.js
+++ b/scripts/sequence/Parser.js
@@ -188,6 +188,15 @@ define([
return true;
},
+ (line, meta) => { // theme
+ if(tokenKeyword(line[0]) !== 'theme') {
+ return null;
+ }
+
+ meta.theme = joinLabel(line, 1);
+ return true;
+ },
+
(line, meta) => { // terminators
if(tokenKeyword(line[0]) !== 'terminators') {
return null;
@@ -356,6 +365,7 @@ define([
const result = {
meta: {
title: '',
+ theme: '',
terminators: 'none',
},
stages: [],
diff --git a/scripts/sequence/Parser_spec.js b/scripts/sequence/Parser_spec.js
index fdc6171..c7254de 100644
--- a/scripts/sequence/Parser_spec.js
+++ b/scripts/sequence/Parser_spec.js
@@ -33,6 +33,7 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
expect(parsed).toEqual({
meta: {
title: '',
+ theme: '',
terminators: 'none',
},
stages: [],
@@ -44,6 +45,11 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
expect(parsed.meta.title).toEqual('foo');
});
+ it('reads theme metadata', () => {
+ const parsed = parser.parse('theme foo');
+ expect(parsed.meta.theme).toEqual('foo');
+ });
+
it('reads terminators metadata', () => {
const parsed = parser.parse('terminators bar');
expect(parsed.meta.terminators).toEqual('bar');
diff --git a/scripts/sequence/Renderer.js b/scripts/sequence/Renderer.js
index 11ca9ee..ba24a34 100644
--- a/scripts/sequence/Renderer.js
+++ b/scripts/sequence/Renderer.js
@@ -63,8 +63,24 @@ define([
};
}
+ function makeThemes(themes) {
+ if(themes.length === 0) {
+ throw new Error('Cannot render without a theme');
+ }
+ const themeMap = new Map();
+ themes.forEach((theme) => {
+ themeMap.set(theme.name, theme);
+ });
+ themeMap.set('', themes[0]);
+ return themeMap;
+ }
+
+ let globalNamespace = 0;
+
return class Renderer {
- constructor(theme, {
+ constructor({
+ themes = [],
+ namespace = null,
components = null,
SVGTextBlockClass = SVGShapes.TextBlock,
} = {}) {
@@ -93,11 +109,16 @@ define([
this.state = {};
this.width = 0;
this.height = 0;
- this.theme = theme;
+ this.themes = makeThemes(themes);
+ this.theme = null;
+ this.namespace = namespace;
+ if(namespace === null) {
+ this.namespace = 'R' + globalNamespace;
+ ++ globalNamespace;
+ }
this.components = components;
this.SVGTextBlockClass = SVGTextBlockClass;
this.knownDefs = new Set();
- this.currentSequence = null;
this.buildStaticElements();
this.components.forEach((component) => {
component.makeState(this.state);
@@ -112,11 +133,13 @@ define([
this.defs = svg.make('defs');
this.mask = svg.make('mask', {
- 'id': 'lineMask',
+ 'id': this.namespace + 'LineMask',
'maskUnits': 'userSpaceOnUse',
});
this.maskReveal = svg.make('rect', {'fill': '#FFFFFF'});
- this.agentLines = svg.make('g', {'mask': 'url(#lineMask)'});
+ this.agentLines = svg.make('g', {
+ 'mask': 'url(#' + this.namespace + 'LineMask)',
+ });
this.blocks = svg.make('g');
this.sections = svg.make('g');
this.actionShapes = svg.make('g');
@@ -133,11 +156,15 @@ define([
}
addDef(name, generator) {
+ const namespacedName = this.namespace + name;
if(this.knownDefs.has(name)) {
- return;
+ return namespacedName;
}
this.knownDefs.add(name);
- this.defs.appendChild(generator());
+ const def = generator();
+ def.setAttribute('id', namespacedName);
+ this.defs.appendChild(def);
+ return namespacedName;
}
addSeparation(agentName1, agentName2, dist) {
@@ -517,16 +544,6 @@ define([
this.height = (y1 - y0);
}
- setTheme(theme) {
- if(this.theme === theme) {
- return;
- }
- this.theme = theme;
- if(this.currentSequence) {
- this.render(this.currentSequence);
- }
- }
-
_reset() {
this.knownDefs.clear();
svg.empty(this.defs);
@@ -546,6 +563,12 @@ define([
render(sequence) {
this._reset();
+ const themeName = sequence.meta.theme;
+ this.theme = this.themes.get(themeName);
+ if(!this.theme) {
+ this.theme = this.themes.get('');
+ }
+
this.title.set({
attrs: this.theme.titleAttrs,
text: sequence.meta.title,
@@ -564,7 +587,6 @@ define([
this.sizer.resetCache();
this.sizer.detach();
- this.currentSequence = sequence;
}
getAgentX(name) {
diff --git a/scripts/sequence/Renderer_spec.js b/scripts/sequence/Renderer_spec.js
index 9e88687..7b8ff0e 100644
--- a/scripts/sequence/Renderer_spec.js
+++ b/scripts/sequence/Renderer_spec.js
@@ -3,14 +3,14 @@ defineDescribe('Sequence Renderer', [
'./themes/Basic',
], (
Renderer,
- Theme
+ BasicTheme
) => {
'use strict';
let renderer = null;
beforeEach(() => {
- renderer = new Renderer(new Theme());
+ renderer = new Renderer({themes: [new BasicTheme()]});
document.body.appendChild(renderer.svg());
});
diff --git a/scripts/sequence/components/AgentCap.js b/scripts/sequence/components/AgentCap.js
index b6efea9..067579d 100644
--- a/scripts/sequence/components/AgentCap.js
+++ b/scripts/sequence/components/AgentCap.js
@@ -155,11 +155,8 @@ define([
render(y, {x, label}, env, isBegin) {
const config = env.theme.agentCap.fade;
- const gradID = isBegin ? 'fadeIn' : 'fadeOut';
-
- env.addDef(gradID, () => {
+ const gradID = env.addDef(isBegin ? 'FadeIn' : 'FadeOut', () => {
const grad = svg.make('linearGradient', {
- 'id': gradID,
'x1': '0%',
'y1': isBegin ? '100%' : '0%',
'x2': '0%',
diff --git a/scripts/sequence/sequence_integration_spec.js b/scripts/sequence/sequence_integration_spec.js
index 87d5a17..25aea72 100644
--- a/scripts/sequence/sequence_integration_spec.js
+++ b/scripts/sequence/sequence_integration_spec.js
@@ -9,7 +9,7 @@ defineDescribe('Sequence Integration', [
Parser,
Generator,
Renderer,
- Theme,
+ BasicTheme,
SVGTextBlock
) => {
'use strict';
@@ -20,10 +20,14 @@ defineDescribe('Sequence Integration', [
let theme = null;
beforeEach(() => {
- theme = new Theme();
+ theme = new BasicTheme();
parser = new Parser();
generator = new Generator();
- renderer = new Renderer(theme, {SVGTextBlockClass: SVGTextBlock});
+ renderer = new Renderer({
+ themes: [new BasicTheme()],
+ namespace: '',
+ SVGTextBlockClass: SVGTextBlock,
+ });
document.body.appendChild(renderer.svg());
});
@@ -45,12 +49,12 @@ defineDescribe('Sequence Integration', [
expect(getSimplifiedContent(renderer)).toEqual(
''
);
});
@@ -63,12 +67,12 @@ defineDescribe('Sequence Integration', [
expect(getSimplifiedContent(renderer)).toEqual(
'