feat: use live editor on desktop
This commit is contained in:
@@ -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
262
components/OneLive.vue
Normal 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
104
components/OneRepl.vue
Normal 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>
|
Reference in New Issue
Block a user