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 00000000..b05d10af Binary files /dev/null and b/images/icon.png differ 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($("