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], () => { | ||||
| 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; | ||||
|  | ||||
| @ -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,7 +26,9 @@ let stopUpdateWatcher: WatchStopHandle | ||||
| onMounted(createSandbox) | ||||
| 
 | ||||
| // reset sandbox when import map changes | ||||
| watch(() => store.importMap, (importMap, prev) => { | ||||
| watch( | ||||
|   () => store.importMap, | ||||
|   (importMap, prev) => { | ||||
|     if (!importMap) { | ||||
|       if (prev) { | ||||
|         // import-map.json deleted | ||||
| @ -38,9 +39,7 @@ watch(() => store.importMap, (importMap, prev) => { | ||||
|     try { | ||||
|       const map = JSON.parse(importMap) | ||||
|       if (!map.imports) { | ||||
|       store.errors = [ | ||||
|         `import-map.json is missing "imports" field.` | ||||
|       ] | ||||
|         store.errors = [`import-map.json is missing "imports" field.`] | ||||
|         return | ||||
|       } | ||||
|       if (map.imports.vue) { | ||||
| @ -51,10 +50,11 @@ watch(() => store.importMap, (importMap, prev) => { | ||||
|       } | ||||
|       createSandbox() | ||||
|     } catch (e) { | ||||
|     store.errors = [e] | ||||
|       store.errors = [e as Error] | ||||
|       return | ||||
|     } | ||||
| }) | ||||
|   } | ||||
| ) | ||||
| 
 | ||||
| // reset sandbox when version changes | ||||
| watch(vueRuntimeUrl, createSandbox) | ||||
| @ -73,7 +73,9 @@ function createSandbox() { | ||||
|   } | ||||
| 
 | ||||
|   sandbox = document.createElement('iframe') | ||||
|   sandbox.setAttribute('sandbox', [ | ||||
|   sandbox.setAttribute( | ||||
|     'sandbox', | ||||
|     [ | ||||
|       'allow-forms', | ||||
|       'allow-modals', | ||||
|       'allow-pointer-lock', | ||||
| @ -81,13 +83,14 @@ function createSandbox() { | ||||
|       'allow-same-origin', | ||||
|       'allow-scripts', | ||||
|       'allow-top-navigation-by-user-activation' | ||||
|   ].join(' ')) | ||||
|     ].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,12 +110,14 @@ 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.*$/, '') + | ||||
|         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" | ||||
|   import { createApp as _createApp } from "vue" | ||||
|    | ||||
| if (window.__app__) { | ||||
|   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() | ||||
|   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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user