blob: 88a056bff052522be8dfec6ebfc639b5444e710a [file] [log] [blame]
<template>
<div id="main-container">
<div
id="editor-left-container"
:style="{ width: leftContainerSize + '%' }"
v-if="!shared.isMobile"
>
<el-tabs v-model="currentTab" type="border-card">
<el-tab-pane name="code-editor">
<span slot="label">{{ $t('editor.tabEditor') }}</span>
<el-container>
<el-header id="editor-control-panel">
<div class="languages">
<el-tooltip
:content="$t('editor.tooltip.jsMode')"
placement="bottom"
>
<a
:class="{ js: true, active: !shared.typeCheck }"
@click="changeLang('js')"
>JS</a
>
</el-tooltip>
<el-tooltip
:content="$t(`editor.tooltip.${hasTs ? 'tsMode' : 'noTs'}`)"
placement="bottom"
>
<a
@click="hasTs && changeLang('ts')"
:class="{
ts: true,
active: shared.typeCheck,
disabled: !hasTs
}"
>TS</a
>
</el-tooltip>
</div>
<div class="editor-controls">
<a
class="btn btn-sm codepen"
@click="toExternalEditor('CodePen')"
:title="$t('editor.openWithCodePen')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="none"
>
<path
d="M21.838 8.445c0-.001-.001-.001 0 0l-.003-.004l-.001-.001v-.001a.809.809 0 0 0-.235-.228l-9.164-6.08a.834.834 0 0 0-.898 0L2.371 8.214A.786.786 0 0 0 2 8.897v6.16a.789.789 0 0 0 .131.448v.001l.002.002l.01.015v.002h.001l.001.001l.001.001c.063.088.14.16.226.215l9.165 6.082a.787.787 0 0 0 .448.139a.784.784 0 0 0 .45-.139l9.165-6.082a.794.794 0 0 0 .371-.685v-6.16a.793.793 0 0 0-.133-.452zm-9.057-4.172l6.953 4.613l-3.183 2.112l-3.771-2.536V4.273zm-1.592 0v4.189l-3.771 2.536l-3.181-2.111l6.952-4.614zm-7.595 6.098l2.395 1.59l-2.395 1.611v-3.201zm7.595 9.311l-6.96-4.617l3.195-2.15l3.765 2.498v4.269zm.795-5.653l-3.128-2.078l3.128-2.105l3.131 2.105l-3.131 2.078zm.797 5.653v-4.27l3.766-2.498l3.193 2.15l-6.959 4.618zm7.597-6.11l-2.396-1.611l2.396-1.59v3.201z"
fill="currentColor"
></path>
</svg>
</a>
<a
class="btn btn-sm codesandbox"
@click="toExternalEditor('CodeSandBox')"
:title="$t('editor.openWithCodeSandBox')"
>
<svg
viewBox="0 0 512 512"
fill="none"
stroke="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M69 153.99L256 263.99M256 263.99L443 153.99M256 263.99V463.99M448 341.37V170.61C447.993 165.021 446.523 159.531 443.735 154.687C440.947 149.843 436.939 145.814 432.11 143L280.11 54.54C272.787 50.2765 264.464 48.0303 255.99 48.0303C247.516 48.0303 239.193 50.2765 231.87 54.54L79.89 143C75.0609 145.814 71.053 149.843 68.2652 154.687C65.4773 159.531 64.0068 165.021 64 170.61V341.37C64.0033 346.962 65.4722 352.456 68.2602 357.304C71.0482 362.152 75.058 366.185 79.89 369L231.89 457.46C239.215 461.718 247.537 463.96 256.01 463.96C264.483 463.96 272.805 461.718 280.13 457.46L432.13 369C436.958 366.182 440.964 362.148 443.748 357.301C446.533 352.453 447.999 346.96 448 341.37Z"
stroke="currentColor"
stroke-width="38"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</svg>
</a>
<a
class="btn btn-sm format"
@click="format"
:title="$t('editor.format')"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
/>
</svg>
</a>
<a class="btn btn-default btn-sm run" @click="disposeAndRun">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{{ $t('editor.run') }}</span>
</a>
</div>
</el-header>
<el-main>
<CodeMonaco
v-if="shared.typeCheck"
id="code-panel"
:initialCode="initialCode"
></CodeMonaco>
<CodeAce
v-else
id="code-panel"
:initialCode="initialCode"
></CodeAce>
</el-main>
</el-container>
</el-tab-pane>
<el-tab-pane
:label="$t('editor.tabFullCodePreview')"
name="full-code"
:lazy="true"
>
<el-container style="width: 100%; height: 100%">
<el-header id="full-code-generate-config">
<span class="full-code-generate-config-label">
<!-- <i class="el-icon-setting"></i> 配置 -->
</span>
<el-switch
v-model="fullCodeConfig.minimal"
:active-text="$t('editor.minimalBundle')"
:inactive-text="''"
>
</el-switch>
<el-switch
v-if="!shared.typeCheck"
v-model="fullCodeConfig.esm"
active-text="ES Modules"
:inactive-text="''"
>
</el-switch>
</el-header>
<el-main>
<FullCodePreview :code="fullCode"></FullCodePreview>
</el-main>
</el-container>
</el-tab-pane>
<el-tab-pane :label="$t('editor.tabOptionPreview')" name="full-option">
<div id="option-outline" ref="optionOutline"></div>
</el-tab-pane>
</el-tabs>
</div>
<div
class="handler"
id="h-handler"
@mousedown="onSplitterDragStart"
:style="{ left: leftContainerSize + '%' }"
v-if="!shared.isMobile"
></div>
<Preview
:inEditor="true"
@ready="onPreviewReady"
class="right-container"
ref="preview"
:style="{
width: 100 - leftContainerSize + '%',
left: leftContainerSize + '%'
}"
></Preview>
</div>
</template>
<script>
import CodeAce from './CodeAce.vue';
import CodeMonaco from './CodeMonaco.vue';
import FullCodePreview from './FullCodePreview.vue';
import Preview from './Preview.vue';
import {
store,
loadExampleCode,
parseSourceCode,
getExampleConfig
} from '../common/store';
import { collectDeps, buildExampleCode } from '../../common/buildCode';
import { gotoURL } from '../common/route';
import { mount } from '@lang/object-visualizer';
import './object-visualizer.css';
import { URL_PARAMS } from '../common/config';
import { formatCode } from '../common/helper';
import openWithCodePen from './sandbox/openwith/codepen';
import openWithCodeSandBox from './sandbox/openwith/codesandbox';
export default {
components: {
CodeAce,
CodeMonaco,
FullCodePreview,
Preview
},
data() {
return {
mousedown: false,
leftContainerSize: 40,
mobileMode: false,
shared: store,
initialCode: '',
currentTab: 'code-editor',
fullCode: '',
exampleConfig: getExampleConfig(),
fullCodeConfig: {
mimimal: false,
esm: true,
node: false // If is in node
}
};
},
computed: {
hasTs() {
return this.exampleConfig && this.exampleConfig.ts;
}
},
mounted() {
if (store.isMobile) {
this.leftContainerSize = 0;
loadExampleCode().then((code) => {
// No editor available. Set to runCode directly.
store.runCode = parseSourceCode(code);
});
} else {
loadExampleCode().then((code) => {
// Only set the code in editor. editor will sync to the store.
store.initialCode = this.initialCode = parseSourceCode(code);
});
window.addEventListener('mousemove', (e) => {
if (this.mousedown) {
let percentage = e.clientX / window.innerWidth;
percentage = Math.min(0.9, Math.max(0.1, percentage));
this.leftContainerSize = percentage * 100;
}
});
window.addEventListener('mouseup', (e) => {
this.mousedown = false;
});
}
},
methods: {
toExternalEditor(vendor) {
const previewRef = this.$refs.preview;
if (!previewRef) {
return;
}
const assets = previewRef.getAssets();
const vendors = {
CodePen: openWithCodePen,
CodeSandBox: openWithCodeSandBox
};
vendors[vendor](
this.exampleConfig && this.exampleConfig.title,
assets.scripts,
assets.css
);
},
onSplitterDragStart() {
this.mousedown = true;
},
disposeAndRun() {
this.$refs.preview.refreshAll();
},
updateFullCode() {
const option = this.$refs.preview.getOption();
if (!option) {
return;
}
const deps = collectDeps(option);
deps.push(store.renderer === 'svg' ? 'SVGRenderer' : 'CanvasRenderer');
this.fullCode = buildExampleCode(store.sourceCode, deps, {
minimal: this.fullCodeConfig.minimal,
ts: store.typeCheck,
esm: this.fullCodeConfig.esm,
// legacy: true,
theme: store.darkMode ? 'dark' : '',
ROOT_PATH: store.cdnRoot
});
// Format
formatCode(this.fullCode).then((code) => {
this.fullCode = code;
});
},
updateOptionOutline() {
const option = Object.freeze(this.$refs.preview.getOption());
if (!option) {
return;
}
const tipTitle = this.$t('editor.tooltip.gotoDoc');
const lang = this.$i18n.locale;
mount(option, this.$refs.optionOutline, {
getKeys(object) {
return Object.keys(object).filter((key) => {
if (Array.isArray(object[key]) && !object[key].length) {
return false;
}
return true;
});
},
renderName(name, path) {
let obj = option;
let hash = [];
let isTopLevel = true;
for (let i = 0; i < path.length; i++) {
let key = path[i];
obj = obj[key];
if (obj == null) {
hash.push(key);
break;
}
if (Array.isArray(obj) && isTopLevel) {
// Get type of component / series.
const item = obj[path[i + 1]];
const type = item && item.type;
if (type) {
key += '-' + type;
i++;
obj = item;
}
} else if (!isNaN(key)) {
// Ignore data[0]
continue;
}
hash.push(key);
}
const isObjOrArray = typeof obj === 'object' && obj != null;
const link = `https://echarts.apache.org/${lang}/option.html#${hash.join(
'.'
)}`;
return !isObjOrArray
? `<a href="${link}" target="_blank" title="${tipTitle}">${name}</a>`
: `${name}<a href="${link}" target="_blank" title="${tipTitle}"><i class="el-icon-document"></i></a>`;
},
expandOnCreatedAndUpdated(path) {
return (
path.length === 0 || (path[0] === 'series' && path.length <= 1)
);
}
});
},
updateTabContent(tab) {
if (tab === 'full-code') {
this.updateFullCode();
} else if (tab === 'full-option') {
this.updateOptionOutline();
}
},
changeLang(lang) {
if ((URL_PARAMS.lang || 'js').toLowerCase() !== lang) {
if (!this.initialCode || store.sourceCode === this.initialCode) {
gotoURL(
Object.assign({}, URL_PARAMS, {
lang
})
);
} else {
this.$confirm(this.$t('editor.codeChangedConfirm'), '', {
confirmButtonText: this.$t('editor.confirmButtonText'),
cancelButtonText: this.$t('editor.cancelButtonText'),
type: 'warning'
})
.then(() => {
gotoURL(
Object.assign({}, URL_PARAMS, {
lang
})
);
})
.catch(() => {});
}
}
},
format() {
formatCode(store.sourceCode).then((code) => {
store.initialCode = this.initialCode = code;
});
},
onPreviewReady() {
this.updateTabContent(this.currentTab);
}
},
watch: {
currentTab(tab) {
this.updateTabContent(tab);
},
'shared.runHash'() {
this.updateTabContent(this.currentTab);
},
fullCodeConfig: {
deep: true,
handler() {
this.updateFullCode();
}
}
}
};
</script>
<style lang="scss">
@import '../style/color.scss';
$control-panel-height: 30px;
$pd-basic: 10px;
$handler-width: 5px;
#v4-link {
display: none !important;
}
#main-container {
.handler {
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: $handler-width;
cursor: col-resize;
z-index: 100;
background-color: transparent;
border-left: 1px solid #ececec;
// border-right: 1px solid $clr-border;
}
}
#editor-left-container {
position: absolute;
left: 0;
bottom: 0;
top: 0;
width: 50%;
.el-tab-pane {
height: 100%;
.el-container {
width: 100%;
height: 100%;
}
.el-header {
height: $control-panel-height !important;
position: relative;
z-index: 10;
padding: 0;
}
.el-main {
padding: 0;
position: relative;
::-webkit-scrollbar {
height: 8px;
width: 8px;
transition: all 0.3s ease-in-out;
border-radius: 2px;
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-thumb {
width: 8px;
min-height: 15px;
background: rgba(50, 50, 50, 0.6) !important;
transition: all 0.3s ease-in-out;
border-radius: 2px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.5) !important;
}
}
}
.el-tabs {
box-shadow: none;
}
.el-tabs--border-card > .el-tabs__header {
border-bottom: none;
}
.el-tabs__content {
position: absolute;
top: 34px;
left: 0;
right: 0;
bottom: 0;
padding: 0;
}
.el-tabs__item {
height: 34px;
line-height: 34px;
}
}
#editor-control-panel,
#full-code-generate-config {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
#option-outline {
// height: 100%;
// Fix safari
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
font-size: 13px;
font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas',
monospace;
.el-icon-document {
margin-left: 5px;
font-size: 1rem;
&:hover {
text-decoration: underline;
}
}
}
#full-code-generate-config {
.full-code-generate-config-label {
height: $control-panel-height;
line-height: $control-panel-height;
vertical-align: middle;
margin: 0 0 0 20px;
}
.el-switch {
margin-right: 10px;
}
.el-switch__label {
margin-left: 8px;
margin-top: -2px;
}
.el-switch__label * {
font-size: 12px;
}
}
#editor-control-panel {
.setting-panel {
display: inline-block;
.btn-group + .btn-group {
margin-left: $pd-basic;
}
}
.languages {
display: inline-block;
padding: 2px 10px;
font-weight: bold;
a {
display: inline-block;
padding: 3px 10px;
margin-left: 5px;
vertical-align: middle;
cursor: pointer;
&.ts {
color: #3178c6;
}
&.ts.disabled {
color: #ddd;
cursor: default;
&:hover {
text-decoration: none;
}
}
&.js {
color: #000;
}
&.active {
font-size: 12px;
border-radius: 3px;
&.js {
background: #f7df1e;
color: #000;
}
&.ts {
background: #3178c6;
color: #fff;
}
}
}
}
.editor-controls {
float: right;
.el-switch__label {
margin-top: -3px;
}
.el-switch__label * {
font-size: 12px;
}
.btn {
border-radius: 0;
margin: 0;
border: none;
height: 30px;
background: none;
color: $clr-text;
&:hover {
color: #409eff;
}
& > * {
display: inline-block;
vertical-align: middle;
}
svg {
width: 15px;
height: 15px;
}
&.run {
color: #fff;
background-color: #409eff;
}
&.run:hover {
background-color: lighten($color: #409eff, $amount: 5);
}
&.codepen {
svg {
transform: scale(1.08);
}
}
}
}
}
.right-container {
position: absolute;
right: 0;
width: 50%;
height: 100%;
padding: 0;
padding-left: $handler-width;
border: none;
z-index: 30;
background: $clr-bg;
}
</style>