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 @@