import { CtlBase } from './CtlBase'
import { CtlXtermDefault } from './CtlDefaultValue'
import { parseYvanPropChangeVJson } from './CtlUtils'
import { YvEvent, YvEventDispatch } from './YvanEvent'
import webix from 'webix'
import qs from "qs";
webix.protoUI(
{
name: 'xterm',
defaults: {},
$init: function (config: any) {
this._domid = webix.uid();
this.$view.innerHTML = `
`
this.$ready.push(this._ready)
_.extend(this.config, config)
if (config.on && typeof config.on.onInited === 'function') {
config.on.onInited.call(this)
}
},
_ready: function (this: any) {
this.isXtermLoad = false;
},
_set_inner_size: function () {
if (!this._term || !this.$width) return
this._updateScrollSize()
// this._editor.scrollTo(0, 0) //force repaint, mandatory for IE
},
destructor: function () {
if (this.$destructed) {
return;
}
this.$destructed = true;
if (this.config.on && typeof this.config.on.onDestruct === 'function') {
this.config.on.onDestruct.call(this)
}
},
_updateScrollSize: function () {
var box = this._term.element;
var height = (this.$height || 0) + 'px'
box.style.height = height
box.style.width = (this.$width || 0) + 'px'
if (this._fitAddon) {
this._fitAddon.fit();
this._updateXtermInfo()
}
},
_updateXtermInfo: function () {
const info = {
"xtermCols": this._term.cols,
"xtermRows": this._term.rows,
"xtermWp": this.$width,
"xtermHp": this.$height
}
this.wrapper.xtermInfo = info;
if (this.wrapper._connection) {
this.wrapper._resizeClientData(info);
}
},
$setSize: function (x: any, y: any) {
if (webix.ui.view.prototype.$setSize.call(this, x, y)) {
_.defer(() => {
this._set_inner_size()
if (this.isXtermLoad == false) {
this.isXtermLoad = true
//@ts-ignore
require(['xterm', 'xterm-addon-fit'], (xterm: any, addon: any) => {
const term = new xterm.Terminal(this.config.termConfig);
if (this.wrapper.allowInput) {
term.onData((data: any) => {
this.wrapper._sendClientData(data);
});
}
const fitAddon = new addon.FitAddon();
term.loadAddon(fitAddon);
term.open(this.$view.firstChild);
fitAddon.fit();
this._term = term;
this._fitAddon = fitAddon;
this._updateXtermInfo()
if (this.wrapper._shouldConnectUrl) {
this.wrapper.connectHost(this.wrapper._shouldConnectUrl);
delete this.wrapper._shouldConnectUrl
}
})
}
})
}
}
},
webix.ui.view
)
export class CtlXterm extends CtlBase {
static create(module: any, vjson: any): CtlXterm {
const that = new CtlXterm(vjson)
that._module = module
_.defaultsDeep(vjson, CtlXtermDefault)
const yvanProp = parseYvanPropChangeVJson(vjson, [
'value',
'allowInput',
'onOpen',
'onClose',
])
// 将 vjson 存至 _webixConfig
that._webixConfig = vjson
// 将 yvanProp 合并至当前 Ctl 对象, 期间会操作 _webixConfig
_.assign(that, yvanProp)
// 将 事件与 _webixConfig 一起存至 vjson 交给 webix 框架做渲染
_.merge(vjson, that._webixConfig, {
on: {
onInited(this: any) {
that.attachHandle(this, { ...vjson, ...yvanProp })
this.wrapper = that
},
onDestruct(this: any) {
that.connectionClose()
that.removeHandle()
}
}
})
return that
}
/**
* xterm 信息
* cols
* rows
* width
* height
*/
xtermInfo?: any
/**
* 是否允许从 xterm 接收指令,给 websocket
*/
allowInput?: boolean
/**
* socket打开时的事件
*/
onOpen?: YvEvent
/**
* socket关闭时的事件
*/
onClose?: YvEvent
/**
* 获取终端
*/
get term(): any {
return this._webix._term
}
/**
* 获取填充插件
*/
get fitAddon(): any {
return this._webix._fitAddon
}
clear() {
this.term.clear()
}
connectHost(host: string) {
if (!this.term) {
this._shouldConnectUrl = host
return;
}
if (!this._connection) {
let hostUrl = host;
if (hostUrl.indexOf("?") === -1) {
hostUrl = hostUrl + "?" + qs.stringify(this.xtermInfo);
}
else {
const params = hostUrl.slice(hostUrl.indexOf("?") + 1)
if (params.length > 0) {
hostUrl = hostUrl + "&" + qs.stringify(this.xtermInfo);
}
else {
hostUrl += qs.stringify(this.xtermInfo);
}
}
this._connection = new WebSocket(hostUrl)
this._connection.onopen = this._onSocketOpen.bind(this);
this._connection.onmessage = this._onSocketMessage.bind(this);
this._connection.onerror = this._onSocketError.bind(this);
this._connection.onclose = this._onSocketClose.bind(this);
}
else {
this.term.write('Error: WebSocket Not Supported\r\n');
}
}
sendMessage(msg: any) {
this._sendClientData(msg)
}
connectionClose() {
if (this._connection) {
console.log('WebSocket closed', this._connection)
this._connection.close();
}
}
/*********************** 私有变量 **********************/
private _connection?: WebSocket
private _shouldConnectUrl?: string
private _onSocketOpen() {
this.term.write('连接已建立,正在等待数据...\r\n');
if (this.onOpen) {
YvEventDispatch(this.onOpen, this, undefined);
}
this._sendInitData({ operate: 'connect' });
}
private _onSocketMessage(msg: any) {
const data = msg.data.toString();
this.term.write(data);
}
private _onSocketClose() {
if (this._webix) {
this.term.write("\r\n连接已关闭\r\n");
if (this.onClose) {
YvEventDispatch(this.onClose, this, undefined);
}
}
this._connection = undefined;
}
private _onSocketError() {
this.term.write("\r\n连接发生异常\r\n");
}
private _sendInitData(options: any) {
if (!this._connection) {
console.error('_connection 没有初始化')
return;
}
//连接参数
this._connection.send(JSON.stringify(options));
}
private _resizeClientData(data: any) {
if (!this._connection) {
console.error('_connection 没有初始化')
return;
}
//发送指令
this._connection.send(JSON.stringify({ "operate": "resize", ...data }))
}
private _sendClientData(data: any) {
if (!this._connection) {
console.error('_connection 没有初始化')
return;
}
//发送指令
this._connection.send(JSON.stringify({ "operate": "command", "command": data }))
}
}