feat: use live editor on desktop

This commit is contained in:
Justineo
2021-11-20 13:22:48 +08:00
committed by GU Yiling
parent 54393e41bc
commit 6fd9a5a4f4
20 changed files with 1257 additions and 1037 deletions

View File

@@ -1,7 +1,7 @@
<template>
<article
class="one-demo"
:class="{ expanded: localExpanded }"
:class="{ expanded }"
>
<section class="demo">
<browser-window
@@ -26,27 +26,35 @@
ui="icon"
@click="play('CodeSandbox')"
>
<veui-icon
name="one-demo-codesandbox"
/>
<veui-icon name="one-demo-codesandbox"/>
</veui-button>
<veui-button
v-tooltip="t('playInStackBlitz')"
ui="icon"
@click="play('StackBlitz')"
>
<veui-icon
name="one-demo-stackblitz"
/>
<veui-icon name="one-demo-stackblitz"/>
</veui-button>
<veui-button
v-tooltip="t(localExpanded ? 'hideCode' : 'showCode')"
v-tooltip="t('expandEditor')"
class="toggle-editor"
ui="icon"
@click="localExpanded = !localExpanded"
@click="editing = true"
>
<veui-icon
scale="1.2"
:name="localExpanded ? 'one-demo-code-off' : 'one-demo-code'"
:name="expanded ? 'one-demo-code-off' : 'one-demo-code'"
/>
</veui-button>
<veui-button
v-tooltip="t(expanded ? 'hideCode' : 'showCode')"
class="toggle-source"
ui="icon"
@click="expanded = !expanded"
>
<veui-icon
scale="1.2"
:name="expanded ? 'one-demo-code-off' : 'one-demo-code'"
/>
</veui-button>
<one-edit-link
@@ -59,16 +67,25 @@
v-if="$slots.source"
ref="source"
class="source"
:style="{ height: localExpanded ? `${sourceHeight || 0}px` : '0' }"
:style="{ height: expanded ? `${sourceHeight || 0}px` : '0' }"
>
<slot name="source"/>
</section>
<transition name="editor">
<one-repl
v-if="editing"
class="one-demo-editor"
:code="code"
@close="editing = false"
/>
</transition>
</article>
</template>
<script>
import { Button, Icon } from 'veui'
import tooltip from 'veui/directives/tooltip'
import modal from 'veui/managers/modal'
import i18n from 'veui/mixins/i18n'
import { BrowserWindow } from 'vue-windows'
import { getLocale } from '../common/i18n'
@@ -84,26 +101,29 @@ export default {
'veui-button': Button,
'veui-icon': Icon,
BrowserWindow,
OneEditLink
OneEditLink,
OneRepl: () => import('./OneRepl')
},
mixins: [i18n],
props: {
expanded: Boolean,
browser: String,
path: String
},
data () {
return {
code: '',
sourceHeight: 0,
localExpanded: this.expanded
expanded: false,
editing: false
}
},
watch: {
expanded (val) {
this.localExpanded = val
},
localExpanded (val) {
this.$emit('update:expanded', val)
editing (value) {
if (value) {
modal.open()
} else {
modal.close()
}
}
},
mounted () {
@@ -113,6 +133,8 @@ export default {
style.height = source.offsetHeight
this.sourceHeight = source.offsetHeight
style.height = '0'
this.code = this.$refs.source?.textContent
},
methods: {
play (vendor) {
@@ -126,22 +148,26 @@ Icon.register({
'one-demo-code': {
width: 24,
height: 24,
d: 'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6l6 6l1.4-1.4zm5.2 0l4.6-4.6l-4.6-4.6L16 6l6 6l-6 6l-1.4-1.4z'
d:
'M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6l6 6l1.4-1.4zm5.2 0l4.6-4.6l-4.6-4.6L16 6l6 6l-6 6l-1.4-1.4z'
},
'one-demo-code-off': {
width: 24,
height: 24,
d: 'M19.17 12l-4.58-4.59L16 6l6 6l-3.59 3.59L17 14.17L19.17 12zM1.39 4.22l4.19 4.19L2 12l6 6l1.41-1.41L4.83 12L7 9.83l12.78 12.78l1.41-1.41L2.81 2.81L1.39 4.22z'
d:
'M19.17 12l-4.58-4.59L16 6l6 6l-3.59 3.59L17 14.17L19.17 12zM1.39 4.22l4.19 4.19L2 12l6 6l1.41-1.41L4.83 12L7 9.83l12.78 12.78l1.41-1.41L2.81 2.81L1.39 4.22z'
},
'one-demo-codesandbox': {
width: 32,
height: 32,
d: 'M2.667 8l13.938-8l13.943 8l.12 15.932L16.605 32L2.667 24zm2.786 3.307v6.344l4.458 2.479v4.688l5.297 3.063V16.85zm22.318 0l-9.755 5.542V27.88l5.292-3.063v-4.682l4.464-2.484zM6.844 8.802l9.74 5.526l9.76-5.573l-5.161-2.932l-4.547 2.594l-4.573-2.625z'
d:
'M2.667 8l13.938-8l13.943 8l.12 15.932L16.605 32L2.667 24zm2.786 3.307v6.344l4.458 2.479v4.688l5.297 3.063V16.85zm22.318 0l-9.755 5.542V27.88l5.292-3.063v-4.682l4.464-2.484zM6.844 8.802l9.74 5.526l9.76-5.573l-5.161-2.932l-4.547 2.594l-4.573-2.625z'
},
'one-demo-stackblitz': {
width: 28,
height: 28,
d: 'M12.747 16.273h-7.46L18.925 1.5l-3.671 10.227h7.46L9.075 26.5l3.671-10.227z'
d:
'M12.747 16.273h-7.46L18.925 1.5l-3.671 10.227h7.46L9.075 26.5l3.671-10.227z'
}
})
</script>
@@ -208,4 +234,33 @@ Icon.register({
top 50%
transform translateY(-50%)
font-size 12px
.one-demo-editor
position fixed
top 0
left 0
right 0
bottom 0
z-index 10
background-color #fff
.editor-enter-active
.editor-leave-active
transform-origin 50% 50%
transition all 0.3s
.editor-enter
.editor-leave-to
opacity 0
transform scale(0.99) translateY(3px)
.toggle-source
display none
@media (max-width 480px)
.toggle-source
display inline-block
.toggle-editor
display none
</style>

262
components/OneLive.vue Normal file
View File

@@ -0,0 +1,262 @@
<template>
<v-splitpanes class="one-live">
<v-pane
min-size="30"
class="live-editor"
>
<v-live-editor
:code="localCode"
line-numbers
@change="handleChange"
/>
<div class="editor-toolbar">
<veui-button
v-tooltip="t('@onedemo.playInCodeSandbox')"
ui="s translucent square"
@click="play('CodeSandbox')"
>
<veui-icon name="one-demo-codesandbox"/>
</veui-button>
<veui-button
v-tooltip="t('@onedemo.playInStackBlitz')"
ui="s translucent square"
@click="play('StackBlitz')"
>
<veui-icon name="one-demo-stackblitz"/>
</veui-button>
<veui-button
v-tooltip="t('reset')"
ui="s translucent square"
@click="reset"
>
<veui-icon name="anticlockwise"/>
</veui-button>
<veui-button
v-tooltip="t('copyCode')"
ui="s translucent square"
@click="copy"
>
<veui-icon name="copy"/>
</veui-button>
<div class="editor-live-badge">
<span>Live</span>
</div>
</div>
</v-pane>
<v-pane
min-size="40"
class="live-preview"
>
<v-live-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 { VueLiveEditor, VueLivePreview } from 'vue-live'
import 'vue-live/lib/vue-live.esm.css'
import 'prism-theme-night-owl/build/no-italics.css'
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'
Vue.use(toast)
export default {
name: 'one-live',
components: {
'veui-button': Button,
'veui-icon': Icon,
'veui-alert': Alert,
'v-splitpanes': Splitpanes,
'v-pane': Pane,
'v-live-editor': VueLiveEditor,
'v-live-preview': VueLivePreview
},
directives: {
tooltip
},
mixins: [i18n],
inheritAttrs: false,
props: {
code: {
type: String,
default: ''
}
},
data () {
return {
localCode: this.code,
transformedCode: '',
error: null,
imports: {
veui,
lodash,
'veui-theme-dls-icons': {}
}
}
},
computed: {
errorMessage () {
const { error } = this
if (!error) {
return null
}
return error.name ? `${error.name}: ${error.message}` : error.message
}
},
watch: {
localCode: {
immediate: true,
handler (code) {
this.$nextTick(() => {
try {
this.transformedCode = transformLessCode(code)
} catch (e) {
this.error = e
return
}
this.error = null
})
}
}
},
methods: {
play (vendor) {
let locale = getLocale(this.$route.path)
play(this.code, { 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'))
}
},
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)
.editor-toolbar
position absolute
top 12px
right 20px
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
span
position relative
top -1px
&::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)
</style>

104
components/OneRepl.vue Normal file
View File

@@ -0,0 +1,104 @@
<template>
<article class="repl">
<header class="header">
<h1>{{ t('liveEdit') }}</h1>
<section class="actions">
<veui-button
ui="strong text"
@click="handleClose"
>
{{ t('exit') }}
</veui-button>
</section>
</header>
<one-live
class="editor"
:code="code"
/>
</article>
</template>
<script>
import { Button } from 'veui'
import i18n from 'veui/mixins/i18n'
import OneLive from './OneLive.vue'
export default {
name: 'one-repl',
components: {
'veui-button': Button,
'one-live': OneLive
},
mixins: [i18n],
props: {
code: {
type: String,
default: ''
}
},
methods: {
handleClose () {
this.$emit('close')
}
}
}
</script>
<style lang="stylus" scoped>
.repl
display flex
flex-direction column
.header
display flex
align-items center
flex none
height 48px
padding 0 24px
box-shadow 0 0 4px #0006
position relative
h1
flex none
margin 0
font-size 16px
.actions
display flex
justify-content flex-end
flex 1 1 auto
.editor
flex 1 1 auto
height calc(100vh - 48px)
& >>> .prism-editor-wrapper
padding 8px 12px
font-size 12px
color #eee
background-color #0a0b0d
line-height 1.5
font-family Menlo, consolas, monospace
-webkit-font-smoothing auto
&::-webkit-scrollbar
width 8px
background transparent
transition all 0.3s
&-thumb
border-radius 4px
background-color #282c33
&:hover::-webkit-scrollbar-thumb
background-color #545b66
textarea
outline none
& >>> .live-preview
padding 24px 36px
& >>> .VueLive-error
display none
</style>