]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
workflow(sfc-playground): share and download buttons
authorEvan You <yyx990803@gmail.com>
Mon, 29 Mar 2021 03:36:36 +0000 (23:36 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 29 Mar 2021 03:36:36 +0000 (23:36 -0400)
15 files changed:
packages/global.d.ts
packages/sfc-playground/package.json
packages/sfc-playground/src/App.vue
packages/sfc-playground/src/Header.vue
packages/sfc-playground/src/download/download.ts [new file with mode: 0644]
packages/sfc-playground/src/download/template/README.md [new file with mode: 0644]
packages/sfc-playground/src/download/template/index.html [new file with mode: 0644]
packages/sfc-playground/src/download/template/main.js [new file with mode: 0644]
packages/sfc-playground/src/download/template/package.json [new file with mode: 0644]
packages/sfc-playground/src/download/template/vite.config.js [new file with mode: 0644]
packages/sfc-playground/src/editor/FileSelector.vue
packages/sfc-playground/src/output/Output.vue
packages/sfc-playground/src/output/srcdoc.html
packages/sfc-playground/src/store.ts
yarn.lock

index e6239ffd05fa2f549c2b7043ddff72d1c139573e..8c6c57d8b7a65f2292d0e845b51e3667678466c0 100644 (file)
@@ -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
 }
index 5d3fed8f93a2bdcfcd4634578b60340ef5d7a07f..84e7f7d5be73fffceea246cb699c25b38b7a0628 100644 (file)
@@ -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"
   }
 }
index f39ec3ab1fc4278e4685bfd2ac711849119805fb..d2f1693212678d0725fefcc71cd8c5dd11bfd286 100644 (file)
@@ -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>
\ No newline at end of file
+
+button {
+  border: none;
+  outline: none;
+  cursor: pointer;
+  margin: 0;
+  background-color: transparent;
+}
+</style>
index 1a1ecc8f4c68f90ae94ec5570e72630cad29408d..bdcc02a3620f286a15d5f1d666f428fc4a6742f8 100644 (file)
@@ -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>
diff --git a/packages/sfc-playground/src/download/download.ts b/packages/sfc-playground/src/download/download.ts
new file mode 100644 (file)
index 0000000..91cdfd0
--- /dev/null
@@ -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')
+}
diff --git a/packages/sfc-playground/src/download/template/README.md b/packages/sfc-playground/src/download/template/README.md
new file mode 100644 (file)
index 0000000..39c47d2
--- /dev/null
@@ -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
+```
diff --git a/packages/sfc-playground/src/download/template/index.html b/packages/sfc-playground/src/download/template/index.html
new file mode 100644 (file)
index 0000000..030a6ff
--- /dev/null
@@ -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>
diff --git a/packages/sfc-playground/src/download/template/main.js b/packages/sfc-playground/src/download/template/main.js
new file mode 100644 (file)
index 0000000..01433bc
--- /dev/null
@@ -0,0 +1,4 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/packages/sfc-playground/src/download/template/package.json b/packages/sfc-playground/src/download/template/package.json
new file mode 100644 (file)
index 0000000..6bede6c
--- /dev/null
@@ -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"
+  }
+}
\ No newline at end of file
diff --git a/packages/sfc-playground/src/download/template/vite.config.js b/packages/sfc-playground/src/download/template/vite.config.js
new file mode 100644 (file)
index 0000000..315212d
--- /dev/null
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue()]
+})
index 23c6d4f363e9daee17efd9e533cf627355ec7765..639b0ec186095c19bb1941c975e8930c2c8f5220 100644 (file)
@@ -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;
 }
index 0027cdaed049bdb9465ba50b4e37094f4e572e8a..87b189094f2b62e35fc1210335e642ec25bc7abc 100644 (file)
@@ -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;
 }
index 063a522d456ab5c0d6d557607ff5edd6788a9366..e389e39aa3f3a3c20c9c4f070916e533a7d19364 100644 (file)
@@ -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);
index 0eb121b109c6266ceedea3634ca083172d181d6c..2328d52af62b37da1ccba20584b33b7f03f3f5d4 100644 (file)
@@ -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
 }
index 98966e2ae4a282694b833480f213bf2405708ec1..ecf5c50c062ad7b67eefbdb1e38c9e0cb3f5d5e9 100644 (file)
--- a/yarn.lock
+++ b/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"