2018-12-20 20:02:48 +08:00

377 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import UI from '../../ui/UI';
import SetScriptValueCommand from '../../command/SetScriptValueCommand';
/**
* 脚本编辑器
* @author mrdoob / http://mrdoob.com/
* @author tengge / https://github.com/tengge1
*/
function ScriptEditor(options) {
UI.Control.call(this, options);
this.app = options.app;
this.codemirror = null;
this.server = null;
this.delay = null; // 代码校验延迟函数
this.delayTime = 1000; // 代码校验间隔时间(毫秒)
this.uuid = null;
this.name = null;
this.mode = null;
this.source = null;
this.title = null;
this.errorLines = []; // 代码错误行数
this.widgets = [];
this.callback = null;
};
ScriptEditor.prototype = Object.create(UI.Control.prototype);
ScriptEditor.prototype.constructor = ScriptEditor;
ScriptEditor.prototype.render = function () {
var data = {
xtype: 'div',
parent: this.parent,
id: 'scriptEditor',
cls: 'script',
style: {
backgroundColor: '#272822',
display: 'none'
},
children: [{
xtype: 'div',
style: {
padding: '10px'
},
children: [{
xtype: 'text',
id: 'scriptTitle',
style: {
color: '#fff'
}
}, {
xtype: 'closebutton',
style: {
position: 'absolute',
top: '3px',
right: '1px',
cursor: 'pointer'
},
onClick: this.hide.bind(this)
}]
}]
};
var container = UI.create(data);
container.render();
};
/**
* 打开脚本文件
* @param {*} uuid 脚本uuid
* @param {*} name 名称
* @param {*} mode 类型 javascript、vertexShader、fragmentShader、json 默认javascript
* @param {*} source 源码 文件初始代码 默认:空
* @param {*} title 标题 文件标题 默认:未命名.${文件类型}
* @param {*} callback 回调函数
*/
ScriptEditor.prototype.open = function (uuid, name, mode, source, title, callback) {
if (this.init === undefined) {
this.init = true;
this.app.require(['codemirror', 'codemirror-addon', 'esprima', 'jsonlint', 'glslprep', 'acorn', 'ternjs']).then(() => {
this._initCodeMirror();
this._openScript(uuid, name, mode, source, title, callback);
});
} else {
this._openScript(uuid, name, mode, source, title, callback);
}
};
ScriptEditor.prototype._initCodeMirror = function () {
var container = UI.get('scriptEditor');
var codemirror = CodeMirror(container.dom, {
value: '',
lineNumbers: true,
matchBrackets: true,
indentWithTabs: true,
tabSize: 4,
indentUnit: 4,
hintOptions: {
completeSingle: false
}
});
codemirror.setOption('theme', 'monokai');
codemirror.on('change', this.onCodeMirrorChange.bind(this));
// 防止回退键删除物体
var wrapper = codemirror.getWrapperElement();
wrapper.addEventListener('keydown', event => {
event.stopPropagation();
});
// tern js 自动完成
var server = new CodeMirror.TernServer({
caseInsensitive: true,
plugins: { threejs: null }
});
// 快捷键
codemirror.setOption('extraKeys', {
'Ctrl-Space': cm => { server.complete(cm); },
'Ctrl-I': cm => { server.showType(cm); },
'Ctrl-O': cm => { server.showDocs(cm); },
'Alt-.': cm => { server.jumpToDef(cm); },
'Alt-,': cm => { server.jumpBack(cm); },
'Ctrl-Q': cm => { server.rename(cm); },
'Ctrl-.': cm => { server.selectName(cm); }
});
codemirror.on('cursorActivity', cm => {
if (this.mode !== 'javascript') {
return;
}
server.updateArgHints(cm);
});
codemirror.on('keypress', (cm, kb) => {
if (this.mode !== 'javascript') {
return;
}
var typed = String.fromCharCode(kb.which || kb.keyCode);
if (/[\w\.]/.exec(typed)) {
server.complete(cm);
}
});
this.codemirror = codemirror;
this.server = server;
};
ScriptEditor.prototype._openScript = function (uuid, name, mode, source, title, callback) {
var scriptTitle = UI.get('scriptTitle');
// 连续打开脚本时,自动保存上次打开的文件
if (this.uuid != null) {
this.save();
this.uuid = null;
this.name = null;
this.mode = null;
this.source = null;
this.title = null;
}
// 打开新文件
name = name || '未命名';
mode = mode || 'javascript';
source = source || '';
title = title || '未命名';
title = `${title}.${(mode === 'vertexShader' || mode === 'fragmentShader') ? '.glsl' : (mode === 'json' ? '.json' : '.js')}`;
this.uuid = uuid;
this.name = name;
this.mode = mode;
this.source = source;
this.title = title;
this.callback = callback;
this.show();
scriptTitle.setValue(title);
this.codemirror.setValue(source);
// 设置codemirror模式
if (mode === 'json') {
this.codemirror.setOption('mode', {
name: 'javascript',
json: true
});
} if (mode === 'vertexShader' || mode === 'fragmentShader') {
this.codemirror.setOption('mode', 'glsl');
} else {
this.codemirror.setOption('mode', mode);
}
this.codemirror.focus();
this.codemirror.setCursor({
line: 0,
ch: 0
});
};
/**
* 显示脚本编辑器
*/
ScriptEditor.prototype.show = function () {
var container = UI.get('scriptEditor');
container.dom.style.display = 'block';
};
/**
* 隐藏脚本编辑器
*/
ScriptEditor.prototype.hide = function () {
var container = UI.get('scriptEditor');
this.save();
container.dom.style.display = 'none';
this.uuid = null;
this.name = null;
this.mode = null;
this.source = null;
this.title = null;
};
/**
* 保存脚本
*/
ScriptEditor.prototype.save = function () {
var value = this.codemirror.getValue();
if (typeof (this.callback) === 'function') {
this.callback.call(this, value);
}
this.app.log(`${this.uuid}脚本保存成功!`);
};
/**
* 刷新脚本编辑器
* @param {*} title 标题
* @param {*} source 代码
* @param {*} cursorPosition 光标位置
* @param {*} scrollInfo 滚动信息
*/
ScriptEditor.prototype.refresh = function (title, source, cursorPosition, scrollInfo) {
var container = UI.get('scriptEditor');
var title = UI.get('scriptTitle');
// 复制codemirror的历史记录因为"codemirror.setValue(...)"函数会改变它的历史。
var history = this.codemirror.getHistory();
title.setValue(title);
this.codemirror.setValue(source);
if (cursorPosition !== undefined) {
this.codemirror.setCursor(cursorPosition);
this.codemirror.scrollTo(scrollInfo.left, scrollInfo.top);
}
this.codemirror.setHistory(history); // 设置历史到先前状态
};
/**
* 代码修改事件
*/
ScriptEditor.prototype.onCodeMirrorChange = function () {
if (this.codemirror.state.focused === false) {
return;
}
if (this.delay) {
clearTimeout(this.delay);
}
this.delay = setTimeout(() => {
var code = this.codemirror.getValue();
this.validate(code);
}, this.delayTime);
};
/**
* 校验编辑器中代码正确性
* @param {*} string
*/
ScriptEditor.prototype.validate = function (string) {
var codemirror = this.codemirror;
var mode = this.mode;
var errorLines = this.errorLines;
var widgets = this.widgets;
var errors = [];
return codemirror.operation(() => {
while (errorLines.length > 0) {
codemirror.removeLineClass(errorLines.shift(), 'background', 'errorLine');
}
while (widgets.length > 0) {
codemirror.removeLineWidget(widgets.shift());
}
switch (mode) {
case 'javascript':
try {
var syntax = esprima.parse(string, { tolerant: true });
errors = syntax.errors;
} catch (error) {
errors.push({
lineNumber: error.lineNumber - 1,
message: error.message
});
}
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
error.message = error.message.replace(/Line [0-9]+: /, '');
}
break;
case 'json':
jsonlint.parseError = (message, info) => {
message = message.split('\n')[3];
errors.push({
lineNumber: info.loc.first_line - 1,
message: message
});
};
try {
jsonlint.parse(string);
} catch (error) {
// ignore failed error recovery
}
break;
case 'vertexShader':
case 'fragmentShader':
try {
var shaderType = mode === 'vertexShader' ? glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
glslprep.parseGlsl(string, shaderType);
} catch (error) {
if (error instanceof glslprep.SyntaxError) {
errors.push({
lineNumber: error.line,
message: "Syntax Error: " + error.message
});
} else {
console.error(error.stack || error);
}
}
}
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
var message = document.createElement('div');
message.className = 'esprima-error';
message.textContent = error.message;
var lineNumber = Math.max(error.lineNumber, 0);
errorLines.push(lineNumber);
codemirror.addLineClass(lineNumber, 'background', 'errorLine');
var widget = codemirror.addLineWidget(lineNumber, message);
widgets.push(widget);
}
return errors.length === 0;
});
};
export default ScriptEditor;