diff --git a/interface/src/components/Graph/CommentBox.tsx b/interface/src/components/Graph/CommentBox.tsx
index b12c4e57..85570bfb 100644
--- a/interface/src/components/Graph/CommentBox.tsx
+++ b/interface/src/components/Graph/CommentBox.tsx
@@ -7,6 +7,11 @@ import { createEventListenerMap } from "@solid-primitives/event-listener";
import clsx from "clsx";
import { Show, createMemo, createRoot, createSignal, onMount } from "solid-js";
+import {
+ commentBoxToClipboardItem,
+ writeClipboardItemToClipboard,
+} from "@macrograph/clipboard";
+import { toast } from "solid-sonner";
import type { SelectionItem } from "../../actions";
import { useInterfaceContext } from "../../context";
import { useGraphContext } from "./Context";
@@ -160,6 +165,18 @@ export function CommentBox(props: Props) {
setEditing(true)}>
Rename
+ {
+ writeClipboardItemToClipboard(
+ commentBoxToClipboardItem(box(), (node) =>
+ interfaceCtx.nodeSizes.get(node),
+ ),
+ );
+ toast("Comment Box copied to clipboard");
+ }}
+ >
+ Copy
+
{
interfaceCtx.execute("deleteGraphItems", {
diff --git a/interface/src/components/Graph/Node.tsx b/interface/src/components/Graph/Node.tsx
index 55194b69..f89e9ffe 100644
--- a/interface/src/components/Graph/Node.tsx
+++ b/interface/src/components/Graph/Node.tsx
@@ -18,6 +18,11 @@ import clsx from "clsx";
import * as Solid from "solid-js";
import { createContext, useContext } from "solid-js";
+import {
+ nodeToClipboardItem,
+ writeClipboardItemToClipboard,
+} from "@macrograph/clipboard";
+import { toast } from "solid-sonner";
import { config } from "../../ConfigDialog";
import { useInterfaceContext } from "../../context";
import { isCtrlEvent } from "../../util";
@@ -312,6 +317,16 @@ export const Node = (props: Props) => {
)}
*/}
+ {
+ writeClipboardItemToClipboard(
+ nodeToClipboardItem(node()),
+ );
+ toast("Node copied to clipboard");
+ }}
+ >
+ Copy
+
{
interfaceCtx.execute("deleteGraphItems", {
diff --git a/interface/src/components/SchemaMenu/index.tsx b/interface/src/components/SchemaMenu/index.tsx
index 4016deb3..464f1106 100644
--- a/interface/src/components/SchemaMenu/index.tsx
+++ b/interface/src/components/SchemaMenu/index.tsx
@@ -42,6 +42,7 @@ interface Props {
extra?: { name?: string; defaultProperties?: Record },
): void | Promise;
onCreateCommentBox(): void;
+ onPasteClipboard(): void;
position: XY;
suggestion?: { pin: Pin };
onClose?(): void;
@@ -220,6 +221,12 @@ export function SchemaMenu(props: Props) {
>
Add Comment Box
+ {/*-
+ Paste from Clipboard
+
*/}
{(p) => {
diff --git a/interface/src/index.tsx b/interface/src/index.tsx
index 1b7cac5b..2ba14a2e 100644
--- a/interface/src/index.tsx
+++ b/interface/src/index.tsx
@@ -444,6 +444,35 @@ function ProjectInterface() {
ctx.setState({ status: "idle" });
});
}}
+ onPasteClipboard={async () => {
+ const item = deserializeClipboardItem(
+ await readFromClipboard(),
+ );
+
+ if (item.type === "selection") {
+ const graph = currentGraph();
+ if (!graph) return;
+
+ const { model, state } = graph;
+
+ const mousePosition = toGraphSpace(
+ {
+ x: data().position.x - (rootBounds.left ?? 0),
+ y: data().position.y - (rootBounds.top ?? 0) + 40,
+ },
+ ctx.graphBounds,
+ state,
+ );
+
+ ctx.execute("pasteGraphSelection", {
+ graphId: model.id,
+ mousePosition,
+ selection: item,
+ });
+
+ ctx.setState({ status: "idle" });
+ }
+ }}
onSchemaClicked={(schema, targetSuggestion, extra) => {
const graphId = data().graph.id;
ctx.batch(() => {
@@ -675,34 +704,7 @@ function createKeydownShortcuts(
case "KeyV": {
if (!isCtrlEvent(e)) return;
- let item = deserializeClipboardItem(await readFromClipboard());
-
- switch (item.type) {
- case "node": {
- item = {
- type: "selection",
- origin: item.node.position,
- nodes: [item.node],
- commentBoxes: [],
- connections: [],
- };
-
- break;
- }
- case "commentBox": {
- item = {
- type: "selection",
- origin: item.commentBox.position,
- nodes: item.nodes,
- commentBoxes: [item.commentBox],
- connections: item.connections,
- };
-
- break;
- }
- default:
- break;
- }
+ const item = deserializeClipboardItem(await readFromClipboard());
switch (item.type) {
case "selection": {
diff --git a/packages/clipboard/src/index.ts b/packages/clipboard/src/index.ts
index f15fcdaa..f560c20e 100644
--- a/packages/clipboard/src/index.ts
+++ b/packages/clipboard/src/index.ts
@@ -64,7 +64,36 @@ export function serializeClipboardItem(
}
export function deserializeClipboardItem(input: string) {
- return v.parse(ClipboardItem, JSON.parse(atob(input)));
+ let item = v.parse(ClipboardItem, JSON.parse(atob(input)));
+
+ switch (item.type) {
+ case "node": {
+ item = {
+ type: "selection",
+ origin: item.node.position,
+ nodes: [item.node],
+ commentBoxes: [],
+ connections: [],
+ };
+
+ break;
+ }
+ case "commentBox": {
+ item = {
+ type: "selection",
+ origin: item.commentBox.position,
+ nodes: item.nodes,
+ commentBoxes: [item.commentBox],
+ connections: item.connections,
+ };
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ return item;
}
export async function readFromClipboard() {
diff --git a/packages/packages/src/http.ts b/packages/packages/src/http.ts
index bc6efaab..1d554116 100644
--- a/packages/packages/src/http.ts
+++ b/packages/packages/src/http.ts
@@ -1,7 +1,7 @@
-import { JSONEnum, type JSONValue, jsToJSON, jsonToJS } from "@macrograph/json";
+import { JSONEnum, jsToJSON, jsonToJS } from "@macrograph/json";
import { Maybe, None } from "@macrograph/option";
-import { type Core, Package, type PropertyDef } from "@macrograph/runtime";
-import { type MapValue, t } from "@macrograph/typesystem";
+import { type Core, Package } from "@macrograph/runtime";
+import { t } from "@macrograph/typesystem";
import { ReactiveMap } from "@solid-primitives/map";
import { writeBinaryFile } from "@tauri-apps/api/fs";