Skip to content

Commit

Permalink
Implement our own clipboard (WIP #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandernst committed Sep 8, 2024
1 parent 52c43d8 commit e5d39ba
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 16 deletions.
1 change: 0 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/;\
Expand Down
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/;\
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:^",
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/handlers/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ export const copy = () => {

if(!collection.length) return;

keyboard.clipboard.copyElements(collection, engine.model);
keyboard.clipboard.copy(collection, engine.model);
}
2 changes: 1 addition & 1 deletion src/controllers/handlers/cut.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion src/controllers/handlers/paste.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
107 changes: 107 additions & 0 deletions src/core/ClipboardHandler.js
Original file line number Diff line number Diff line change
@@ -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());
}
}
11 changes: 2 additions & 9 deletions src/core/KeyboardHandler.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion src/engine/BaseEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class BaseEngine extends dia.Paper {
inertia: {
friction: 0.95,
},
borderless: false,
borderless: true,
padding: 5000,
cursor: "grab",
});
Expand Down

0 comments on commit e5d39ba

Please sign in to comment.