From 8b801e39dffae897b4df6fa1fec6300019923210 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Wed, 26 Aug 2020 20:42:18 -0500 Subject: [PATCH] electron: open workspace w/ metadata file, write files --- README.md | 2 +- doc/notes.txt | 7 +- electron-preload.js | 9 ++ electron.html | 23 +++++ electron.js | 208 +++++++++++++++++++++++++++++++------------- images/icon.png | Bin 0 -> 3768 bytes index.html | 14 +-- meta/electron.diff | 78 ++++++++++------- package-lock.json | 102 +++++++++++++--------- package.json | 11 +-- src/ide/project.ts | 11 ++- src/ide/ui.ts | 32 +++++-- 12 files changed, 336 insertions(+), 161 deletions(-) create mode 100644 images/icon.png diff --git a/README.md b/README.md index f8b8e39a..544b5b24 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Build Status](https://travis-ci.org/sehugg/8bitworkshop.svg?branch=master)](https://travis-ci.org/sehugg/8bitworkshop) -The latest release is online at http://8bitworkshop.com/ +The latest release is online at https://8bitworkshop.com/ ## Install diff --git a/doc/notes.txt b/doc/notes.txt index e2d1f202..25b66c4f 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -15,20 +15,15 @@ TODO: - watchpoints - step over (line, instruction) - slowdown beam for all platforms? -- show errors in list (maybe window list?) - - click to go to error - - what if error in include file you can't edit b/c it never appears? -- online help +- intro/help text for each platform - show self-modifying code insns left of editor - update Javatari version? (and others?) - sound mute? - $error updates source editor - online tools for music etc - text log debugging script -- intro/help text for each platform - vscode/atom extension? - click to break on raster position -- restructure src/ folders - debug bankswitching for funky formats - 'undefined' for bitmap replacer - requestInterrupt needs to be disabled after breakpoint? diff --git a/electron-preload.js b/electron-preload.js index 7f262505..b60016f5 100644 --- a/electron-preload.js +++ b/electron-preload.js @@ -24,9 +24,18 @@ process.once('loaded', () => { return null; } } + // from browser: put workspace file + window.putWorkspaceFile = function(path, data) { + if (wsroot == null) throw Error("no workspace root set"); + var fullpath = modpath.join(wsroot, modpath.normalize(path)); + var encoding = typeof data === 'string' ? 'utf8' : null; + fs.writeFileSync(fullpath, data, {encoding:encoding}); + } // from electron.js: set workspace root directory ipcRenderer.on('setWorkspaceRoot', (event, data) => { wsroot = data.root; + var binpath = modpath.join(wsroot, 'bin'); + if (!fs.existsSync(binpath)) fs.mkdirSync(binpath, {}); console.log('setWorkspaceRoot', wsroot); }); // from electron.js: file changed diff --git a/electron.html b/electron.html index f194a955..798eae63 100644 --- a/electron.html +++ b/electron.html @@ -14,6 +14,29 @@ body { + + + + diff --git a/electron.js b/electron.js index ace72272..57c7f7de 100644 --- a/electron.js +++ b/electron.js @@ -6,16 +6,33 @@ const isMac = process.platform === 'darwin' const chokidar = require('chokidar') const Store = require('electron-store'); const store = new Store(); -const KEY_lastWorkspaceFilePath = "lastWorkspaceFilePath"; +const WSMETA_FILENAME = "8bitworkshop.json" +const README_FILENAME = "README.md" + +// call updater +require('update-electron-app')() + +// show error dialog +function showError(msg, detail) { + msg = msg.message || msg; + dialog.showMessageBoxSync({ + type: "error", + message: msg, + detail: detail + }); +} // file watcher -// TODO: add workspace metadata for platform, ROM output, README, etc. class Workspace { - constructor(directory, mainfile, wnd) { + constructor(directory, meta, wnd) { this.directory = directory; - this.mainfile = mainfile; - wnd.webContents.send('setWorkspaceRoot', {root:this.directory}); - this.watcher = chokidar.watch(modpath.join(directory, mainfile)); + this.mainfile = meta.mainfile; + this.platform = meta.platform; + if (!this.mainfile) throw new Error(`The "mainfile" key is missing in ${WSMETA_FILENAME}.`) + if (!this.platform) throw new Error(`The "platform" key is missing in ${WSMETA_FILENAME}.`) + var mainfilepath = modpath.join(directory, this.mainfile); + if (!fs.existsSync(mainfilepath)) throw new Error(`The file "${mainfilepath}" is missing.`); + this.watcher = chokidar.watch(mainfilepath); this.watcher.on('all', (event, path) => { console.log(event, path); switch (event) { @@ -38,34 +55,51 @@ class Workspace { } } -function openFile(path, wnd) { - if (!fs.existsSync(path)) { - dialog.showMessageBox({ - type: "error", - message: "File not found.", - detail: path - }); - return; +function readWorkspaceMetadata(directory) { + // check README file + var readmepath = modpath.join(directory, README_FILENAME); + if (fs.existsSync(readmepath)) { + let readme = fs.readFileSync(readmepath, 'utf8'); + let sess = {}; + // check README for main file + const re8main = /8bitworkshop.com[^)]+file=([^)&]+)/; + m = re8main.exec(readme); + if (m && m[1]) { + sess.mainfile = m[1]; + } + // check README for proper platform + // unless we use githubURL= + const re8plat = /8bitworkshop.com[^)]+platform=([A-Za-z0-9._\-]+)/; + m = re8plat.exec(readme); + if (m) { + sess.platform = m[1]; + } + if (sess.mainfile != null && sess.platform != null) return sess; } - var dirname = modpath.dirname(path); - var filename = modpath.basename(path); - if (!wnd) wnd = BrowserWindow.getFocusedWindow(); - var ws = new Workspace(dirname, filename, wnd); + // check JSON file + var metapath = modpath.join(directory, WSMETA_FILENAME); + if (fs.existsSync(metapath)) { + return JSON.parse(fs.readFileSync(metapath, 'utf8')); + } +} + +function writeWorkspaceMetadata(directory, meta) { + var metapath = modpath.join(directory, WSMETA_FILENAME); + fs.writeFileSync(metapath, JSON.stringify(meta), 'utf8'); +} + +function openWorkspace(wnd, ws) { if (wnd.workspace) { wnd.workspace.close(); } wnd.workspace = ws; wnd.on('closed', () => { ws.close(); }); - openWorkspace(wnd, ws); - app.addRecentDocument(path); - store.set(KEY_lastWorkspaceFilePath, path); -} - -function openWorkspace(wnd, ws) { var qs = new URLSearchParams(); qs.set('electron_ws', 1); qs.set('repo', ws.directory); qs.set('file', ws.mainfile); + qs.set('platform', ws.platform); + console.log(qs); wnd.loadURL(`file://${__dirname}/electron.html?${qs}`).then(() => { wnd.webContents.send('setWorkspaceRoot', {root:ws.directory}); }); @@ -81,42 +115,88 @@ function reloadCurrentWindow() { } } -function openFileDialog() { +// TODO: better way to get this? +function getCurrentPlatform(wnd) { + var url = wnd.webContents.getURL(); + if (url != null) { + console.log(url); + var m = /platform=([^&]+)/.exec(url); + if (m) return m[1]; + } +} + +function openWorkspaceWindow(wspath) { + try { + // replace current window + var wnd = BrowserWindow.getFocusedWindow() || createWindow(); + // read metadata file + var meta = readWorkspaceMetadata(wspath); + // does it exist? + if (meta == null) { + // create a new workspace? + var cancel = dialog.showMessageBoxSync(wnd, { + type: 'question', + title: 'Create Workspace?', + message: `Project metadata not found. Create new ${getCurrentPlatform(wnd)||""} project in this directory?`, + detail: wspath, + buttons: ['Create', 'Cancel'], + }); + if (!cancel) { + var platform = getCurrentPlatform(wnd); + // choose main file + var files = dialog.showOpenDialogSync({ + message: `Choose the main file of your ${platform} project, or create one.`, + defaultPath: wspath, + properties: ['openFile','promptToCreate'], + }); + if (files != null) { + var mainfile = modpath.relative(wspath, files[0]); + // write new metadata + meta = { + platform: platform, + mainfile: mainfile, + }; + console.log(meta); + if (meta.platform == null) { + showError("Can't determine current platform."); + } else { + writeWorkspaceMetadata(wspath, meta); + openWorkspaceWindow(wspath); + } + } + } + } else { + console.log(meta); + var ws = new Workspace(wspath, meta, wnd); + openWorkspace(wnd, ws); + app.addRecentDocument(wspath); + } + } catch (e) { + showError(e); + } +} + +function openDefaultWorkspace() { + if (process.argv.length >= 3) { + openWorkspaceWindow(modpath.resolve(process.argv[2])); + } else { + createWindow(); + } +} + +function openWorkspaceDialog() { dialog.showOpenDialog({ - title: "Open File", - properties: ['openFile','promptToCreate'], + title: "Open Workspace", + properties: ['openDirectory'], + message: "Choose the directory that holds your source files.", }).then((rtn) => { if (!rtn.canceled && rtn.filePaths && rtn.filePaths.length > 0) { var path = rtn.filePaths[0]; - openFile(path); + openWorkspaceWindow(path); } }); } -function openDefaultFile() { - var wnd = createWindow(); - if (process.argv.length >= 3) { - openFile(process.argv[2], wnd); - } - /* TODO - var lastfile = store.get(KEY_lastWorkspaceFilePath); - if (lastfile != null) { - openFile(lastfile); - } - */ -} - -/* -function openWorkspace() { - const { dialog } = require('electron') - console.log(dialog.showOpenDialog({ - title: "Open Workspace", - properties: ['openDirectory','createDirectory','promptToCreate'], - message: "Choose a directory that holds your source files.", - })) -} -*/ - function openURL(url) { return async () => { const { shell } = require('electron') @@ -147,8 +227,13 @@ function buildMenu() { label: 'File', submenu: [ { - label: 'Open File...', - click: openFileDialog, + label: 'New Playground', + click: openDefaultWorkspace, + accelerator: 'CmdOrCtrl+N', + }, + { + label: 'Open Workspace...', + click: openWorkspaceDialog, accelerator: 'CmdOrCtrl+O', }, // When a file is requested from the recent documents menu, the open-file event of app module will be emitted for it. @@ -239,13 +324,18 @@ function buildMenu() { label: 'Latest News', click: openURL('https://8bitworkshop.com/blog/') }, + { + label: 'Report an Issue', + click: openURL('https://github.com/sehugg/8bitworkshop/issues/new') + }, + { type: 'separator' }, { label: 'Follow @8bitworkshop on Twitter', click: openURL('https://twitter.com/8bitworkshop') }, { - label: 'Report an Issue', - click: openURL('https://github.com/sehugg/8bitworkshop/issues/new') + label: 'Become a Patreon', + click: openURL('https://www.patreon.com/8bitworkshop') }, ] } @@ -286,7 +376,7 @@ function createWindow () { // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.whenReady().then(buildMenu).then(openDefaultFile) +app.whenReady().then(buildMenu).then(openDefaultWorkspace) // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits @@ -301,12 +391,12 @@ app.on('activate', () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { - createWindow() + openDefaultWorkspace() } }) app.on('open-file', (event, path) => { - openFile(path); + openWorkspaceWindow(path); }) // In this file you can include the rest of your app's specific main process diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b05d10afdc7ec328d1a4c75cde0758d1c8c9fd00 GIT binary patch literal 3768 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 70c29e49..de9a1934 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,7 @@ + @@ -36,16 +38,22 @@ - - - -- -- -- -- + -- - - ++Sentry.init({ ++ dsn: 'https://bf329df3d1b34afa9f5b5e8ecd80ad11@o320878.ingest.sentry.io/1813925', ++ beforeBreadcrumb(breadcrumb, hint) { ++ if (breadcrumb.category === 'xhr' && typeof breadcrumb.data.url === 'string') { ++ if (breadcrumb.data.url.startsWith('http')) return null; // discard external scripts ++ } ++ return breadcrumb; ++ }, ++ beforeSend(event, hint) { ++ const error = hint.originalException; ++ if (error instanceof EmuHalt) return null; // ignore EmuHalt ++ return event; ++ }, ++}); + -@@ -88,26 +38,6 @@ +- +- +- + + + +@@ -90,26 +61,6 @@
  • Add Include File...
  • Add Linked File...
  • @@ -91,7 +116,7 @@
  • Break Expression...
  • @@ -128,7 +153,7 @@ -@@ -258,39 +158,6 @@ +@@ -260,39 +181,6 @@ evals/clk @@ -168,7 +193,7 @@ -@@ -477,73 +344,6 @@ +@@ -479,73 +367,6 @@ @@ -242,16 +267,3 @@ -@@ -635,12 +435,5 @@ - startUI(); - - -- -- - - diff --git a/package-lock.json b/package-lock.json index 3d05a552..0aa37ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -268,9 +268,9 @@ "optional": true }, "@types/node": { - "version": "14.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", - "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", + "version": "14.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz", + "integrity": "sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A==", "dev": true }, "@types/sizzle": { @@ -606,9 +606,9 @@ }, "dependencies": { "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -678,9 +678,9 @@ } }, "chokidar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz", - "integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -886,6 +886,13 @@ "safe-buffer": "~5.1.1" } }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true, + "optional": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1123,9 +1130,9 @@ } }, "electron": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.1.2.tgz", - "integrity": "sha512-xEYadr3XqIqJ4ktBPo0lhzPdovv4jLCpiUUGc2M1frUhFhwqXokwhPaTUcE+zfu5+uf/ONDnQApwjzznBsRrgQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.2.1.tgz", + "integrity": "sha512-ZsetaQjXB8+9/EFW1FnfK4ukpkwXCxMEaiKiUZhZ0ZLFlLnFCpe0Bg4vdDf7e4boWGcnlgN1jAJpBw7w0eXuqA==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -1134,13 +1141,18 @@ }, "dependencies": { "@types/node": { - "version": "12.12.53", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.53.tgz", - "integrity": "sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ==", + "version": "12.12.54", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.54.tgz", + "integrity": "sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==", "dev": true } } }, + "electron-is-dev": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz", + "integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4=" + }, "electron-notarize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.0.0.tgz", @@ -1219,9 +1231,9 @@ } }, "electron-packager": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.0.0.tgz", - "integrity": "sha512-J0yQP7/fKPkjxo9Yz5+vsQVig0dBbSXW8LQYA1pvNMvi+bL00hfI2SAyORP6EU7XaeiXGUIBSG2Px01EkKfGCw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-15.1.0.tgz", + "integrity": "sha512-THNm4bz1DfvR9f0g51+NjuAYELflM8+1vhQ/iv/G8vyZNKzSMuFd5doobngQKq3rRsLdPNZVnGqDdgS884d7Og==", "dev": true, "requires": { "@electron/get": "^1.6.0", @@ -1240,7 +1252,7 @@ "rcedit": "^2.0.0", "resolve": "^1.1.6", "semver": "^7.1.3", - "yargs-parser": "^18.0.0" + "yargs-parser": "^19.0.1" }, "dependencies": { "extract-zip": { @@ -1268,9 +1280,9 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -1299,14 +1311,10 @@ "dev": true }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-19.0.4.tgz", + "integrity": "sha512-eXeQm7yXRjPFFyf1voPkZgXQZJjYfjgQUmGPbD2TLtZeIYzvacgWX7sQ5a1HsRgVP+pfKAkRZDNtTGev4h9vhw==", + "dev": true } } }, @@ -1790,6 +1798,14 @@ "assert-plus": "^1.0.0" } }, + "github-url-to-object": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/github-url-to-object/-/github-url-to-object-4.0.4.tgz", + "integrity": "sha512-1Ri1pR8XTfzLpbtPz5MlW/amGNdNReuExPsbF9rxLsBfO1GH9RtDBamhJikd0knMWq3RTTQDbTtw0GGvvEAJEA==", + "requires": { + "is-url": "^1.1.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1828,13 +1844,6 @@ "serialize-error": "^7.0.1" }, "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true, - "optional": true - }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -2165,6 +2174,11 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2934,8 +2948,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nested-error-stacks": { "version": "2.1.0", @@ -4140,6 +4153,17 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "update-electron-app": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/update-electron-app/-/update-electron-app-1.5.0.tgz", + "integrity": "sha512-g7noW9JfQ8Hwq6zw9lmZei+R/ikOIBcaZ04TbmIcU5zNfv23HkN80QLLAyiR/47KvfS4sjnh2/wuDq5nh8+0mQ==", + "requires": { + "electron-is-dev": "^0.3.0", + "github-url-to-object": "^4.0.4", + "is-url": "^1.2.4", + "ms": "^2.1.1" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index 773ec63a..4aa0f6ec 100644 --- a/package.json +++ b/package.json @@ -9,24 +9,25 @@ }, "license": "GPL-3.0", "dependencies": { - "chokidar": "^3.4.1", + "chokidar": "^3.4.2", "electron-store": "^6.0.0", "jquery": "^3.5.1", - "reflect-metadata": "^0.1.13" + "reflect-metadata": "^0.1.13", + "update-electron-app": "^1.5.0" }, "devDependencies": { "@types/bootbox": "^4.4.36", "@types/bootstrap": "^3.4.0", "@types/file-saver": "^2.0.1", "@types/jquery": "^3.5.1", - "@types/node": "^14.0.27", + "@types/node": "^14.6.2", "atob": "^2.1.x", "bootstrap": "^3.4.1", "bootstrap-tourist": "^0.2.1", "btoa": "^1.2.x", "clipboard": "^2.0.6", - "electron": "^9.1.2", - "electron-packager": "^15.0.0", + "electron": "^9.2.1", + "electron-packager": "^15.1.0", "file-saver": "^2.0.2", "jsdom": "^12.2.0", "jsfuzz": "^1.0.14", diff --git a/src/ide/project.ts b/src/ide/project.ts index 4482ab54..36b79b7e 100644 --- a/src/ide/project.ts +++ b/src/ide/project.ts @@ -19,17 +19,19 @@ export class CodeProject { mainPath : string; pendingWorkerMessages = 0; tools_preloaded = {}; - callbackBuildResult : BuildResultCallback; - callbackBuildStatus : BuildStatusCallback; worker : Worker; platform_id : string; platform : Platform; store : any; - callbackGetRemote : GetRemoteCallback; isCompiling : boolean = false; filename2path = {}; // map stripped paths to full paths persistent : boolean = true; // set to true and won't modify store + callbackBuildResult : BuildResultCallback; + callbackBuildStatus : BuildStatusCallback; + callbackGetRemote : GetRemoteCallback; + callbackStoreFile : IterateFilesCallback; + constructor(worker, platform_id:string, platform, store) { this.worker = worker; this.platform_id = platform_id; @@ -155,6 +157,9 @@ export class CodeProject { if (this.persistent && !isEmptyString(text)) { this.store.setItem(path, text); } + if (this.callbackStoreFile != null) { + this.callbackStoreFile(path, text); + } } // TODO: test duplicate files, local paths mixed with presets diff --git a/src/ide/ui.ts b/src/ide/ui.ts index a00895be..27423a81 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -114,7 +114,7 @@ function newWorker() : Worker { return new Worker("./src/worker/loader.js"); } -var hasLocalStorage : boolean = function() { +const hasLocalStorage : boolean = function() { try { const key = "__some_random_key_you_are_not_going_to_use__"; localStorage.setItem(key, key); @@ -151,7 +151,7 @@ function getCurrentPresetTitle() : string { function setLastPreset(id:string) { if (hasLocalStorage) { - if (repo_id && platform_id) + if (repo_id && platform_id && !isElectron) localStorage.setItem("__lastrepo_" + platform_id, repo_id); else localStorage.removeItem("__lastrepo_" + platform_id); @@ -170,10 +170,10 @@ function unsetLastPreset() { function initProject() { current_project = new CodeProject(newWorker(), platform_id, platform, store); projectWindows = new ProjectWindows($("#workspace")[0] as HTMLElement, current_project); - if (qs['electron_ws']) { - current_project.callbackGetRemote = getElectronFile; + if (isElectronWorkspace) { current_project.persistent = false; - // TODO: save file when edited + current_project.callbackGetRemote = getElectronFile; + current_project.callbackStoreFile = putWorkspaceFile; } else { current_project.callbackGetRemote = getWithBinary; } @@ -942,9 +942,11 @@ function _downloadROMImage(e) { var suffix = (platform.getROMExtension && platform.getROMExtension(current_output)) || "-" + getBasePlatform(platform_id) + ".bin"; saveAs(blob, prefix + suffix); - } else { + } else if (current_output.code != null) { var blob = new Blob([(current_output).code], {type: "text/plain"}); saveAs(blob, prefix + ".js"); + } else { + alertError(`The "${platform_id}" platform doesn't have downloadable ROMs.`); } } @@ -1003,7 +1005,7 @@ function populateExamples(sel) { } function populateRepos(sel) { - if (hasLocalStorage) { + if (hasLocalStorage && !isElectron) { var n = 0; var repos = getRepos(); if (repos) { @@ -1052,7 +1054,7 @@ async function updateSelector() { await populateFiles(sel, "Local Files", "", foundFiles); finishSelector(sel); } else { - if (!qs['electron_ws']) { + if (!isElectronWorkspace) { sel.append($("