chore(sfc-playground): update code style and syntax
This commit is contained in:
parent
ceace3a8cc
commit
e22d7cdb08
@ -1,3 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import Header from './Header.vue'
|
||||
import SplitPane from './SplitPane.vue'
|
||||
import Editor from './editor/Editor.vue'
|
||||
import Output from './output/Output.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="wrapper">
|
||||
@ -12,13 +19,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Header from './Header.vue'
|
||||
import SplitPane from './SplitPane.vue'
|
||||
import Editor from './editor/Editor.vue'
|
||||
import Output from './output/Output.vue'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-size: 13px;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { downloadProject } from './download/download'
|
||||
import { setVersion, resetVersion } from './sfcCompiler'
|
||||
|
@ -1,23 +1,17 @@
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<pre v-if="!dismissed && (err || warn)"
|
||||
class="msg"
|
||||
:class="err ? 'err' : 'warn'"
|
||||
@click="dismissed = true">{{ formatMessage(err || warn) }}</pre>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import type { CompilerError } from '@vue/compiler-sfc'
|
||||
import { CompilerError } from '@vue/compiler-sfc'
|
||||
|
||||
const props = defineProps(['err', 'warn'])
|
||||
|
||||
const dismissed = ref(false)
|
||||
|
||||
watch(() => [props.err, props.warn], () => {
|
||||
dismissed.value = false
|
||||
})
|
||||
watch(
|
||||
() => [props.err, props.warn],
|
||||
() => {
|
||||
dismissed.value = false
|
||||
}
|
||||
)
|
||||
|
||||
function formatMessage(err: string | Error): string {
|
||||
if (typeof err === 'string') {
|
||||
@ -33,6 +27,18 @@ function formatMessage(err: string | Error): string {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<pre
|
||||
v-if="!dismissed && (err || warn)"
|
||||
class="msg"
|
||||
:class="err ? 'err' : 'warn'"
|
||||
@click="dismissed = true"
|
||||
>{{ formatMessage(err || warn) }}</pre
|
||||
>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.msg {
|
||||
position: absolute;
|
||||
|
@ -1,22 +1,3 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
class="split-pane"
|
||||
:class="{ dragging: state.dragging }"
|
||||
@mousemove="dragMove"
|
||||
@mouseup="dragEnd"
|
||||
@mouseleave="dragEnd"
|
||||
>
|
||||
<div class="left" :style="{ width: boundSplit() + '%' }">
|
||||
<slot name="left" />
|
||||
<div class="dragger" @mousedown.prevent="dragStart" />
|
||||
</div>
|
||||
<div class="right" :style="{ width: (100 - boundSplit()) + '%' }">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
@ -29,11 +10,7 @@ const state = reactive({
|
||||
|
||||
function boundSplit() {
|
||||
const { split } = state
|
||||
return split < 20
|
||||
? 20
|
||||
: split > 80
|
||||
? 80
|
||||
: split
|
||||
return split < 20 ? 20 : split > 80 ? 80 : split
|
||||
}
|
||||
|
||||
let startPosition = 0
|
||||
@ -50,7 +27,7 @@ function dragMove(e: MouseEvent) {
|
||||
const position = e.pageX
|
||||
const totalSize = container.value.offsetWidth
|
||||
const dp = position - startPosition
|
||||
state.split = startSplit + ~~(dp / totalSize * 100)
|
||||
state.split = startSplit + ~~((dp / totalSize) * 100)
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +36,25 @@ function dragEnd() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
class="split-pane"
|
||||
:class="{ dragging: state.dragging }"
|
||||
@mousemove="dragMove"
|
||||
@mouseup="dragEnd"
|
||||
@mouseleave="dragEnd"
|
||||
>
|
||||
<div class="left" :style="{ width: boundSplit() + '%' }">
|
||||
<slot name="left" />
|
||||
<div class="dragger" @mousedown.prevent="dragStart" />
|
||||
</div>
|
||||
<div class="right" :style="{ width: 100 - boundSplit() + '%' }">
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.split-pane {
|
||||
display: flex;
|
||||
@ -88,4 +84,4 @@ function dragEnd() {
|
||||
width: 10px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,11 +1,3 @@
|
||||
<template>
|
||||
<FileSelector/>
|
||||
<div class="editor-container">
|
||||
<CodeMirror @change="onChange" :value="activeCode" :mode="activeMode" />
|
||||
<Message :err="store.errors[0]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FileSelector from './FileSelector.vue'
|
||||
import CodeMirror from '../codemirror/CodeMirror.vue'
|
||||
@ -19,8 +11,8 @@ const onChange = debounce((code: string) => {
|
||||
}, 250)
|
||||
|
||||
const activeCode = ref(store.activeFile.code)
|
||||
const activeMode = computed(
|
||||
() => (store.activeFilename.endsWith('.vue') ? 'htmlmixed' : 'javascript')
|
||||
const activeMode = computed(() =>
|
||||
store.activeFilename.endsWith('.vue') ? 'htmlmixed' : 'javascript'
|
||||
)
|
||||
|
||||
watch(
|
||||
@ -31,6 +23,14 @@ watch(
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FileSelector />
|
||||
<div class="editor-container">
|
||||
<CodeMirror @change="onChange" :value="activeCode" :mode="activeMode" />
|
||||
<Message :err="store.errors[0]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.editor-container {
|
||||
height: calc(100% - 35px);
|
||||
|
@ -1,31 +1,6 @@
|
||||
<template>
|
||||
<div class="file-selector">
|
||||
<div
|
||||
v-for="(file, i) in Object.keys(store.files)"
|
||||
class="file"
|
||||
:class="{ active: store.activeFilename === file }"
|
||||
@click="setActive(file)">
|
||||
<span class="label">{{ file }}</span>
|
||||
<span v-if="i > 0" class="remove" @click.stop="deleteFile(file)">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" class="svelte-cghqrp"><line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line><line stroke="#999" x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="pending" class="file" >
|
||||
<input
|
||||
v-model="pendingFilename"
|
||||
spellcheck="false"
|
||||
@keyup.enter="doneAddFile"
|
||||
@keyup.esc="cancelAddFile"
|
||||
@vnodeMounted="focus">
|
||||
</div>
|
||||
<button class="add" @click="startAddFile">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { store, addFile, deleteFile, setActive } from '../store'
|
||||
import { ref } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { ref, VNode } from 'vue'
|
||||
|
||||
const pending = ref(false)
|
||||
const pendingFilename = ref('Comp.vue')
|
||||
@ -39,7 +14,7 @@ function cancelAddFile() {
|
||||
}
|
||||
|
||||
function focus({ el }: VNode) {
|
||||
(el as HTMLInputElement).focus()
|
||||
;(el as HTMLInputElement).focus()
|
||||
}
|
||||
|
||||
function doneAddFile() {
|
||||
@ -50,7 +25,9 @@ function doneAddFile() {
|
||||
!filename.endsWith('.js') &&
|
||||
filename !== 'import-map.json'
|
||||
) {
|
||||
store.errors = [`Playground only supports *.vue, *.js files or import-map.json.`]
|
||||
store.errors = [
|
||||
`Playground only supports *.vue, *.js files or import-map.json.`
|
||||
]
|
||||
return
|
||||
}
|
||||
|
||||
@ -66,6 +43,35 @@ function doneAddFile() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="file-selector">
|
||||
<div
|
||||
v-for="(file, i) in Object.keys(store.files)"
|
||||
class="file"
|
||||
:class="{ active: store.activeFilename === file }"
|
||||
@click="setActive(file)"
|
||||
>
|
||||
<span class="label">{{ file }}</span>
|
||||
<span v-if="i > 0" class="remove" @click.stop="deleteFile(file)">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" class="svelte-cghqrp">
|
||||
<line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line stroke="#999" x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="pending" class="file">
|
||||
<input
|
||||
v-model="pendingFilename"
|
||||
spellcheck="false"
|
||||
@keyup.enter="doneAddFile"
|
||||
@keyup.esc="cancelAddFile"
|
||||
@vnodeMounted="focus"
|
||||
/>
|
||||
</div>
|
||||
<button class="add" @click="startAddFile">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.file-selector {
|
||||
box-sizing: border-box;
|
||||
|
@ -1,6 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import Preview from './Preview.vue'
|
||||
import CodeMirror from '../codemirror/CodeMirror.vue'
|
||||
import { store } from '../store'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const modes = ['preview', 'js', 'css', 'ssr'] as const
|
||||
|
||||
type Modes = typeof modes[number]
|
||||
const mode = ref<Modes>('preview')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-buttons">
|
||||
<button v-for="m of modes" :class="{ active: mode === m }" @click="mode = m">{{ m }}</button>
|
||||
<button
|
||||
v-for="m of modes"
|
||||
:class="{ active: mode === m }"
|
||||
@click="mode = m"
|
||||
>
|
||||
{{ m }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="output-container">
|
||||
@ -14,18 +32,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Preview from './Preview.vue'
|
||||
import CodeMirror from '../codemirror/CodeMirror.vue'
|
||||
import { store } from '../store'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const modes = ['preview', 'js', 'css', 'ssr'] as const
|
||||
|
||||
type Modes = typeof modes[number]
|
||||
const mode = ref<Modes>('preview')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.output-container {
|
||||
height: calc(100% - 35px);
|
||||
|
@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div class="preview-container" ref="container">
|
||||
</div>
|
||||
<Message :err="runtimeError" />
|
||||
<Message v-if="!runtimeError" :warn="runtimeWarning" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Message from '../Message.vue'
|
||||
import { ref, onMounted, onUnmounted, watchEffect, watch } from 'vue'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
watchEffect,
|
||||
watch,
|
||||
WatchStopHandle
|
||||
} from 'vue'
|
||||
import srcdoc from './srcdoc.html?raw'
|
||||
import { PreviewProxy } from './PreviewProxy'
|
||||
import { MAIN_FILE, vueRuntimeUrl } from '../sfcCompiler'
|
||||
@ -27,34 +26,35 @@ let stopUpdateWatcher: WatchStopHandle
|
||||
onMounted(createSandbox)
|
||||
|
||||
// reset sandbox when import map changes
|
||||
watch(() => store.importMap, (importMap, prev) => {
|
||||
if (!importMap) {
|
||||
if (prev) {
|
||||
// import-map.json deleted
|
||||
createSandbox()
|
||||
}
|
||||
return
|
||||
}
|
||||
try {
|
||||
const map = JSON.parse(importMap)
|
||||
if (!map.imports) {
|
||||
store.errors = [
|
||||
`import-map.json is missing "imports" field.`
|
||||
]
|
||||
watch(
|
||||
() => store.importMap,
|
||||
(importMap, prev) => {
|
||||
if (!importMap) {
|
||||
if (prev) {
|
||||
// import-map.json deleted
|
||||
createSandbox()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (map.imports.vue) {
|
||||
store.errors = [
|
||||
'Select Vue versions using the top-right dropdown.\n' +
|
||||
'Specifying it in the import map has no effect.'
|
||||
]
|
||||
try {
|
||||
const map = JSON.parse(importMap)
|
||||
if (!map.imports) {
|
||||
store.errors = [`import-map.json is missing "imports" field.`]
|
||||
return
|
||||
}
|
||||
if (map.imports.vue) {
|
||||
store.errors = [
|
||||
'Select Vue versions using the top-right dropdown.\n' +
|
||||
'Specifying it in the import map has no effect.'
|
||||
]
|
||||
}
|
||||
createSandbox()
|
||||
} catch (e) {
|
||||
store.errors = [e as Error]
|
||||
return
|
||||
}
|
||||
createSandbox()
|
||||
} catch (e) {
|
||||
store.errors = [e]
|
||||
return
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// reset sandbox when version changes
|
||||
watch(vueRuntimeUrl, createSandbox)
|
||||
@ -73,21 +73,24 @@ function createSandbox() {
|
||||
}
|
||||
|
||||
sandbox = document.createElement('iframe')
|
||||
sandbox.setAttribute('sandbox', [
|
||||
'allow-forms',
|
||||
'allow-modals',
|
||||
'allow-pointer-lock',
|
||||
'allow-popups',
|
||||
'allow-same-origin',
|
||||
'allow-scripts',
|
||||
'allow-top-navigation-by-user-activation'
|
||||
].join(' '))
|
||||
sandbox.setAttribute(
|
||||
'sandbox',
|
||||
[
|
||||
'allow-forms',
|
||||
'allow-modals',
|
||||
'allow-pointer-lock',
|
||||
'allow-popups',
|
||||
'allow-same-origin',
|
||||
'allow-scripts',
|
||||
'allow-top-navigation-by-user-activation'
|
||||
].join(' ')
|
||||
)
|
||||
|
||||
let importMap: Record<string, any>
|
||||
try {
|
||||
importMap = JSON.parse(store.importMap || `{}`)
|
||||
} catch (e) {
|
||||
store.errors = [`Syntax error in import-map.json: ${e.message}`]
|
||||
store.errors = [`Syntax error in import-map.json: ${(e as Error).message}`]
|
||||
return
|
||||
}
|
||||
|
||||
@ -95,7 +98,10 @@ function createSandbox() {
|
||||
importMap.imports = {}
|
||||
}
|
||||
importMap.imports.vue = vueRuntimeUrl.value
|
||||
const sandboxSrc = srcdoc.replace(/<!--IMPORT_MAP-->/, JSON.stringify(importMap))
|
||||
const sandboxSrc = srcdoc.replace(
|
||||
/<!--IMPORT_MAP-->/,
|
||||
JSON.stringify(importMap)
|
||||
)
|
||||
sandbox.srcdoc = sandboxSrc
|
||||
container.value.appendChild(sandbox)
|
||||
|
||||
@ -104,13 +110,15 @@ function createSandbox() {
|
||||
// pending_imports = progress;
|
||||
},
|
||||
on_error: (event: any) => {
|
||||
const msg = event.value instanceof Error ? event.value.message : event.value
|
||||
const msg =
|
||||
event.value instanceof Error ? event.value.message : event.value
|
||||
if (
|
||||
msg.includes('Failed to resolve module specifier') ||
|
||||
msg.includes('Error resolving module specifier')
|
||||
) {
|
||||
runtimeError.value = msg.replace(/\. Relative references must.*$/, '') +
|
||||
`.\nTip: add an "import-map.json" file to specify import paths for dependencies.`
|
||||
runtimeError.value =
|
||||
msg.replace(/\. Relative references must.*$/, '') +
|
||||
`.\nTip: add an "import-map.json" file to specify import paths for dependencies.`
|
||||
} else {
|
||||
runtimeError.value = event.value
|
||||
}
|
||||
@ -173,24 +181,30 @@ async function updatePreview() {
|
||||
`window.__modules__ = {};window.__css__ = ''`,
|
||||
...modules,
|
||||
`
|
||||
import { createApp as _createApp } from "vue"
|
||||
|
||||
if (window.__app__) {
|
||||
window.__app__.unmount()
|
||||
document.getElementById('app').innerHTML = ''
|
||||
}
|
||||
|
||||
document.getElementById('__sfc-styles').innerHTML = window.__css__
|
||||
const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default)
|
||||
app.config.errorHandler = e => console.error(e)
|
||||
app.mount('#app')`.trim()
|
||||
import { createApp as _createApp } from "vue"
|
||||
|
||||
if (window.__app__) {
|
||||
window.__app__.unmount()
|
||||
document.getElementById('app').innerHTML = ''
|
||||
}
|
||||
|
||||
document.getElementById('__sfc-styles').innerHTML = window.__css__
|
||||
const app = window.__app__ = _createApp(__modules__["${MAIN_FILE}"].default)
|
||||
app.config.errorHandler = e => console.error(e)
|
||||
app.mount('#app')`.trim()
|
||||
])
|
||||
} catch (e) {
|
||||
runtimeError.value = e.message
|
||||
runtimeError.value = (e as Error).message
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="preview-container" ref="container"></div>
|
||||
<Message :err="runtimeError" />
|
||||
<Message v-if="!runtimeError" :warn="runtimeWarning" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.preview-container,
|
||||
iframe {
|
||||
|
Loading…
Reference in New Issue
Block a user