diff --git a/bust-cache.js b/bust-cache.js new file mode 100644 index 0000000..bc0e1ae --- /dev/null +++ b/bust-cache.js @@ -0,0 +1,6 @@ +const fs = require('fs'); +const { version } = require('./package.json'); + +let swFile = fs.readFileSync('./service-worker.js', 'utf-8'); +swFile = swFile.replace(/LATEST_VERSION = '[^']+';/, `LATEST_VERSION = '${version}';`); +fs.writeFileSync('./service-worker.js', swFile); diff --git a/package.json b/package.json index be14bae..8b6f007 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "duinoapp-client", - "version": "3.0.1", + "version": "3.1.0", "author": "Fraser Bullock", "license": "GPL-3.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "lint": "vue-cli-service lint", + "version": "node bust-cache.js && git add package.json service-worker.js" }, "dependencies": { "@feathersjs/feathers": "^4.5.8", @@ -18,8 +19,11 @@ "debounce-promise": "^3.1.2", "feathers-localstorage": "^5.1.1", "feathers-vuex": "^3.12.3", + "fflate": "^0.4.4", + "file-saver": "^2.0.5", "intel-hex": "^0.1.2", "lodash": "^4.17.15", + "monaco-editor-webpack-plugin": "<=1.8.2", "register-service-worker": "^1.7.1", "roboto-fontface": "*", "sass": "^1.26.7", diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 0000000..185910e --- /dev/null +++ b/service-worker.js @@ -0,0 +1,36 @@ +// service-worker.js + +workbox.core.setCacheNameDetails({ prefix: 'd4' }); + +// Do not touch this line +const LATEST_VERSION = '3.1.0'; + +self.addEventListener('activate', (event) => { + console.log(`%c ${LATEST_VERSION} `, 'background: #ddd; color: #0000ff'); + if (caches) { + caches.keys().then((arr) => { + arr.forEach((key) => { + if (key.indexOf('d4-precache') < -1) { + caches.delete(key).then(() => console.log(`%c Cleared ${key}`, 'background: #333; color: #ff0000')); + } else { + caches.open(key).then((cache) => { + cache.match('version').then((res) => { + if (!res) { + cache.put('version', new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION })); + } else if (res.statusText !== LATEST_VERSION) { + caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, 'background: #333; color: #ff0000')); + } else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, 'background: #333; color: #00ff00'); + }); + }); + } + }); + }); + } +}); + +workbox.skipWaiting(); +workbox.clientsClaim(); + +self.__precacheManifest = [].concat(self.__precacheManifest || []); +workbox.precaching.suppressWarnings(); +workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); diff --git a/src/App.vue b/src/App.vue index 626ef9e..488ed06 100644 --- a/src/App.vue +++ b/src/App.vue @@ -117,11 +117,16 @@ export default { else setTimeout(() => this.checkSerialReady(), 100); }, }, - mounted() { + async mounted() { this.checkSerialReady(); - this.$FeathersVuex.api.File.find(); - this.$FeathersVuex.api.Project.find(); - this.$FeathersVuex.api.Setting.find(); + this.$FeathersVuex.api.File.find({ query: { $limit: 9999999 } }); + this.$FeathersVuex.api.Project.find({ query: { $limit: 9999999 } }); + await this.$FeathersVuex.api.Setting.find({ query: { $limit: 9999999 } }); + const { Setting } = this.$FeathersVuex.api; + const { data } = Setting.findInStore({ query: { key: 'editor' } }); + // eslint-disable-next-line no-console + console.log(data[0]); + this.$vuetify.theme.dark = /(dark)|(black)/.test(data[0]?.value?.theme ?? ''); }, }; diff --git a/src/components/files/add-file.vue b/src/components/files/add-file.vue new file mode 100644 index 0000000..9188d2e --- /dev/null +++ b/src/components/files/add-file.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/src/components/files/add-folder.vue b/src/components/files/add-folder.vue new file mode 100644 index 0000000..fa8f0d7 --- /dev/null +++ b/src/components/files/add-folder.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/components/files/editor.vue b/src/components/files/editor.vue index 9686d71..8ec3092 100644 --- a/src/components/files/editor.vue +++ b/src/components/files/editor.vue @@ -1,38 +1,27 @@ diff --git a/src/plugins/bundler.js b/src/plugins/bundler.js new file mode 100644 index 0000000..6b884ba --- /dev/null +++ b/src/plugins/bundler.js @@ -0,0 +1,79 @@ +/* eslint-disable class-methods-use-this */ +// import moment from 'dayjs'; +import { + zip, unzip, strToU8, strFromU8, +} from 'fflate'; +import { saveAs } from 'file-saver'; + +const getType = (ref) => { + switch (ref.split('.').pop()) { + case 'ino': + return 'text/x-arduino'; + case 'c': + case 'cpp': + case 'h': + return 'text/x-c'; + case 'txt': + return 'text/plain'; + case 'md': + return 'text/markdown'; + default: + return ''; + } +}; + +class Bundler { + install(Vue) { + // eslint-disable-next-line no-param-reassign + Vue.$bundler = this; + // eslint-disable-next-line no-param-reassign + Vue.prototype.$bundler = this; + this.Vue = Vue; + } + + async createFile(project, fileObj, pathName, main) { + const { File } = this.Vue.$FeathersVuex.api; + const contentType = getType(pathName); + if (!contentType) return; + const file = new File({ + name: main ? `${project.ref}.ino` : pathName, + ref: main ? `${project.ref}/${project.ref}.ino` : `${project.ref}/${pathName}`, + body: strFromU8(fileObj), + contentType, + main, + projectId: project.uuid, + }); + await file.save(); + } + + async unzipFile(zipFile) { + const zipArr = new Uint8Array(await zipFile.arrayBuffer()); + return new Promise((resolve, reject) => unzip(zipArr, (err, res) => (err ? reject(err) : resolve(res)))); + } + + async importProjectFiles(project, unzipped, inoFile) { + const fileNames = Object.keys(unzipped); + const inoPath = inoFile.replace(/\/[^/]+.ino$/, ''); + await Promise.all( + fileNames + .filter((i) => !inoPath || i.indexOf(`${inoPath}/`) === 0) + .map((i) => this.createFile(project, unzipped[i], i.replace(`${inoPath}/`, ''), i === inoFile)), + ); + return project; + } + + async exportProject(project) { + const { File } = this.Vue.$FeathersVuex.api; + const { data: files } = File.findInStore({ query: { projectId: project.uuid } }); + // eslint-disable-next-line no-param-reassign + const fileObj = files.reduce((a, file) => { a[file.ref] = strToU8(file.body); return a; }, {}); + // eslint-disable-next-line no-console + console.log(files, fileObj); + const zipped = await new Promise((resolve, reject) => zip(fileObj, (err, res) => (err ? reject(err) : resolve(res)))); + // eslint-disable-next-line no-console + console.log(zipped); + saveAs(new Blob([zipped.buffer]), `${project.ref}.zip`); + } +} + +export default new Bundler(); diff --git a/src/plugins/index.js b/src/plugins/index.js index 70f30d1..0445f14 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -3,11 +3,13 @@ import './serial'; import FlagIcon from 'vue-flag-icon'; import CompileServer from './compile-server'; import CurrentStore from './current-store'; +import Bundler from './bundler'; Vue.use(FlagIcon); Vue.use(CurrentStore); Vue.use(CompileServer); +Vue.use(Bundler); // CompileServer.on('console.log', console.log); // CompileServer.on('console.error', console.error); diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js index e32aa92..81ff588 100644 --- a/src/registerServiceWorker.js +++ b/src/registerServiceWorker.js @@ -21,6 +21,9 @@ if (process.env.NODE_ENV === 'production') { }, updated() { console.log('New content is available; please refresh.'); + setTimeout(() => { + window.location.reload(true); + }, 3000); }, offline() { console.log('No internet connection found. App is running in offline mode.'); diff --git a/src/store/tools.js b/src/store/tools.js index eaaa016..7003894 100644 --- a/src/store/tools.js +++ b/src/store/tools.js @@ -34,5 +34,8 @@ export const settingsDefaults = { editor: { autoSaveInterval: 10, theme: 'vs', + fontSize: 14, + wordWrap: 'off', + scrollBeyondLastLine: true, }, }; diff --git a/src/views/Code.vue b/src/views/Code.vue index a738fa0..32affac 100644 --- a/src/views/Code.vue +++ b/src/views/Code.vue @@ -2,13 +2,39 @@
-