diff --git a/electron-preload.js b/electron-preload.js index 103c1500..ae606b8f 100644 --- a/electron-preload.js +++ b/electron-preload.js @@ -1,18 +1,38 @@ // preload.js for Electron app const { ipcRenderer } = require('electron'); +const fs = require('fs'); +const modpath = require('path'); + +// workspace root path +// TODO: reloading clears this +var wsroot = null; + +// from browser: read workspace file synchronously +window.getWorkspaceFile = function(path, filetype) { + if (wsroot == null) throw Error("no workspace root set"); + try { + // TODO: detect text or binary, or decide based on incbin vs. source file + var fullpath = modpath.join(wsroot, modpath.normalize(path)); + var data = fs.readFileSync(fullpath, {encoding:filetype=='text'?'utf8':'binary'}); + console.log("getWorkspaceFile", path, filetype); + // TODO: add to watched files + return data; + } catch (e) { + console.log(e); + return null; + } +} process.once('loaded', () => { - ipcRenderer.on('updateFile', (event, data) => { - console.log('updateFile', data); - data.cmd = 'updateFile'; - postMessage(data); + // from electron.js: set workspace root directory + ipcRenderer.on('setWorkspaceRoot', (event, data) => { + wsroot = data.root; + console.log('setWorkspaceRoot', wsroot); }); - window.addEventListener('message', event => { - const message = event.data; - console.log('MESSAGE', event, message); - //if (message.myTypeField === 'my-custom-message') { - //ipcRenderer.send('custom-message', message); - //} + // from electron.js: file changed + ipcRenderer.on('fileChanged', (event, data) => { + var path = data.path; + console.log('fileChanged', path); + window.reloadWorkspaceFile(path); }); - //ipcRenderer.send('hello', true); }); diff --git a/electron.js b/electron.js index a02a394d..6beead1f 100644 --- a/electron.js +++ b/electron.js @@ -1,50 +1,73 @@ -const { app, ipcMain, ipcRenderer, Menu, BrowserWindow } = require('electron') +const { app, dialog, ipcMain, ipcRenderer, Menu, BrowserWindow } = require('electron') const modpath = require('path') const fs = require('fs') const {URLSearchParams} = require('url') const isMac = process.platform === 'darwin' const chokidar = require('chokidar') +const Store = require('electron-store'); +const store = new Store(); +const KEY_lastWorkspaceFilePath = "lastWorkspaceFilePath"; // file watcher +// TODO: add workspace metadata for platform, ROM output, README, etc. class Workspace { constructor(directory, mainfile, wnd) { this.directory = directory; this.mainfile = mainfile; + wnd.webContents.send('setWorkspaceRoot', {root:this.directory}); this.watcher = chokidar.watch(modpath.join(directory, mainfile)); this.watcher.on('all', (event, path) => { console.log(event, path); switch (event) { case 'add': - /* - wnd.webContents.send('updateFile', { - main: modpath.relative(mainDirectoryPath, mainFilePath), - path: modpath.relative(mainDirectoryPath, path), - data: data, + case 'change': + wnd.webContents.send('fileChanged', { + path: modpath.relative(this.directory, path), }); - */ break; } }); + console.log("workspace opened", this.directory, this.mainfile); + } + close() { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + console.log("workspace closed", this.directory, this.mainfile); + } } } function openFile(path) { - var data = new Uint8Array(fs.readFileSync(path)); + if (!fs.existsSync(path)) { + dialog.showMessageBox({ + type: "error", + message: "File not found.", + detail: path + }); + return; + } var dirname = modpath.dirname(path); var filename = modpath.basename(path); - //var platform_id = 'vcs'; // TODO: which platform? - //var wnd = createWindow({repo_id:dirname, file:filename, platform_id:platform_id}); var wnd = BrowserWindow.getFocusedWindow(); + if (wnd.workspace) { wnd.workspace.close(); } var ws = new Workspace(dirname, filename, wnd); wnd.workspace = ws; + wnd.on('closed', () => { + ws.close(); + }); var qs = new URLSearchParams(); - qs.set('ws', dirname); + qs.set('electron_ws', 1); + qs.set('repo', dirname); qs.set('file', filename); - wnd.loadURL(`file://${__dirname}/electron.html?${qs}`); + wnd.loadURL(`file://${__dirname}/electron.html?${qs}`).then(() => { + wnd.webContents.send('setWorkspaceRoot', {root:dirname}); + app.addRecentDocument(path); + store.set(KEY_lastWorkspaceFilePath, path); + }); } function openFileDialog() { - const { dialog } = require('electron') dialog.showOpenDialog({ title: "Open File", properties: ['openFile','promptToCreate'], @@ -56,6 +79,17 @@ function openFileDialog() { }); } +function openDefaultFile() { + createWindow(); + /* + var lastfile = store.get(KEY_lastWorkspaceFilePath); + if (lastfile != null) { + openFile(lastfile); + } + */ +} + +/* function openWorkspace() { const { dialog } = require('electron') console.log(dialog.showOpenDialog({ @@ -64,6 +98,7 @@ function openWorkspace() { message: "Choose a directory that holds your source files.", })) } +*/ function openURL(url) { return async () => { @@ -94,13 +129,22 @@ function buildMenu() { { label: 'File', submenu: [ - /* { label: 'Open File...', click: openFileDialog, 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. + { + "label":"Open Recent", + "role":"recentdocuments", + "submenu":[ + { + "label":"Clear Recent", + "role":"clearrecentdocuments" + } + ] + }, { type: 'separator' }, isMac ? { role: 'close' } : { role: 'quit' } ] @@ -202,13 +246,15 @@ function createWindow () { preload: modpath.join(__dirname, './electron-preload.js'), nodeIntegration: false, enableRemoteModule: false, - contextIsolation: true, - sandbox: true, + contextIsolation: false, + sandbox: false, } }) // and load the index.html of the app. - win.loadFile('electron.html') + win.loadFile('electron.html', { + search: 'repo=/' + }) // Open the DevTools. //win.webContents.openDevTools() @@ -221,7 +267,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(createWindow) +app.whenReady().then(buildMenu).then(openDefaultFile) // 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 @@ -240,6 +286,10 @@ app.on('activate', () => { } }) +app.on('open-file', (event, path) => { + openFile(path); +}) + // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/package-lock.json b/package-lock.json index 80f1543d..d7077f05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -251,6 +251,11 @@ "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", "dev": true }, + "atomically": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.3.2.tgz", + "integrity": "sha512-MAiqx5ir1nOoMeG2vLXJnj4oFROJYB1hMqa2aAo6GQVIkPdkIcrq9W9SR0OaRtvEowO7Y2bsXqKFuDMTO4iOAQ==" + }, "author-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", @@ -590,6 +595,46 @@ "typedarray": "^0.0.6" } }, + "conf": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/conf/-/conf-7.1.1.tgz", + "integrity": "sha512-njOu3so+7zcR1oQzXKP0mpPMKRf+riaaWmxBUhgP/c9k32PuDX/SQ+N6cO6ZylY6NOZGPwgzicGxdlRGXUOkSQ==", + "requires": { + "ajv": "^6.12.2", + "atomically": "^1.3.1", + "debounce-fn": "^4.0.0", + "dot-prop": "^5.2.0", + "env-paths": "^2.2.0", + "json-schema-typed": "^7.0.3", + "make-dir": "^3.1.0", + "onetime": "^5.1.0", + "pkg-up": "^3.1.0", + "semver": "^7.3.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", @@ -642,6 +687,14 @@ "whatwg-url": "^7.0.0" } }, + "debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "requires": { + "mimic-fn": "^3.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -721,6 +774,14 @@ "webidl-conversions": "^4.0.2" } }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -945,6 +1006,22 @@ } } }, + "electron-store": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-6.0.0.tgz", + "integrity": "sha512-ujb0a/6gxMxb9vOQ2BjOehK9VCyq5OKvttekd9v/tohA9oBHnAdV+Vxu4eoRh+/F9ShPFhcvDZkMdqO5i+TXUw==", + "requires": { + "conf": "^7.1.1", + "type-fest": "^0.16.0" + }, + "dependencies": { + "type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==" + } + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -979,8 +1056,7 @@ "env-paths": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", - "dev": true + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, "error-ex": { "version": "1.3.2", @@ -1115,8 +1191,7 @@ "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -1199,7 +1274,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -1675,6 +1749,11 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, "is-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", @@ -1809,8 +1888,12 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", - "dev": true + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" + }, + "json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" }, "json-stringify-safe": { "version": "5.0.1", @@ -1929,7 +2012,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -2021,6 +2103,21 @@ "pkginfo": "~0.4.0" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -2055,6 +2152,11 @@ "mime-db": "~1.38.0" } }, + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -2318,6 +2420,21 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + } + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -2342,7 +2459,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -2351,7 +2467,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -2359,8 +2474,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pako": { "version": "1.0.11", @@ -2395,8 +2509,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -2451,6 +2564,14 @@ "dev": true, "optional": true }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + } + }, "pkginfo": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", @@ -2536,8 +2657,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", - "dev": true + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" }, "qs": { "version": "6.5.2", @@ -3156,7 +3276,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", - "dev": true, "requires": { "punycode": "^2.1.0" } diff --git a/package.json b/package.json index 8865d5bf..7f491885 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "license": "GPL-3.0", "dependencies": { "chokidar": "^3.4.1", + "electron-store": "^6.0.0", "jquery": "^3.5.1", "reflect-metadata": "^0.1.13" }, diff --git a/src/ide/project.ts b/src/ide/project.ts index 68df4ec1..0bde64ba 100644 --- a/src/ide/project.ts +++ b/src/ide/project.ts @@ -8,6 +8,10 @@ type BuildStatusCallback = (busy:boolean) => void; type IterateFilesCallback = (path:string, data:FileData) => void; type GetRemoteCallback = (path:string, callback:(data:FileData) => void, datatype:'text'|'arraybuffer') => any; +function isEmptyString(text : FileData) { + return typeof text == 'string' && text.trim && text.trim().length == 0; +} + export class CodeProject { filedata : {[path:string]:FileData} = {}; listings : CodeListingMap; @@ -26,6 +30,7 @@ export class CodeProject { callbackGetRemote : GetRemoteCallback; isCompiling : boolean = false; filename2path = {}; // map stripped paths to full paths + persistent : boolean = true; // set to true and won't modify store constructor(worker, platform_id:string, platform, store) { this.worker = worker; @@ -149,7 +154,7 @@ export class CodeProject { updateFileInStore(path:string, text:FileData) { // protect against accidential whole-file deletion - if (text instanceof Uint8Array || ((text).trim && (text).trim().length)) { + if (this.persistent && !isEmptyString(text)) { this.store.setItem(path, text); } } @@ -228,7 +233,7 @@ export class CodeProject { } else { if (data instanceof ArrayBuffer) data = new Uint8Array(data); // convert to typed array - console.log("GET",webpath,data.length,'bytes'); + console.log("read",webpath,data.length,'bytes'); this.filedata[path] = data; // do not update store, just cache addResult(path, data); } diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 5091fd3e..8d38a955 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -168,7 +168,13 @@ function unsetLastPreset() { function initProject() { current_project = new CodeProject(newWorker(), platform_id, platform, store); projectWindows = new ProjectWindows($("#workspace")[0] as HTMLElement, current_project); - current_project.callbackGetRemote = getWithBinary; + if (qs['electron_ws']) { + current_project.callbackGetRemote = getElectronFile; + current_project.persistent = false; + // TODO: save file when edited + } else { + current_project.callbackGetRemote = getWithBinary; + } current_project.callbackBuildResult = (result:WorkerResult) => { setCompileOutput(result); refreshWindowList(); @@ -1044,7 +1050,9 @@ async function updateSelector() { await populateFiles(sel, "Local Files", "", foundFiles); finishSelector(sel); } else { - sel.append($("