Use progressive enhancement when loading external modules
This commit is contained in:
parent
c1d604aacc
commit
4b33114824
|
@ -22,7 +22,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"almond": "^0.3.3",
|
"almond": "^0.3.3",
|
||||||
"http-server": "^0.10.0",
|
"http-server": "^0.10.0",
|
||||||
"requirejs": "^2.3.5",
|
"requirejs": "2.3.5",
|
||||||
"uglify-es": "^3.1.10"
|
"uglify-es": "^3.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
define([
|
define(['require'], (require) => {
|
||||||
'split',
|
|
||||||
'cm/lib/codemirror',
|
|
||||||
'cm/addon/hint/show-hint',
|
|
||||||
'cm/addon/edit/trailingspace',
|
|
||||||
'cm/addon/comment/comment',
|
|
||||||
], (Split, CodeMirror) => {
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const DELAY_AGENTCHANGE = 500;
|
const DELAY_AGENTCHANGE = 500;
|
||||||
|
@ -25,6 +19,13 @@ define([
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addNewline(value) {
|
||||||
|
if(value.length > 0 && value.charAt(value.length - 1) !== '\n') {
|
||||||
|
return value + '\n';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function on(element, events, fn) {
|
function on(element, events, fn) {
|
||||||
events.forEach((event) => element.addEventListener(event, fn));
|
events.forEach((event) => element.addEventListener(event, fn));
|
||||||
}
|
}
|
||||||
|
@ -43,44 +44,48 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSplit(nodes, options) {
|
function makeSplit(nodes, options) {
|
||||||
// Patches for:
|
// Load on demand for progressive enhancement
|
||||||
// https://github.com/nathancahill/Split.js/issues/97
|
// (failure to load external module will not block functionality)
|
||||||
// https://github.com/nathancahill/Split.js/issues/111
|
require(['split'], (Split) => {
|
||||||
const parent = nodes[0].parentNode;
|
// Patches for:
|
||||||
const oldAEL = parent.addEventListener;
|
// https://github.com/nathancahill/Split.js/issues/97
|
||||||
const oldREL = parent.removeEventListener;
|
// https://github.com/nathancahill/Split.js/issues/111
|
||||||
parent.addEventListener = (event, callback) => {
|
const parent = nodes[0].parentNode;
|
||||||
if(event === 'mousemove' || event === 'touchmove') {
|
const oldAEL = parent.addEventListener;
|
||||||
window.addEventListener(event, callback, {passive: true});
|
const oldREL = parent.removeEventListener;
|
||||||
} else {
|
parent.addEventListener = (event, callback) => {
|
||||||
oldAEL.call(parent, event, callback);
|
if(event === 'mousemove' || event === 'touchmove') {
|
||||||
}
|
window.addEventListener(event, callback, {passive: true});
|
||||||
};
|
} else {
|
||||||
parent.removeEventListener = (event, callback) => {
|
oldAEL.call(parent, event, callback);
|
||||||
if(event === 'mousemove' || event === 'touchmove') {
|
}
|
||||||
window.removeEventListener(event, callback);
|
};
|
||||||
} else {
|
parent.removeEventListener = (event, callback) => {
|
||||||
oldREL.call(parent, event, callback);
|
if(event === 'mousemove' || event === 'touchmove') {
|
||||||
}
|
window.removeEventListener(event, callback);
|
||||||
};
|
} else {
|
||||||
|
oldREL.call(parent, event, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let oldCursor = null;
|
let oldCursor = null;
|
||||||
const resolvedOptions = Object.assign({
|
const resolvedOptions = Object.assign({
|
||||||
direction: 'vertical',
|
direction: 'vertical',
|
||||||
cursor: (options.direction === 'vertical') ?
|
cursor: (options.direction === 'vertical') ?
|
||||||
'row-resize' : 'col-resize',
|
'row-resize' : 'col-resize',
|
||||||
gutterSize: 1,
|
gutterSize: 1,
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
oldCursor = document.body.style.cursor;
|
oldCursor = document.body.style.cursor;
|
||||||
document.body.style.cursor = resolvedOptions.cursor;
|
document.body.style.cursor = resolvedOptions.cursor;
|
||||||
},
|
},
|
||||||
onDragEnd: () => {
|
onDragEnd: () => {
|
||||||
document.body.style.cursor = oldCursor;
|
document.body.style.cursor = oldCursor;
|
||||||
oldCursor = null;
|
oldCursor = null;
|
||||||
},
|
},
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
return new Split(nodes, resolvedOptions);
|
return new Split(nodes, resolvedOptions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return class Interface {
|
return class Interface {
|
||||||
|
@ -98,8 +103,6 @@ define([
|
||||||
this.links = links;
|
this.links = links;
|
||||||
this.minScale = 1.5;
|
this.minScale = 1.5;
|
||||||
|
|
||||||
this.diagram.registerCodeMirrorMode(CodeMirror);
|
|
||||||
|
|
||||||
this.debounced = null;
|
this.debounced = null;
|
||||||
this.latestSeq = null;
|
this.latestSeq = null;
|
||||||
this.renderedSeq = null;
|
this.renderedSeq = null;
|
||||||
|
@ -111,6 +114,8 @@ define([
|
||||||
this._downloadSVGClick = this._downloadSVGClick.bind(this);
|
this._downloadSVGClick = this._downloadSVGClick.bind(this);
|
||||||
this._downloadPNGClick = this._downloadPNGClick.bind(this);
|
this._downloadPNGClick = this._downloadPNGClick.bind(this);
|
||||||
this._downloadPNGFocus = this._downloadPNGFocus.bind(this);
|
this._downloadPNGFocus = this._downloadPNGFocus.bind(this);
|
||||||
|
|
||||||
|
this._enhanceEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildOptionsLinks() {
|
buildOptionsLinks() {
|
||||||
|
@ -154,58 +159,21 @@ define([
|
||||||
|
|
||||||
buildEditor(container) {
|
buildEditor(container) {
|
||||||
const value = this.loadCode() || this.defaultCode;
|
const value = this.loadCode() || this.defaultCode;
|
||||||
const code = new CodeMirror(container, {
|
const code = makeNode('textarea', {'class': 'editor-simple'});
|
||||||
value,
|
code.value = value;
|
||||||
mode: 'sequence',
|
container.appendChild(code);
|
||||||
globals: {
|
|
||||||
themes: this.diagram.getThemeNames(),
|
|
||||||
},
|
|
||||||
lineNumbers: true,
|
|
||||||
showTrailingSpace: true,
|
|
||||||
extraKeys: {
|
|
||||||
'Tab': (cm) => cm.execCommand('indentMore'),
|
|
||||||
'Shift-Tab': (cm) => cm.execCommand('indentLess'),
|
|
||||||
'Cmd-/': (cm) => cm.toggleComment({padding: ''}),
|
|
||||||
'Ctrl-/': (cm) => cm.toggleComment({padding: ''}),
|
|
||||||
'Ctrl-Space': 'autocomplete',
|
|
||||||
'Ctrl-Enter': 'autocomplete',
|
|
||||||
'Cmd-Enter': 'autocomplete',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let lastKey = 0;
|
|
||||||
code.on('keydown', (cm, event) => {
|
|
||||||
lastKey = event.keyCode;
|
|
||||||
});
|
|
||||||
code.on('change', (cm, change) => {
|
|
||||||
if(change.origin === '+input') {
|
|
||||||
if(lastKey === 13) {
|
|
||||||
lastKey = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if(change.origin !== 'complete') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CodeMirror.commands.autocomplete(cm, null, {
|
|
||||||
completeSingle: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
this.code.on('change', () => this.update(false));
|
this.code.addEventListener('input', () => this.update(false));
|
||||||
this.code.on('cursorActivity', () => {
|
|
||||||
const from = this.code.getCursor('from').line;
|
|
||||||
const to = this.code.getCursor('to').line;
|
|
||||||
this.diagram.setHighlight(Math.min(from, to));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.diagram.addEventListener('mouseover', (element) => {
|
this.diagram.addEventListener('mouseover', (element) => {
|
||||||
if(this.marker) {
|
if(this.marker) {
|
||||||
this.marker.clear();
|
this.marker.clear();
|
||||||
}
|
}
|
||||||
if(element.ln !== undefined) {
|
if(element.ln !== undefined && this.code.markText) {
|
||||||
this.marker = this.code.markText(
|
this.marker = this.code.markText(
|
||||||
{line: element.ln, ch: 0},
|
{line: element.ln, ch: 0},
|
||||||
{line: element.ln + 1, ch: 0},
|
{line: element.ln + 1, ch: 0},
|
||||||
|
@ -231,7 +199,7 @@ define([
|
||||||
this.marker.clear();
|
this.marker.clear();
|
||||||
this.marker = null;
|
this.marker = null;
|
||||||
}
|
}
|
||||||
if(element.ln !== undefined) {
|
if(element.ln !== undefined && this.code.setSelection) {
|
||||||
this.code.setSelection(
|
this.code.setSelection(
|
||||||
{line: element.ln, ch: 0},
|
{line: element.ln, ch: 0},
|
||||||
{line: element.ln + 1, ch: 0},
|
{line: element.ln + 1, ch: 0},
|
||||||
|
@ -262,6 +230,7 @@ define([
|
||||||
container: holdInner,
|
container: holdInner,
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
window.console.log('Failed to render preview', e);
|
||||||
hold.setAttribute('class', 'library-item broken');
|
hold.setAttribute('class', 'library-item broken');
|
||||||
holdInner.appendChild(makeText(lib.code));
|
holdInner.appendChild(makeText(lib.code));
|
||||||
}
|
}
|
||||||
|
@ -348,16 +317,32 @@ define([
|
||||||
}
|
}
|
||||||
|
|
||||||
addCodeBlock(block) {
|
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;
|
const lines = block.split('\n').length;
|
||||||
this.code.replaceRange(
|
|
||||||
block + '\n',
|
if(this.code.getCursor) {
|
||||||
pos,
|
const cur = this.code.getCursor('head');
|
||||||
null,
|
const pos = {line: cur.line + ((cur.ch > 0) ? 1 : 0), ch: 0};
|
||||||
'library'
|
this.code.replaceRange(
|
||||||
);
|
addNewline(block),
|
||||||
this.code.setCursor({line: pos.line + lines, ch: 0});
|
pos,
|
||||||
|
null,
|
||||||
|
'library'
|
||||||
|
);
|
||||||
|
this.code.setCursor({line: pos.line + lines, ch: 0});
|
||||||
|
} else {
|
||||||
|
const value = this.value();
|
||||||
|
const cur = this.code.selectionStart;
|
||||||
|
const pos = ('\n' + value + '\n').indexOf('\n', cur);
|
||||||
|
const replaced = (
|
||||||
|
addNewline(value.substr(0, pos)) +
|
||||||
|
addNewline(block)
|
||||||
|
);
|
||||||
|
this.code.value = replaced + value.substr(pos);
|
||||||
|
this.code.selectionStart = replaced.length;
|
||||||
|
this.code.selectionEnd = replaced.length;
|
||||||
|
this.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
this.code.focus();
|
this.code.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,8 +397,16 @@ define([
|
||||||
this.errorMsg.setAttribute('class', 'msg-error');
|
this.errorMsg.setAttribute('class', 'msg-error');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value() {
|
||||||
|
if(this.code.getDoc) {
|
||||||
|
return this.code.getDoc().getValue();
|
||||||
|
} else {
|
||||||
|
return this.code.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update(immediate = true) {
|
update(immediate = true) {
|
||||||
const src = this.code.getDoc().getValue();
|
const src = this.value();
|
||||||
this.saveCode(src);
|
this.saveCode(src);
|
||||||
let sequence = null;
|
let sequence = null;
|
||||||
try {
|
try {
|
||||||
|
@ -484,5 +477,67 @@ define([
|
||||||
const url = this.diagram.getSVGSynchronous();
|
const url = this.diagram.getSVGSynchronous();
|
||||||
this.downloadSVG.setAttribute('href', url);
|
this.downloadSVG.setAttribute('href', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_enhanceEditor() {
|
||||||
|
// Load on demand for progressive enhancement
|
||||||
|
// (failure to load external module will not block functionality)
|
||||||
|
require([
|
||||||
|
'cm/lib/codemirror',
|
||||||
|
'cm/addon/hint/show-hint',
|
||||||
|
'cm/addon/edit/trailingspace',
|
||||||
|
'cm/addon/comment/comment',
|
||||||
|
], (CodeMirror) => {
|
||||||
|
this.diagram.registerCodeMirrorMode(CodeMirror);
|
||||||
|
|
||||||
|
const code = new CodeMirror(this.code.parentNode, {
|
||||||
|
value: this.code.value,
|
||||||
|
mode: 'sequence',
|
||||||
|
globals: {
|
||||||
|
themes: this.diagram.getThemeNames(),
|
||||||
|
},
|
||||||
|
lineNumbers: true,
|
||||||
|
showTrailingSpace: true,
|
||||||
|
extraKeys: {
|
||||||
|
'Tab': (cm) => cm.execCommand('indentMore'),
|
||||||
|
'Shift-Tab': (cm) => cm.execCommand('indentLess'),
|
||||||
|
'Cmd-/': (cm) => cm.toggleComment({padding: ''}),
|
||||||
|
'Ctrl-/': (cm) => cm.toggleComment({padding: ''}),
|
||||||
|
'Ctrl-Space': 'autocomplete',
|
||||||
|
'Ctrl-Enter': 'autocomplete',
|
||||||
|
'Cmd-Enter': 'autocomplete',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.code.parentNode.removeChild(this.code);
|
||||||
|
|
||||||
|
let lastKey = 0;
|
||||||
|
code.on('keydown', (cm, event) => {
|
||||||
|
lastKey = event.keyCode;
|
||||||
|
});
|
||||||
|
|
||||||
|
code.on('change', (cm, change) => {
|
||||||
|
this.update(false);
|
||||||
|
|
||||||
|
if(change.origin === '+input') {
|
||||||
|
if(lastKey === 13) {
|
||||||
|
lastKey = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if(change.origin !== 'complete') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CodeMirror.commands.autocomplete(cm, null, {
|
||||||
|
completeSingle: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
code.on('cursorActivity', () => {
|
||||||
|
const from = code.getCursor('from').line;
|
||||||
|
const to = code.getCursor('to').line;
|
||||||
|
this.diagram.setHighlight(Math.min(from, to));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.code = code;
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ defineDescribe('Interface', ['./Interface'], (Interface) => {
|
||||||
// Thanks, https://stackoverflow.com/a/23522755/1180785
|
// Thanks, https://stackoverflow.com/a/23522755/1180785
|
||||||
const safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
const safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
const defaultCode = 'my default code';
|
||||||
let sequenceDiagram = null;
|
let sequenceDiagram = null;
|
||||||
let container = null;
|
let container = null;
|
||||||
let ui = null;
|
let ui = null;
|
||||||
|
@ -31,7 +32,7 @@ defineDescribe('Interface', ['./Interface'], (Interface) => {
|
||||||
|
|
||||||
ui = new Interface({
|
ui = new Interface({
|
||||||
sequenceDiagram,
|
sequenceDiagram,
|
||||||
defaultCode: 'my default code',
|
defaultCode,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,10 +42,17 @@ defineDescribe('Interface', ['./Interface'], (Interface) => {
|
||||||
expect(container.appendChild).toHaveBeenCalled();
|
expect(container.appendChild).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a code mirror instance with the given code', () => {
|
it('creates a code mirror instance with the given code', (done) => {
|
||||||
ui.build(container);
|
ui.build(container);
|
||||||
const constructorArgs = ui.code.constructor;
|
const check = setInterval(() => {
|
||||||
expect(constructorArgs.options.value).toEqual('my default code');
|
const constructorArgs = ui.code.constructor;
|
||||||
|
if(!constructorArgs.options) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearInterval(check);
|
||||||
|
expect(constructorArgs.options.value).toEqual(defaultCode);
|
||||||
|
done();
|
||||||
|
}, 50);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -46,19 +46,23 @@ html, body {
|
||||||
|
|
||||||
.pane-side {
|
.pane-side {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
width: 30%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane-code {
|
.pane-code {
|
||||||
|
height: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane-library {
|
.pane-library {
|
||||||
background: #EEEEEE;
|
background: #EEEEEE;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
height: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pane-view {
|
.pane-view {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
width: 70%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +98,19 @@ html, body {
|
||||||
background: #EEEEEE;
|
background: #EEEEEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pane-code .editor-simple {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background: #EEEEEE;
|
||||||
|
color: #222222;
|
||||||
|
font: 1em monospace;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 8px 16px 8px;
|
||||||
|
resize: none;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-line.error {
|
.CodeMirror-line.error {
|
||||||
background: rgba(255, 0, 0, 0.2);
|
background: rgba(255, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue