workflow(sfc-playground): support multiple files
This commit is contained in:
@@ -1,17 +1,40 @@
|
||||
<template>
|
||||
<CodeMirror @change="onChange" :value="initialCode" />
|
||||
<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'
|
||||
import Message from '../Message.vue'
|
||||
import { store } from '../store'
|
||||
import { debounce } from '../utils'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
|
||||
const onChange = debounce((code: string) => {
|
||||
store.code = code
|
||||
store.activeFile.code = code
|
||||
}, 250)
|
||||
|
||||
const initialCode = store.code
|
||||
</script>
|
||||
const activeCode = ref(store.activeFile.code)
|
||||
const activeMode = computed(
|
||||
() => (store.activeFilename.endsWith('.js') ? 'javascript' : 'htmlmixed')
|
||||
)
|
||||
|
||||
watch(
|
||||
() => store.activeFilename,
|
||||
() => {
|
||||
activeCode.value = store.activeFile.code
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-container {
|
||||
height: calc(100% - 35px);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
118
packages/sfc-playground/src/editor/FileSelector.vue
Normal file
118
packages/sfc-playground/src/editor/FileSelector.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<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'
|
||||
|
||||
const pending = ref(false)
|
||||
const pendingFilename = ref('Comp.vue')
|
||||
|
||||
function startAddFile() {
|
||||
pending.value = true
|
||||
}
|
||||
|
||||
function cancelAddFile() {
|
||||
pending.value = false
|
||||
}
|
||||
|
||||
function focus({ el }: VNode) {
|
||||
(el as HTMLInputElement).focus()
|
||||
}
|
||||
|
||||
function doneAddFile() {
|
||||
const filename = pendingFilename.value
|
||||
|
||||
if (!filename.endsWith('.vue') && !filename.endsWith('.js')) {
|
||||
store.errors = [`Playground only supports .vue or .js files.`]
|
||||
return
|
||||
}
|
||||
|
||||
if (filename in store.files) {
|
||||
store.errors = [`File "${filename}" already exists.`]
|
||||
return
|
||||
}
|
||||
|
||||
store.errors = []
|
||||
pending.value = false
|
||||
addFile(filename)
|
||||
pendingFilename.value = 'Comp.vue'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.file-selector {
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: white;
|
||||
}
|
||||
.file {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-code);
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.file.active {
|
||||
color: var(--color-branding);
|
||||
border-bottom: 3px solid var(--color-branding);
|
||||
cursor: text;
|
||||
}
|
||||
.file span {
|
||||
display: inline-block;
|
||||
padding: 8px 10px 6px;
|
||||
}
|
||||
.file input {
|
||||
width: 80px;
|
||||
outline: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
padding: 4px 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.file .remove {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 12px;
|
||||
cursor: pointer;
|
||||
padding-left: 0;
|
||||
}
|
||||
.add {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-family: var(--font-code);
|
||||
color: #999;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.add:hover {
|
||||
color: var(--color-branding);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user