workflow(sfc-playground): share and download buttons
This commit is contained in:
parent
aa8bf1b7a3
commit
9613969ffc
7
packages/global.d.ts
vendored
7
packages/global.d.ts
vendored
@ -27,5 +27,10 @@ declare module '*.vue' {
|
||||
|
||||
}
|
||||
declare module '*?raw' {
|
||||
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module 'file-saver' {
|
||||
export function saveAs(blob: any, name: any): void
|
||||
}
|
||||
|
@ -19,5 +19,9 @@
|
||||
"@vitejs/plugin-vue": "^1.2.0",
|
||||
"codemirror": "^5.60.0",
|
||||
"vite": "^2.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ import Output from './output/Output.vue'
|
||||
<style>
|
||||
body {
|
||||
font-size: 13px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
color: #444;
|
||||
margin: 0;
|
||||
background-color: #f8f8f8;
|
||||
@ -36,4 +36,12 @@ body {
|
||||
.wrapper {
|
||||
height: calc(100vh - var(--nav-height));
|
||||
}
|
||||
</style>
|
||||
|
||||
button {
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,9 +1,59 @@
|
||||
<template>
|
||||
<nav>
|
||||
<h1>Vue SFC Playground</h1>
|
||||
|
||||
<button class="share" @click="copyLink">
|
||||
<svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="18" cy="5" r="3"/>
|
||||
<circle cx="6" cy="12" r="3"/>
|
||||
<circle cx="18" cy="19" r="3"/>
|
||||
<path d="M8.59 13.51l6.83 3.98"/>
|
||||
<path d="M15.41 6.51l-6.82 3.98"/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="download" @click="downloadProject">
|
||||
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
|
||||
<g fill="#626262">
|
||||
<rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
|
||||
<rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
|
||||
<rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
|
||||
<path d="M12 15a1 1 0 0 1-.58-.18l-4-2.82a1 1 0 0 1-.24-1.39a1 1 0 0 1 1.4-.24L12 12.76l3.4-2.56a1 1 0 0 1 1.2 1.6l-4 3a1 1 0 0 1-.6.2z"/><path d="M12 13a1 1 0 0 1-1-1V4a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { exportFiles } from './store'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
function copyLink() {
|
||||
navigator.clipboard.writeText(location.href)
|
||||
alert('Sharable URL has been copied to clipboard.')
|
||||
}
|
||||
|
||||
async function downloadProject() {
|
||||
const { default: JSZip } = await import('jszip')
|
||||
const zip = new JSZip()
|
||||
|
||||
// basic structure
|
||||
|
||||
// project src
|
||||
const src = zip.folder('src')!
|
||||
const files = exportFiles()
|
||||
for (const file in files) {
|
||||
src.file(file, files[file])
|
||||
}
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
saveAs(blob, 'vue-project.zip')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
height: var(--nav-height);
|
||||
@ -20,4 +70,16 @@ h1 {
|
||||
line-height: var(--nav-height);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.share {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 56px;
|
||||
}
|
||||
|
||||
.download {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 16px;
|
||||
}
|
||||
</style>
|
||||
|
31
packages/sfc-playground/src/download/download.ts
Normal file
31
packages/sfc-playground/src/download/download.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { exportFiles } from '../store'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
import index from './template/index.html?raw'
|
||||
import main from './template/main.js?raw'
|
||||
import pkg from './template/package.json?raw'
|
||||
import config from './template/vite.config.js?raw'
|
||||
import readme from './template/README.md?raw'
|
||||
|
||||
export async function downloadProject() {
|
||||
const { default: JSZip } = await import('jszip')
|
||||
const zip = new JSZip()
|
||||
|
||||
// basic structure
|
||||
zip.file('index.html', index)
|
||||
zip.file('package.json', pkg)
|
||||
zip.file('vite.config.js', config)
|
||||
zip.file('README.md', readme)
|
||||
|
||||
// project src
|
||||
const src = zip.folder('src')!
|
||||
src.file('main.js', main)
|
||||
|
||||
const files = exportFiles()
|
||||
for (const file in files) {
|
||||
src.file(file, files[file])
|
||||
}
|
||||
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
saveAs(blob, 'vue-project.zip')
|
||||
}
|
14
packages/sfc-playground/src/download/template/README.md
Normal file
14
packages/sfc-playground/src/download/template/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Vite Vue Starter
|
||||
|
||||
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+.
|
||||
|
||||
To start:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# if using yarn:
|
||||
yarn
|
||||
yarn dev
|
||||
```
|
13
packages/sfc-playground/src/download/template/index.html
Normal file
13
packages/sfc-playground/src/download/template/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
4
packages/sfc-playground/src/download/template/main.js
Normal file
4
packages/sfc-playground/src/download/template/main.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
17
packages/sfc-playground/src/download/template/package.json
Normal file
17
packages/sfc-playground/src/download/template/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "vite-vue-starter",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^1.1.5",
|
||||
"@vue/compiler-sfc": "^3.0.9",
|
||||
"vite": "^2.1.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
@ -101,14 +101,9 @@ function doneAddFile() {
|
||||
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;
|
||||
}
|
||||
|
@ -38,15 +38,10 @@ const mode = ref<Modes>('preview')
|
||||
background-color: white;
|
||||
}
|
||||
.tab-buttons button {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-code);
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
padding: 8px 16px 6px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -97,7 +97,8 @@
|
||||
['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach((level) => {
|
||||
const original = console[level];
|
||||
console[level] = (...args) => {
|
||||
if (String(args[0]).includes('You are running a development build of Vue')) {
|
||||
const msg = String(args[0])
|
||||
if (msg.includes('You are running a development build of Vue')) {
|
||||
return
|
||||
}
|
||||
const stringifiedArgs = stringify(args);
|
||||
|
@ -7,8 +7,6 @@ import {
|
||||
rewriteDefault
|
||||
} from '@vue/compiler-sfc'
|
||||
|
||||
const STORAGE_KEY = 'vue-sfc-playground'
|
||||
|
||||
const welcomeCode = `
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
@ -48,12 +46,19 @@ interface Store {
|
||||
errors: (string | Error)[]
|
||||
}
|
||||
|
||||
const savedFiles = localStorage.getItem(STORAGE_KEY)
|
||||
const files = savedFiles
|
||||
? JSON.parse(savedFiles)
|
||||
: {
|
||||
'App.vue': new File(MAIN_FILE, welcomeCode)
|
||||
}
|
||||
let files: Store['files'] = {}
|
||||
|
||||
const savedFiles = location.hash.slice(1)
|
||||
if (savedFiles) {
|
||||
const saved = JSON.parse(decodeURIComponent(savedFiles))
|
||||
for (const filename in saved) {
|
||||
files[filename] = new File(filename, saved[filename])
|
||||
}
|
||||
} else {
|
||||
files = {
|
||||
'App.vue': new File(MAIN_FILE, welcomeCode)
|
||||
}
|
||||
}
|
||||
|
||||
export const store: Store = reactive({
|
||||
files,
|
||||
@ -64,17 +69,26 @@ export const store: Store = reactive({
|
||||
errors: []
|
||||
})
|
||||
|
||||
watchEffect(() => compileFile(store.activeFile))
|
||||
|
||||
for (const file in store.files) {
|
||||
if (file !== MAIN_FILE) {
|
||||
compileFile(store.files[file])
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => compileFile(store.activeFile))
|
||||
watchEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(store.files))
|
||||
location.hash = encodeURIComponent(JSON.stringify(exportFiles()))
|
||||
})
|
||||
|
||||
export function exportFiles() {
|
||||
const exported: Record<string, string> = {}
|
||||
for (const filename in store.files) {
|
||||
exported[filename] = store.files[filename].code
|
||||
}
|
||||
return exported
|
||||
}
|
||||
|
||||
export function setActive(filename: string) {
|
||||
store.activeFilename = filename
|
||||
}
|
||||
|
37
yarn.lock
37
yarn.lock
@ -2840,6 +2840,11 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-saver@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||
|
||||
fill-range@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
|
||||
@ -3362,6 +3367,11 @@ ignore@^5.1.4:
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
|
||||
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
||||
|
||||
immediate@~3.0.5:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
@ -4345,6 +4355,16 @@ jstransformer@1.0.0:
|
||||
is-promise "^2.0.0"
|
||||
promise "^7.0.1"
|
||||
|
||||
jszip@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9"
|
||||
integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ==
|
||||
dependencies:
|
||||
lie "~3.3.0"
|
||||
pako "~1.0.2"
|
||||
readable-stream "~2.3.6"
|
||||
set-immediate-shim "~1.0.1"
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
@ -4480,6 +4500,13 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
lie@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||
@ -5227,6 +5254,11 @@ p-try@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
pako@~1.0.2:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
@ -6309,6 +6341,11 @@ set-blocking@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
set-immediate-shim@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
|
||||
integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
|
||||
|
||||
set-value@^2.0.0, set-value@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
|
||||
|
Loading…
Reference in New Issue
Block a user