diff --git a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts index 73e725692..34c16da71 100644 --- a/gui-js/apps/minsky-electron/src/app/events/electron.events.ts +++ b/gui-js/apps/minsky-electron/src/app/events/electron.events.ts @@ -150,6 +150,15 @@ ipcMain.handle( } ); +ipcMain.handle( + events.GODLEY_POPUP_UNDO, + async (event, payload: { itemId: string; changes: number }) => { + const godley = new GodleyIcon(minsky.namedItems.elem(payload.itemId)); + await godley.popup.undo(payload.changes); + event.sender.send(events.GODLEY_POPUP_REFRESH); + } +); + ipcMain.on( events.REFRESH_ALL_GODLEY_POPUPS, async () => { WindowManager.refreshAllGodleyPopups(); diff --git a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts index c837ce598..8950cfaf3 100644 --- a/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts +++ b/gui-js/apps/minsky-electron/src/app/managers/GodleyMenuManager.ts @@ -20,7 +20,7 @@ export class GodleyMenuManager { const godley = new GodleyIcon(minsky.namedItems.elem(itemInfo.id)); const menu = Menu.buildFromTemplate([ scope.getGodleyFileMenuItem(window, godley), - scope.getGodleyEditMenuItem(godley), + scope.getGodleyEditMenuItem(window, godley, itemInfo.id), scope.getGodleyViewMenuItem(window), scope.getGodleyOptionsMenuItem(window), new MenuItem({ @@ -188,7 +188,9 @@ export class GodleyMenuManager { } private static getGodleyEditMenuItem( - godley: GodleyIcon + window: Electron.BrowserWindow, + godley: GodleyIcon, + itemId: string ) { return new MenuItem({ label: 'Edit', @@ -196,12 +198,18 @@ export class GodleyMenuManager { { label: 'Undo', accelerator: 'CmdOrCtrl + z', - click: () => {godley.popup.undo(1);}, + click: () => window.webContents?.send(events.GODLEY_POPUP_UNDO_REQUEST, { + itemId, + changes: 1, + }), }, { label: 'Redo', accelerator: 'CmdOrCtrl + y', - click: () => {godley.popup.undo(-1);}, + click: () => window.webContents?.send(events.GODLEY_POPUP_UNDO_REQUEST, { + itemId, + changes: -1, + }), }, { label: 'Title', @@ -281,4 +289,3 @@ export class GodleyMenuManager { menu.popup(); } } - diff --git a/gui-js/libs/shared/src/lib/constants/constants.ts b/gui-js/libs/shared/src/lib/constants/constants.ts index 5471e1d10..b473fbd87 100644 --- a/gui-js/libs/shared/src/lib/constants/constants.ts +++ b/gui-js/libs/shared/src/lib/constants/constants.ts @@ -35,6 +35,8 @@ export const events = { GET_CURRENT_WINDOW: 'get-current-window', GET_PREFERENCES: 'get-preferences', GODLEY_POPUP_REFRESH: 'godley-popup-refresh', + GODLEY_POPUP_UNDO: 'godley-popup-undo', + GODLEY_POPUP_UNDO_REQUEST: 'godley-popup-undo-request', GODLEY_VIEW_MOUSEDOWN: 'godley-view-mousedown', GODLEY_VIEW_IMPORT_STOCK: 'godley-view-import-stock', HELP_FOR: 'help-for', diff --git a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts index c37714693..f01e2991d 100644 --- a/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts +++ b/gui-js/libs/ui-components/src/lib/godley-widget-view/godley-widget-view.component.ts @@ -104,6 +104,10 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn this.windowId = (await this.electronService.getCurrentWindow()).id; if(!this.canvasMode) { + this.electronService.on(events.GODLEY_POPUP_UNDO_REQUEST, async (e, payload) => { + await this.finishEditing(); + await this.electronService.invoke(events.GODLEY_POPUP_UNDO, payload); + }); this.electronService.on(events.GODLEY_POPUP_REFRESH, async e => { await this.hardRefresh(); }); @@ -256,6 +260,7 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn async onRowAdd(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.insertRow(i+3); await this.hardRefresh(); } @@ -268,24 +273,28 @@ export class GodleyWidgetViewComponent implements OnDestroy, OnInit, AfterViewIn async onRowMove(i, n) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.moveRow(i+2,n); await this.hardRefresh(); } async onColumnAdd(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.insertCol(i+2); await this.hardRefresh(); } async onColumnDelete(i) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.deleteCol(i+2); await this.hardRefresh(); } async onColumnMove(i, n) { await this.finishEditing(); + await this.namedItemSubCommand.pushHistory(); this.godleyIcon.table.moveCol(i+1,n); await this.hardRefresh(); } diff --git a/gui-js/package-lock.json b/gui-js/package-lock.json index 01b266512..339a74ff4 100644 --- a/gui-js/package-lock.json +++ b/gui-js/package-lock.json @@ -14170,21 +14170,6 @@ } } }, - "node_modules/html-encoding-sniffer/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -16826,21 +16811,6 @@ } } }, - "node_modules/jsdom/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/jsdom/node_modules/lru-cache": { "version": "11.5.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", @@ -24986,21 +24956,6 @@ } } }, - "node_modules/whatwg-url/node_modules/@noble/hashes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", - "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", @@ -25308,24 +25263,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index 7661d5f0b..189c792be 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -222,8 +222,15 @@ namespace minsky } void GodleyIcon::setCell(int row, int col, const string& newVal) + { + setCellImpl(row, col, newVal, true); + } + + void GodleyIcon::setCellImpl(int row, int col, const string& newVal, + bool recordHistory) { if (!table.cellInTable(row,col)) return; + if (recordHistory) popup.pushHistory(); // if this operation is clearing an initial condition cell, set it to 0 string& c=table.cell(row,col); if (newVal.empty() && !c.empty() && table.initialConditionRow(row)) @@ -241,6 +248,7 @@ namespace minsky { if (row>0 && row<=table.rows()) { + popup.pushHistory(); table.deleteRow(row-1); // if shared column data is deleted, remove it from the other tables too for (size_t col=1; col