405 lines
11 KiB
Vue
405 lines
11 KiB
Vue
<template>
|
|
<v-splitpanes class="one-live">
|
|
<v-pane min-size="30" class="live-editor">
|
|
<v-monaco-editor
|
|
v-model="localCode"
|
|
style="height: 100%"
|
|
language="html"
|
|
:theme="theme"
|
|
:options="{
|
|
automaticLayout: true,
|
|
minimap: {
|
|
enabled: false,
|
|
},
|
|
scrollBeyondLastLine: false,
|
|
}"
|
|
/>
|
|
<div class="editor-toolbar">
|
|
<veui-button v-tooltip="t('reset')" :ui="iconUi" @click="reset">
|
|
<veui-icon name="anticlockwise" />
|
|
</veui-button>
|
|
<veui-button v-tooltip="t('copyCode')" :ui="iconUi" @click="copy">
|
|
<veui-icon name="copy" />
|
|
</veui-button>
|
|
<veui-button
|
|
v-tooltip="t(colorSchemeLabelKey)"
|
|
:ui="iconUi"
|
|
@click="switchColorScheme"
|
|
>
|
|
<veui-icon :name="colorSchemeIcon" scale="1.2" />
|
|
</veui-button>
|
|
<!-- <div class="editor-live-badge">
|
|
<span>Live</span>
|
|
</div> -->
|
|
</div>
|
|
</v-pane>
|
|
<v-pane
|
|
min-size="40"
|
|
class="live-preview"
|
|
:class="{
|
|
'live-preview-browser': browser,
|
|
}"
|
|
>
|
|
<one-iframe
|
|
v-if="browser"
|
|
global-style="body { margin: 0 !important; } body > article { margin: 24px 36px; } .veui-layout { min-width: auto !important; }"
|
|
>
|
|
<v-live-preview
|
|
class="editor-preview"
|
|
:code="transformedCode"
|
|
:requires="imports"
|
|
:check-variable-availability="false"
|
|
@success="dismissError"
|
|
@error="handleError"
|
|
/>
|
|
</one-iframe>
|
|
<v-live-preview
|
|
v-else
|
|
class="editor-preview"
|
|
:code="transformedCode"
|
|
:requires="imports"
|
|
:check-variable-availability="false"
|
|
@success="dismissError"
|
|
@error="handleError"
|
|
/>
|
|
<transition name="editor-error">
|
|
<veui-alert
|
|
v-if="error"
|
|
v-tooltip="t('dismiss')"
|
|
ui="s"
|
|
type="error"
|
|
class="editor-error"
|
|
@click.native="dismissError"
|
|
>
|
|
<code>{{ errorMessage }}</code>
|
|
</veui-alert>
|
|
</transition>
|
|
</v-pane>
|
|
</v-splitpanes>
|
|
</template>
|
|
|
|
<script>
|
|
import Vue from "vue";
|
|
import { VueLivePreview } from "vue-live";
|
|
import MonacoEditor from "vue-monaco";
|
|
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
|
import NightOwl from "monaco-themes/themes/Night Owl.json";
|
|
import { Button, Icon, Alert } from "veui";
|
|
import * as veui from "veui";
|
|
import lodash from "lodash";
|
|
import "veui-theme-dls-icons";
|
|
import tooltip from "veui/directives/tooltip";
|
|
import i18n from "veui/mixins/i18n";
|
|
import toast from "veui/plugins/toast";
|
|
import "veui-theme-dls-icons/copy";
|
|
import "veui-theme-dls-icons/anticlockwise";
|
|
import { Splitpanes, Pane } from "splitpanes";
|
|
import "splitpanes/dist/splitpanes.css";
|
|
import { getLocale } from "../common/i18n";
|
|
import { play } from "../common/play";
|
|
import { transformLessCode } from "../common/transform";
|
|
import { loadPref, savePref } from "../common/util";
|
|
import OneIframe from "./OneIframe";
|
|
|
|
Vue.use(toast);
|
|
|
|
editor.defineTheme("night-owl", NightOwl);
|
|
|
|
Icon.register({
|
|
"one-live-color-scheme-auto": {
|
|
width: 24,
|
|
height: 24,
|
|
d: "M7.5 2c-1.79 1.15-3 3.18-3 5.5s1.21 4.35 3.03 5.5C4.46 13 2 10.54 2 7.5A5.5 5.5 0 0 1 7.5 2m11.57 1.5l1.43 1.43L4.93 20.5L3.5 19.07L19.07 3.5m-6.18 2.43L11.41 5L9.97 6l.42-1.7L9 3.24l1.75-.12l.58-1.65L12 3.1l1.73.03l-1.35 1.13l.51 1.67m-3.3 3.61l-1.16-.73l-1.12.78l.34-1.32l-1.09-.83l1.36-.09l.45-1.29l.51 1.27l1.36.03l-1.05.87l.4 1.31M19 13.5a5.5 5.5 0 0 1-5.5 5.5c-1.22 0-2.35-.4-3.26-1.07l7.69-7.69c.67.91 1.07 2.04 1.07 3.26m-4.4 6.58l2.77-1.15l-.24 3.35l-2.53-2.2m4.33-2.7l1.15-2.77l2.2 2.54l-3.35.23m1.15-4.96l-1.14-2.78l3.34.24l-2.2 2.54M9.63 18.93l2.77 1.15l-2.53 2.19l-.24-3.34z",
|
|
},
|
|
"one-live-color-scheme-light": {
|
|
width: 24,
|
|
height: 24,
|
|
d: "M12 9c1.65 0 3 1.35 3 3s-1.35 3-3 3s-3-1.35-3-3s1.35-3 3-3m0-2c-2.76 0-5 2.24-5 5s2.24 5 5 5s5-2.24 5-5s-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 0 0-1.41 0a.996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 0 0-1.41 0a.996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 0 0 0-1.41a.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 0 0 0-1.41a.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z",
|
|
},
|
|
"one-live-color-scheme-dark": {
|
|
width: 24,
|
|
height: 24,
|
|
d: "M9.37 5.51A7.35 7.35 0 0 0 9.1 7.5c0 4.08 3.32 7.4 7.4 7.4c.68 0 1.35-.09 1.99-.27A7.014 7.014 0 0 1 12 19c-3.86 0-7-3.14-7-7c0-2.93 1.81-5.45 4.37-6.49zM12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26a5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z",
|
|
},
|
|
});
|
|
|
|
const iconPackage = "veui-theme-dls-icons";
|
|
const iconNames = Object.keys(Icon.icons).filter(
|
|
(name) => !name.startsWith("one-demo-") && Icon.icons[name]
|
|
);
|
|
const iconModules = [iconPackage].concat(
|
|
iconNames.map((name) => `${iconPackage}/${name}`)
|
|
);
|
|
|
|
const colorSchemeOptions = ["dark", "light", "auto"];
|
|
|
|
export default {
|
|
name: "one-live",
|
|
components: {
|
|
"veui-button": Button,
|
|
"veui-icon": Icon,
|
|
"veui-alert": Alert,
|
|
"v-splitpanes": Splitpanes,
|
|
"v-pane": Pane,
|
|
"v-monaco-editor": MonacoEditor,
|
|
"v-live-preview": VueLivePreview,
|
|
OneIframe,
|
|
},
|
|
directives: {
|
|
tooltip,
|
|
},
|
|
mixins: [i18n],
|
|
inheritAttrs: false,
|
|
props: {
|
|
code: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
browser: Boolean,
|
|
},
|
|
data() {
|
|
return {
|
|
localCode: this.code,
|
|
transformedCode: "",
|
|
error: null,
|
|
imports: {
|
|
veui,
|
|
lodash,
|
|
...iconModules.reduce((mocks, module) => {
|
|
mocks[module] = true;
|
|
return mocks;
|
|
}, {}),
|
|
},
|
|
colorSchemeOption: loadPref("prefers-color-scheme"),
|
|
colorSchemeSystemPref: null,
|
|
};
|
|
},
|
|
computed: {
|
|
errorMessage() {
|
|
const { error } = this;
|
|
|
|
if (!error) {
|
|
return null;
|
|
}
|
|
|
|
return error.name ? `${error.name}: ${error.message}` : error.message;
|
|
},
|
|
colorScheme() {
|
|
if (!this.colorSchemeOption) {
|
|
return "dark";
|
|
}
|
|
|
|
if (this.colorSchemeOption === "auto") {
|
|
return this.colorSchemeSystemPref;
|
|
}
|
|
|
|
return this.colorSchemeOption;
|
|
},
|
|
theme() {
|
|
return this.colorScheme === "dark" ? "night-owl" : "vs";
|
|
},
|
|
iconUi() {
|
|
const ui = "s square icon";
|
|
return this.colorScheme === "dark" ? `${ui} reverse` : ui;
|
|
},
|
|
colorSchemeIcon() {
|
|
if (!this.colorSchemeOption) {
|
|
return "one-live-color-scheme-dark";
|
|
}
|
|
|
|
return {
|
|
light: "one-live-color-scheme-light",
|
|
dark: "one-live-color-scheme-dark",
|
|
auto: "one-live-color-scheme-auto",
|
|
}[this.colorSchemeOption];
|
|
},
|
|
colorSchemeLabelKey() {
|
|
if (!this.colorSchemeOption) {
|
|
return "darkMode";
|
|
}
|
|
|
|
return {
|
|
light: "lightMode",
|
|
dark: "darkMode",
|
|
auto: "followSystem",
|
|
}[this.colorSchemeOption];
|
|
},
|
|
},
|
|
watch: {
|
|
localCode: {
|
|
immediate: true,
|
|
handler(code) {
|
|
this.$nextTick(() => {
|
|
try {
|
|
this.transformedCode = transformLessCode(code);
|
|
} catch (e) {
|
|
this.error = e;
|
|
return;
|
|
}
|
|
this.error = null;
|
|
});
|
|
},
|
|
},
|
|
},
|
|
mounted() {
|
|
this.mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
this.mql.addEventListener("change", this.handleColorSchemeChange);
|
|
this.colorSchemeSystemPref = this.mql.matches ? "dark" : "light";
|
|
},
|
|
beforeDestroy() {
|
|
this.mql.removeEventListener("change", this.handleColorSchemeChange);
|
|
},
|
|
methods: {
|
|
play(vendor) {
|
|
let locale = getLocale(this.$route.path);
|
|
play(this.localCode, { locale, vendor });
|
|
},
|
|
async copy() {
|
|
try {
|
|
await navigator.clipboard.writeText(this.code);
|
|
this.$toast.success(this.t("copySuccess"));
|
|
} catch (e) {
|
|
this.$toast.error(this.t("copyFailed"));
|
|
}
|
|
},
|
|
switchColorScheme() {
|
|
if (!this.colorSchemeOption) {
|
|
this.colorSchemeOption = "light";
|
|
} else {
|
|
this.colorSchemeOption =
|
|
colorSchemeOptions[
|
|
(colorSchemeOptions.indexOf(this.colorSchemeOption) + 1) %
|
|
colorSchemeOptions.length
|
|
];
|
|
}
|
|
|
|
savePref("prefers-color-scheme", this.colorSchemeOption);
|
|
},
|
|
handleColorSchemeChange() {
|
|
this.colorSchemeSystemPref = this.mql.matches ? "dark" : "light";
|
|
},
|
|
reset() {
|
|
this.localCode = this.code;
|
|
},
|
|
handleChange(code) {
|
|
this.localCode = code;
|
|
},
|
|
handleError(error) {
|
|
this.error = error;
|
|
},
|
|
dismissError() {
|
|
this.error = null;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="stylus" scoped>
|
|
.one-live {
|
|
& >>> .splitpanes__pane {
|
|
position: relative;
|
|
}
|
|
|
|
& >>> .splitpanes__splitter {
|
|
width: 6px;
|
|
background: #eee;
|
|
transition: all 0.3s;
|
|
|
|
&:hover {
|
|
background: #ccc;
|
|
transform: scaleX(2);
|
|
}
|
|
}
|
|
|
|
& >>> .live-preview {
|
|
overflow: auto;
|
|
}
|
|
}
|
|
|
|
.live-preview-browser {
|
|
transform: translate(0, 0);
|
|
padding: 0 !important;
|
|
}
|
|
|
|
.editor-toolbar {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.editor-live-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
margin-left: 8px;
|
|
padding: 0 4px 0 20px;
|
|
border-radius: 2px;
|
|
font-size: 12px;
|
|
background-color: #00bf5c;
|
|
color: #fff;
|
|
height: 18px;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 7px;
|
|
top: 6px;
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background-color: #fff;
|
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 1);
|
|
animation: pulse 2s infinite;
|
|
}
|
|
}
|
|
|
|
.editor-error {
|
|
position: absolute;
|
|
bottom: 16px;
|
|
right: 16px;
|
|
left: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
|
|
&:hover {
|
|
opacity: 0.8;
|
|
}
|
|
}
|
|
|
|
.editor-error-enter, .editor-error-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% {
|
|
transform: scale(0.95);
|
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.9);
|
|
}
|
|
|
|
70% {
|
|
transform: scale(1);
|
|
box-shadow: 0 0 0 12px rgba(255, 255, 255, 0);
|
|
}
|
|
|
|
100% {
|
|
transform: scale(0.95);
|
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
|
|
}
|
|
}
|
|
|
|
.veui-button[ui~='icon'][ui~='reverse'] {
|
|
color: #ebedf5;
|
|
|
|
&:hover, &[data-focus-visible-added] {
|
|
color: #f6f7fa;
|
|
}
|
|
|
|
&:active {
|
|
color: #fff;
|
|
}
|
|
}
|
|
</style>
|