+<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">
</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;
-
<script setup lang="ts">
import { downloadProject } from './download/download'
import { setVersion, resetVersion } from './sfcCompiler'
-<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') {
}
</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;
-<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'
function boundSplit() {
const { split } = state
- return split < 20
- ? 20
- : split > 80
- ? 80
- : split
+ return split < 20 ? 20 : split > 80 ? 80 : split
}
let startPosition = 0
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)
}
}
}
</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;
width: 10px;
cursor: ew-resize;
}
-</style>
\ No newline at end of file
+</style>
-<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'
}, 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(
)
</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);
-<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')
}
function focus({ el }: VNode) {
- (el as HTMLInputElement).focus()
+ ;(el as HTMLInputElement).focus()
}
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
}
}
</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;
+<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">
</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);
-<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'
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)
}
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
}
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)
// 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
}
`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 {