From e5d39ba8cae342ab2eff3abd8e19673de4db20a8 Mon Sep 17 00:00:00 2001 From: Alexander Nestorov Date: Sun, 8 Sep 2024 14:06:52 +0200 Subject: [PATCH] Implement our own clipboard (WIP #1) --- .gitlab-ci.yml | 1 - Makefile | 1 - package.json | 1 - src/controllers/handlers/copy.js | 2 +- src/controllers/handlers/cut.js | 2 +- src/controllers/handlers/paste.js | 2 +- src/core/ClipboardHandler.js | 107 ++++++++++++++++++++++++++++++ src/core/KeyboardHandler.js | 11 +-- src/engine/BaseEngine.js | 2 +- 9 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 src/core/ClipboardHandler.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4275a0b..be0577a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,7 +15,6 @@ stages: - rm -rf packages/joint/plus/jointplus;\ - unzip -q joint-plus.zip -d packages/joint/plus;\ - mkdir -p packages/joint/plus/src/packages;\ - - mv packages/joint/plus/jointplus/src/packages/joint-clipboard packages/joint/plus/src/packages/;\ - mv packages/joint/plus/jointplus/src/packages/joint-command-manager packages/joint/plus/src/packages/;\ - mv packages/joint/plus/jointplus/src/packages/joint-format-raster packages/joint/plus/src/packages/;\ - mv packages/joint/plus/jointplus/src/packages/joint-format-svg packages/joint/plus/src/packages/;\ diff --git a/Makefile b/Makefile index 430bd3c..3352900 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,6 @@ jjplus: rm -rf packages/joint/plus/jointplus;\ unzip -q joint-plus.zip -d packages/joint/plus;\ mkdir -p packages/joint/plus/src/packages;\ - mv packages/joint/plus/jointplus/src/packages/joint-clipboard packages/joint/plus/src/packages/;\ mv packages/joint/plus/jointplus/src/packages/joint-command-manager packages/joint/plus/src/packages/;\ mv packages/joint/plus/jointplus/src/packages/joint-format-raster packages/joint/plus/src/packages/;\ mv packages/joint/plus/jointplus/src/packages/joint-format-svg packages/joint/plus/src/packages/;\ diff --git a/package.json b/package.json index 3dfa1dd..afc5695 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "generate_dependencies_list": "yarn info --manifest --json | jq -c --arg exclusions $(cat src/ui/structure/About/dev.json | tr '\n' '|' | sed 's/|$//') '[inputs | select(.value | test($exclusions) | not) | { name: .value, version: .children.Version, license: .children.Manifest.License, website: .children.Manifest.Homepage }]' > src/ui/structure/About/deps.json" }, "dependencies": { - "@joint/clipboard": "workspace:^", "@joint/command-manager": "workspace:^", "@joint/core": "workspace:^", "@joint/format-raster": "workspace:^", diff --git a/src/controllers/handlers/copy.js b/src/controllers/handlers/copy.js index 7575965..5433009 100644 --- a/src/controllers/handlers/copy.js +++ b/src/controllers/handlers/copy.js @@ -24,5 +24,5 @@ export const copy = () => { if(!collection.length) return; - keyboard.clipboard.copyElements(collection, engine.model); + keyboard.clipboard.copy(collection, engine.model); } diff --git a/src/controllers/handlers/cut.js b/src/controllers/handlers/cut.js index 310adca..79f7a14 100644 --- a/src/controllers/handlers/cut.js +++ b/src/controllers/handlers/cut.js @@ -16,5 +16,5 @@ export const cut = () => { collection.filter(node => node.prop("removable")) ); - keyboard.clipboard.cutElements(collection, engine.model); + keyboard.clipboard.cut(collection, engine.model); } diff --git a/src/controllers/handlers/paste.js b/src/controllers/handlers/paste.js index 7a87530..4b5102d 100644 --- a/src/controllers/handlers/paste.js +++ b/src/controllers/handlers/paste.js @@ -9,7 +9,7 @@ export const paste = () => { // Paste the content of the cliboard logger.debug("Pasteing clipboard contents..."); - let elements = keyboard.clipboard.pasteCells(engine.model); + let elements = keyboard.clipboard.paste(engine.model); // Remove links that are linked to cells that weren't copied const nodes = elements.map( diff --git a/src/core/ClipboardHandler.js b/src/core/ClipboardHandler.js new file mode 100644 index 0000000..d05aee1 --- /dev/null +++ b/src/core/ClipboardHandler.js @@ -0,0 +1,107 @@ +import { dia, mvc } from '@joint/core'; +import { Runtime } from "@src/core/Runtime"; + +const LOCAL_STORAGE_KEY = "clipboard"; + +export class ClipboardHandler { + constructor() { + this.runtime = new Runtime(); + this.cb_collection = new mvc.Collection(); + } + + copy(selection, graph) { + let originalCells = []; + selection.toArray().forEach(cell => { + originalCells.push(cell, ...cell.getEmbeddedCells({ deep: true })); + }); + + const graphClone = graph.cloneSubgraph(originalCells, { deep: true }); + const cells = Object.values(graphClone).sort( + (a, b) => (a.attributes.z || 0) - (b.attributes.z || 0) + ); + + this.cb_collection.reset(cells); + + const payload = JSON.stringify(this.cb_collection.toJSON()); + localStorage.setItem(LOCAL_STORAGE_KEY, payload); + + return originalCells; + } + + cut(selection, graph) { + graph.startBatch('cut'); + this.copy(selection, graph).forEach(e => e.remove()); + graph.stopBatch('cut'); + } + + paste(graph) { + this.updateFromStorage(graph); + + const maxZIndex = graph.maxZIndex(); + const cells = this.cb_collection.toArray().map((cell, idx) => { + cell.set('z', maxZIndex + idx + 1); + + // It's necessary to unset the collection reference here. mvc.Collection + // adds collection attribute to every new model, except if the model + // already has one. The pasted elements needs to have collection + // attribute set to the Graph collection (not the Selection collection). + cell.collection = null; + + return cell; + }).sort( + (a, b) => (a.isLink() ? 2 : 1) - (b.isLink() ? 2 : 1) + ); + + // Move the cells to the center of the visible area (viewport) + // Step 1: Find the center of the viewport + const scroller = this.runtime.get("objects.engine.scroller"); + const viewportCenterP = scroller.getVisibleArea().center(); + + // Step 2: Find the cell located at the left-most coordinate + const theCell = cells.reduce((acc, cell) => { + if(!acc) return cell; + return acc.position().x < cell.position().x ? acc : cell; + }, null); + + // Step 3: Move the cell to the center of the viewport and get the + // displacement delta + const cellCurrentPosP = theCell.position(); + const bbox = theCell.getBBox(); + theCell.position( + viewportCenterP.x - (bbox.width / 2), + viewportCenterP.y - (bbox.height / 2), + ); + const cellNewPosP = theCell.position(); + const displacementDelta = { + x: cellNewPosP.x - cellCurrentPosP.x, + y: cellNewPosP.y - cellCurrentPosP.y, + } + + // Step 4: Move all the cells using the displacement delta + cells.forEach(cell => { + if(cell.id === theCell.id) return; + cell.translate(displacementDelta.x, displacementDelta.y); + }); + + graph.startBatch('paste'); + graph.addCells(cells); + graph.stopBatch('paste'); + + this.copy(this.cb_collection, graph); + + return cells; + } + + updateFromStorage(graph) { + const cells = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || null; + if (!cells) return; + + const graphJSON = { cells }; + // Note there is a `{ sort: false }` option passed to make sure + // the temporary graph does not change the order of cells. + // i.e. elements must stay before links + const cellNamespace = graph.get('cells').cellNamespace; + const tmpGraph = new dia.Graph([], { cellNamespace }).fromJSON(graphJSON, { sort: false, dry: true }); + this.cb_collection.reset(tmpGraph.getCells()); + } +} diff --git a/src/core/KeyboardHandler.js b/src/core/KeyboardHandler.js index 820e69b..dca8062 100644 --- a/src/core/KeyboardHandler.js +++ b/src/core/KeyboardHandler.js @@ -1,8 +1,8 @@ -import { Clipboard } from "@joint/clipboard"; import { Keyboard } from "@joint/keyboard"; import { Logger } from "@src/core/Logger"; import { Runtime } from "@src/core/Runtime"; import { EventBus } from "@src/core/EventBus"; +import { ClipboardHandler } from "@src/core/ClipboardHandler"; export const KB_SHORTCUTS = { NONE: 0b00000000, @@ -45,14 +45,7 @@ export class KeyboardHandler { return !keyboard.isConsumerElement(evt); }, }); - this.clipboard = new Clipboard({ - useLocalStorage: true, - deep: true, - translate: { - dx: 120, - dy: 20, - }, - }); + this.clipboard = new ClipboardHandler(); this.kb_shortcuts_filter = KB_SHORTCUTS.ALL; diff --git a/src/engine/BaseEngine.js b/src/engine/BaseEngine.js index 6ec3c8e..22536bb 100644 --- a/src/engine/BaseEngine.js +++ b/src/engine/BaseEngine.js @@ -160,7 +160,7 @@ export class BaseEngine extends dia.Paper { inertia: { friction: 0.95, }, - borderless: false, + borderless: true, padding: 5000, cursor: "grab", });