From 71d53095fbe46b594d51ab906d690a3227b47371 Mon Sep 17 00:00:00 2001 From: cha0s Date: Fri, 8 Apr 2022 17:15:23 -0500 Subject: [PATCH] feat: undo/redo --- build/flecks.yml | 2 +- packages/core/src/index.js | 32 +++++++++---- .../src/server/packets/decorators/action.js | 45 ++++++++++++++++++- packages/core/src/server/start-flush.js | 16 ++++--- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/build/flecks.yml b/build/flecks.yml index 7c3e78e..e6cf9d0 100644 --- a/build/flecks.yml +++ b/build/flecks.yml @@ -51,8 +51,8 @@ show: false quitOnClosed: false window: - - '@persea/core' - '...' + - '@persea/core' '@flecks/governor': {} '@flecks/react': providers: diff --git a/packages/core/src/index.js b/packages/core/src/index.js index daa1e5a..8a2fda6 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,4 +1,9 @@ -import {patchJsonResource, replaceResource} from '@avocado/resource/persea'; +import { + patchJsonResource, + redo, + replaceResource, + undo, +} from '@avocado/resource/persea'; import {Hooks} from '@flecks/core'; import Persea from './components/persea'; @@ -17,14 +22,23 @@ export default { '@flecks/react.roots': () => Persea, '@flecks/redux.effects': (flecks) => { const withSocket = (fn) => (...args) => fn(...args.concat(flecks.get('$flecks/socket.socket'))); - return { - [patchJsonResource]: withSocket((store, action, socket) => { - socket.send(['Action', action]); - }), - [replaceResource]: withSocket((store, action, socket) => { - socket.send(['Action', action]); - }), - }; + return Object.fromEntries( + [ + patchJsonResource, + redo, + replaceResource, + undo, + ] + .map((action) => [ + action, + withSocket((store, action, socket) => { + if (action.meta?.isRemote) { + return; + } + socket.send(['Action', action]); + }), + ]), + ); }, '@flecks/redux.slices': () => ({ project, diff --git a/packages/core/src/server/packets/decorators/action.js b/packages/core/src/server/packets/decorators/action.js index 26708a4..56a017d 100644 --- a/packages/core/src/server/packets/decorators/action.js +++ b/packages/core/src/server/packets/decorators/action.js @@ -1,4 +1,13 @@ -import {patchJsonResource, replaceResource} from '@avocado/resource/persea'; +import {readFile, writeFile} from 'fs/promises'; +import {join} from 'path'; + +import { + patchJsonResource, + redo, + replaceResource, + undo, +} from '@avocado/resource/persea'; +import {applyPatch, compare} from 'fast-json-patch'; export default (Action, flecks) => class ProjectAction extends Action { @@ -9,10 +18,44 @@ export default (Action, flecks) => class ProjectAction extends Action { flecks.get('$persea/core.patches').push(payload); break; } + case redo.toString(): { + const {project, uri} = payload; + const path = join(process.cwd(), 'projects', project, uri); + const {toBuffer, fromBuffer} = flecks.get('$avocado/resource/persea.controllers') + .find(({matcher}) => uri.match(matcher)); + const buffer = await readFile(path); + const json = fromBuffer(buffer, flecks); + if (json.history?.redo?.length) { + const patch = json.history.redo[json.history.redo.length - 1]; + socket.send(['Action', patchJsonResource({patch, project, uri}, true)]); + applyPatch(json, patch); + json.history.undo.push(compare(json, fromBuffer(buffer, flecks))); + json.history.redo.pop(); + await writeFile(path, toBuffer(json, flecks)); + } + break; + } case replaceResource.toString(): { flecks.get('$persea/core.replacements').push(payload); break; } + case undo.toString(): { + const {project, uri} = payload; + const path = join(process.cwd(), 'projects', project, uri); + const {toBuffer, fromBuffer} = flecks.get('$avocado/resource/persea.controllers') + .find(({matcher}) => uri.match(matcher)); + const buffer = await readFile(path); + const json = fromBuffer(buffer, flecks); + if (json.history?.undo?.length) { + const patch = json.history.undo[json.history.undo.length - 1]; + socket.send(['Action', patchJsonResource({patch, project, uri}, true)]); + applyPatch(json, patch); + json.history.redo.push(compare(json, fromBuffer(buffer, flecks))); + json.history.undo.pop(); + await writeFile(path, toBuffer(json, flecks)); + } + break; + } default: } return super.respond(packet, socket); diff --git a/packages/core/src/server/start-flush.js b/packages/core/src/server/start-flush.js index 75f2e24..528beda 100644 --- a/packages/core/src/server/start-flush.js +++ b/packages/core/src/server/start-flush.js @@ -1,11 +1,7 @@ -import fs from 'fs'; +import {readFile, writeFile} from 'fs/promises'; import {join} from 'path'; -import {promisify} from 'util'; -import {applyPatch} from 'fast-json-patch'; - -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); +import {applyPatch, compare} from 'fast-json-patch'; const startFlush = (flecks) => { const flushPatches = async () => { @@ -21,6 +17,7 @@ const startFlush = (flecks) => { patching[path] = new Promise((resolve) => { readFile(path).then((buffer) => { resolve({ + fromBuffer, toBuffer, json: fromBuffer(buffer, flecks), }); @@ -34,7 +31,12 @@ const startFlush = (flecks) => { await Promise.all( Object.entries(patching) .map(async ([path, promise]) => { - const {toBuffer, json} = await promise; + const {fromBuffer, toBuffer, json} = await promise; + const buffer = await readFile(path); + const undo = compare(json, fromBuffer(buffer, flecks)); + json.history = json.history || {redo: [], undo: []}; + json.history.redo = []; + json.history.undo.push(undo); return writeFile(path, toBuffer(json, flecks)); }), );