Skip to content

Commit 9613969

Browse files
committed
workflow(sfc-playground): share and download buttons
1 parent aa8bf1b commit 9613969

File tree

15 files changed

+231
-24
lines changed

15 files changed

+231
-24
lines changed

packages/global.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,10 @@ declare module '*.vue' {
2727

2828
}
2929
declare module '*?raw' {
30+
const content: string
31+
export default content
32+
}
3033

34+
declare module 'file-saver' {
35+
export function saveAs(blob: any, name: any): void
3136
}

packages/sfc-playground/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@
1919
"@vitejs/plugin-vue": "^1.2.0",
2020
"codemirror": "^5.60.0",
2121
"vite": "^2.1.3"
22+
},
23+
"dependencies": {
24+
"file-saver": "^2.0.5",
25+
"jszip": "^3.6.0"
2226
}
2327
}

packages/sfc-playground/src/App.vue

+11-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import Output from './output/Output.vue'
2222
<style>
2323
body {
2424
font-size: 13px;
25-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
26-
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
25+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
26+
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
2727
color: #444;
2828
margin: 0;
2929
background-color: #f8f8f8;
@@ -36,4 +36,12 @@ body {
3636
.wrapper {
3737
height: calc(100vh - var(--nav-height));
3838
}
39-
</style>
39+
40+
button {
41+
border: none;
42+
outline: none;
43+
cursor: pointer;
44+
margin: 0;
45+
background-color: transparent;
46+
}
47+
</style>

packages/sfc-playground/src/Header.vue

+62
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
11
<template>
22
<nav>
33
<h1>Vue SFC Playground</h1>
4+
5+
<button class="share" @click="copyLink">
6+
<svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
7+
<g fill="none" stroke="#626262" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
8+
<circle cx="18" cy="5" r="3"/>
9+
<circle cx="6" cy="12" r="3"/>
10+
<circle cx="18" cy="19" r="3"/>
11+
<path d="M8.59 13.51l6.83 3.98"/>
12+
<path d="M15.41 6.51l-6.82 3.98"/>
13+
</g>
14+
</svg>
15+
</button>
16+
17+
<button class="download" @click="downloadProject">
18+
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
19+
<g fill="#626262">
20+
<rect x="4" y="18" width="16" height="2" rx="1" ry="1"/>
21+
<rect x="3" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 5 18)"/>
22+
<rect x="17" y="17" width="4" height="2" rx="1" ry="1" transform="rotate(-90 19 18)"/>
23+
<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"/>
24+
</g>
25+
</svg>
26+
</button>
427
</nav>
528
</template>
629

30+
<script setup lang="ts">
31+
import { exportFiles } from './store'
32+
import { saveAs } from 'file-saver'
33+
34+
function copyLink() {
35+
navigator.clipboard.writeText(location.href)
36+
alert('Sharable URL has been copied to clipboard.')
37+
}
38+
39+
async function downloadProject() {
40+
const { default: JSZip } = await import('jszip')
41+
const zip = new JSZip()
42+
43+
// basic structure
44+
45+
// project src
46+
const src = zip.folder('src')!
47+
const files = exportFiles()
48+
for (const file in files) {
49+
src.file(file, files[file])
50+
}
51+
52+
const blob = await zip.generateAsync({ type: 'blob' })
53+
saveAs(blob, 'vue-project.zip')
54+
}
55+
</script>
56+
757
<style>
858
nav {
959
height: var(--nav-height);
@@ -20,4 +70,16 @@ h1 {
2070
line-height: var(--nav-height);
2171
font-weight: 500;
2272
}
73+
74+
.share {
75+
position: absolute;
76+
top: 14px;
77+
right: 56px;
78+
}
79+
80+
.download {
81+
position: absolute;
82+
top: 13px;
83+
right: 16px;
84+
}
2385
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { exportFiles } from '../store'
2+
import { saveAs } from 'file-saver'
3+
4+
import index from './template/index.html?raw'
5+
import main from './template/main.js?raw'
6+
import pkg from './template/package.json?raw'
7+
import config from './template/vite.config.js?raw'
8+
import readme from './template/README.md?raw'
9+
10+
export async function downloadProject() {
11+
const { default: JSZip } = await import('jszip')
12+
const zip = new JSZip()
13+
14+
// basic structure
15+
zip.file('index.html', index)
16+
zip.file('package.json', pkg)
17+
zip.file('vite.config.js', config)
18+
zip.file('README.md', readme)
19+
20+
// project src
21+
const src = zip.folder('src')!
22+
src.file('main.js', main)
23+
24+
const files = exportFiles()
25+
for (const file in files) {
26+
src.file(file, files[file])
27+
}
28+
29+
const blob = await zip.generateAsync({ type: 'blob' })
30+
saveAs(blob, 'vue-project.zip')
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Vite Vue Starter
2+
3+
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) v12+.
4+
5+
To start:
6+
7+
```sh
8+
npm install
9+
npm run dev
10+
11+
# if using yarn:
12+
yarn
13+
yarn dev
14+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite App</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.js"></script>
12+
</body>
13+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createApp } from 'vue'
2+
import App from './App.vue'
3+
4+
createApp(App).mount('#app')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "vite-vue-starter",
3+
"version": "0.0.0",
4+
"scripts": {
5+
"dev": "vite",
6+
"build": "vite build",
7+
"serve": "vite preview"
8+
},
9+
"dependencies": {
10+
"vue": "^3.0.9"
11+
},
12+
"devDependencies": {
13+
"@vitejs/plugin-vue": "^1.1.5",
14+
"@vue/compiler-sfc": "^3.0.9",
15+
"vite": "^2.1.3"
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from 'vite'
2+
import vue from '@vitejs/plugin-vue'
3+
4+
// https://vitejs.dev/config/
5+
export default defineConfig({
6+
plugins: [vue()]
7+
})

packages/sfc-playground/src/editor/FileSelector.vue

-5
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,9 @@ function doneAddFile() {
101101
padding-left: 0;
102102
}
103103
.add {
104-
margin: 0;
105104
font-size: 20px;
106105
font-family: var(--font-code);
107106
color: #999;
108-
border: none;
109-
outline: none;
110-
background-color: transparent;
111-
cursor: pointer;
112107
vertical-align: middle;
113108
margin-left: 6px;
114109
}

packages/sfc-playground/src/output/Output.vue

-5
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,10 @@ const mode = ref<Modes>('preview')
3838
background-color: white;
3939
}
4040
.tab-buttons button {
41-
margin: 0;
4241
font-size: 13px;
4342
font-family: var(--font-code);
44-
border: none;
45-
outline: none;
46-
background-color: transparent;
4743
padding: 8px 16px 6px;
4844
text-transform: uppercase;
49-
cursor: pointer;
5045
color: #999;
5146
box-sizing: border-box;
5247
}

packages/sfc-playground/src/output/srcdoc.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@
9797
['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach((level) => {
9898
const original = console[level];
9999
console[level] = (...args) => {
100-
if (String(args[0]).includes('You are running a development build of Vue')) {
100+
const msg = String(args[0])
101+
if (msg.includes('You are running a development build of Vue')) {
101102
return
102103
}
103104
const stringifiedArgs = stringify(args);

packages/sfc-playground/src/store.ts

+24-10
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {
77
rewriteDefault
88
} from '@vue/compiler-sfc'
99

10-
const STORAGE_KEY = 'vue-sfc-playground'
11-
1210
const welcomeCode = `
1311
<template>
1412
<h1>{{ msg }}</h1>
@@ -48,12 +46,19 @@ interface Store {
4846
errors: (string | Error)[]
4947
}
5048

51-
const savedFiles = localStorage.getItem(STORAGE_KEY)
52-
const files = savedFiles
53-
? JSON.parse(savedFiles)
54-
: {
55-
'App.vue': new File(MAIN_FILE, welcomeCode)
56-
}
49+
let files: Store['files'] = {}
50+
51+
const savedFiles = location.hash.slice(1)
52+
if (savedFiles) {
53+
const saved = JSON.parse(decodeURIComponent(savedFiles))
54+
for (const filename in saved) {
55+
files[filename] = new File(filename, saved[filename])
56+
}
57+
} else {
58+
files = {
59+
'App.vue': new File(MAIN_FILE, welcomeCode)
60+
}
61+
}
5762

5863
export const store: Store = reactive({
5964
files,
@@ -64,17 +69,26 @@ export const store: Store = reactive({
6469
errors: []
6570
})
6671

72+
watchEffect(() => compileFile(store.activeFile))
73+
6774
for (const file in store.files) {
6875
if (file !== MAIN_FILE) {
6976
compileFile(store.files[file])
7077
}
7178
}
7279

73-
watchEffect(() => compileFile(store.activeFile))
7480
watchEffect(() => {
75-
localStorage.setItem(STORAGE_KEY, JSON.stringify(store.files))
81+
location.hash = encodeURIComponent(JSON.stringify(exportFiles()))
7682
})
7783

84+
export function exportFiles() {
85+
const exported: Record<string, string> = {}
86+
for (const filename in store.files) {
87+
exported[filename] = store.files[filename].code
88+
}
89+
return exported
90+
}
91+
7892
export function setActive(filename: string) {
7993
store.activeFilename = filename
8094
}

0 commit comments

Comments
 (0)