components/helper/sandbox.ts (177 lines of code) (raw):

import Vue from 'vue' declare const echarts: any export function createSandbox() { let appEnv: any = {} const animatorIdMap = {} let animatorId = 1 // Using echarts timer so it can be paused. function chartSetTimeout(cb: () => void, time: number) { const animator = chartInstance .getZr() .animation.animate({ val: 0 } as any, { loop: false }) .when(time, { val: 1 }) .during(() => { // Please don't fall sleep. // TODO Can be configurable. chartInstance.getZr().wakeUp() }) .done(() => { // NOTE: Must delay the callback. Or zrender flush will invoke the chartSetTimeout callback again. // TODO: This is something needs to be fixed in zrender. Vue.nextTick(cb) }) .start() return animator } function chartSetInterval(cb: () => void, time: number) { const animator = chartInstance .getZr() .animation.animate({ val: 0 } as any, { loop: true }) .when(time, { val: 1 }) .during((target, percent) => { // Please don't fall sleep. // TODO Can be configurable. chartInstance.getZr().wakeUp() if (percent === 1) { // NOTE: Must delay the callback. Or zrender flush will invoke the chartSetTimeout callback again. // TODO: This is something needs to be fixed in zrender. Vue.nextTick(cb) } }) .start() return animator } function setTimeout(func, delay) { const animator = chartSetTimeout(func, delay) animatorIdMap[animatorId] = animator return animatorId++ } function setInterval(func, gap) { const animator = chartSetInterval(func, gap) animatorIdMap[animatorId] = animator return animatorId++ } function clearTimer(id) { const animator = animatorIdMap[id] if (animator) { chartInstance.getZr().animation.removeAnimator(animator) delete animatorIdMap[id] } } function clearTimeout(id) { clearTimer(id) } function clearInterval(id) { clearTimer(id) } function _clearTimeTickers() { for (let key in animatorIdMap) { if (animatorIdMap.hasOwnProperty(key)) { clearTimer(key) } } } const _events: string[] = [] function _wrapOnMethods(chart) { const oldOn = chart.on const oldSetOption = chart.setOption chart.on = function(eventName) { const res = oldOn.apply(chart, arguments) _events.push(eventName) return res } chart.setOption = function() { const res = oldSetOption.apply(this, arguments) return res } } function _clearChartEvents(chart) { _events.forEach(function(eventName) { if (chart) { chart.off(eventName) } }) _events.length = 0 } let chartInstance return { resize() { if (chartInstance) { chartInstance.resize() } }, dispose() { if (chartInstance) { chartInstance.dispose() chartInstance = null } }, getDataURL() { return chartInstance.getDataURL({ pixelRatio: 2, excludeComponents: ['toolbox'] }) }, getOption() { return chartInstance.getOption() }, getInstance() { return chartInstance }, pause() { if (chartInstance) { chartInstance.getZr().animation.pause() } }, resume() { if (chartInstance) { chartInstance.getZr().animation.resume() } }, run( el: HTMLElement, code: string, opts?: { darkMode?: boolean renderer?: 'svg' | 'canvas' useDirtyRect?: boolean } ) { opts = opts || {} if (!chartInstance) { chartInstance = echarts.init(el, opts.darkMode ? 'dark' : '', { renderer: opts.renderer, useDirtyRect: opts.useDirtyRect }) _wrapOnMethods(chartInstance) } // if (this.hasEditorError()) { // log(this.$t('editor.errorInEditor'), 'error'); // return; // } // TODO Scope the variables in component. _clearTimeTickers() _clearChartEvents(chartInstance) // Reset appEnv.config = null // run the code const compiledCode = code const func = new Function( 'myChart', 'app', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'var option;\n' + compiledCode + '\nreturn option;' ) const option = func( chartInstance, appEnv, setTimeout, setInterval, clearTimer, clearInterval ) let updateTime = 0 if (option && typeof option === 'object') { const startTime = +new Date() chartInstance.setOption(option, true) const endTime = +new Date() updateTime = endTime - startTime } } } }