From 6d412571c7c53b3ef7ce6b28326e5e4e8d8b88a5 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Thu, 3 Sep 2026 16:38:35 +0530 Subject: [PATCH 01/15] Broken: Basic Skeleton of DataFlow in a request --- .../clipboard/clipboard_message_handler.rs | 25 +++++++++++++------ .../messages/portfolio/portfolio_message.rs | 3 +++ .../portfolio/portfolio_message_handler.rs | 6 ++++- editor/src/node_graph_executor.rs | 5 ++++ editor/src/node_graph_executor/runtime.rs | 5 ++++ package-lock.json | 6 +++++ 6 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 package-lock.json diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index 73b72b7d6e8..e50888355d5 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -80,23 +80,31 @@ impl MessageHandler> for Clipboard } } ClipboardMessage::Write { content } => { - let text = match content { + match content { ClipboardContent::Svg(_) => { log::error!("SVG copying is not yet supported"); - return; + // Need to fix this. } ClipboardContent::Image { .. } => { log::error!("Image copying is not yet supported"); - return; } - ClipboardContent::Graphite(graphite) => format!("{CLIPBOARD_PREFIX}{graphite}"), - ClipboardContent::Text(text) => text, - }; - responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); + // THis is where the text/json is getting copied from + // Idea is to rather than copy it only as text, I want to + // move it to the node to get the svg preview and trhen from + // there send both the data as a single write item. + ClipboardContent::Graphite(graphite) => { + let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); + responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); + } + ClipboardContent::Text(text) => { + responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); + } + } } ClipboardMessage::CopyLayers => { if current_tool == &ToolType::Path { + log::debug!("Copying some path"); responses.add(PathToolMessage::Copy); return; } @@ -109,6 +117,7 @@ impl MessageHandler> for Clipboard responses.add(NodeGraphMessage::Copy); return; } + debug!("Copying something else"); let mut buffer = Vec::new(); @@ -209,12 +218,14 @@ impl MessageHandler> for Clipboard } if bytes_to_load.is_empty() { + log::debug!("Bytes to load are empty"); let mut items = items; items.extend(resources.into_iter().map(ClipboardItem::Resource)); if let Some(content) = serialize_clipboard(&items) { responses.add(ClipboardMessage::Write { content }); } } else { + log::debug!("Not empty instance of bytes"); // Load the embedded bytes from the resource storage, then write let load_handle = resource_storage.resources(); responses.add(async move { diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 8edca5950d3..6378508a78e 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -219,6 +219,9 @@ pub enum PortfolioMessage { /// New sizes for the children at that split node. sizes: Vec, }, + RequestSvgTextCopy { + graphite_json: String, + }, } /// Clone helper for the non-serializable `gdd` payload: a cloned mount message carries no `Gdd`. diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 847662e4f0f..f23d721139b 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -198,7 +198,8 @@ impl MessageHandler> for Portfolio } } - responses.add(PortfolioMessage::GarbageCollectResources); + // responses.add(PortfolioMessage::GarbageCollectResources); + // } PortfolioMessage::AutoSaveDocument { document_id } => { let validate = preferences.validate_storage_round_trip; @@ -1689,6 +1690,9 @@ impl MessageHandler> for Portfolio responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout); } } + PortfolioMessage::RequestSvgTextCopy { graphite_json } => { + self.executor.copy_svg_clipboard(graphite_json); + } } } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index a7a9abf6ebf..c8b5f2dd9dd 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -812,6 +812,11 @@ impl NodeGraphExecutor { Ok(()) } + + pub fn copy_svg_clipboard(&self, graphite_json: String) { + // TODO: See if to propagat ethe error here or move it up. + self.runtime_io.send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json)); + } } // TODO: Eventually remove this document upgrade code diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 8ce1ccea575..308dc7ffe2c 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -70,6 +70,7 @@ pub enum GraphRuntimeRequest { GraphUpdate(GraphUpdate), ExecutionRequest(ExecutionRequest), EditorPreferencesUpdate(EditorPreferences), + CopySvgTextClipboard(String), } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -182,6 +183,7 @@ impl NodeRuntime { } } GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request), + GraphRuntimeRequest::CopySvgTextClipboard(_) => todo!(), } } @@ -340,6 +342,9 @@ impl NodeRuntime { }); return texture; } + GraphRuntimeRequest::CopySvgTextClipboard(_) => { + todo!(); + } } } None diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..cabb54e6f1f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Graphite", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 2581e7d575ef3c4c23af098552088cbc9a3acf71 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 10:37:47 +0530 Subject: [PATCH 02/15] Broken: Added selected nodes data flow across runtime --- editor/src/messages/portfolio/portfolio_message_handler.rs | 7 ++++++- editor/src/node_graph_executor.rs | 6 +++--- editor/src/node_graph_executor/runtime.rs | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index f23d721139b..52b033727f5 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1691,7 +1691,12 @@ impl MessageHandler> for Portfolio } } PortfolioMessage::RequestSvgTextCopy { graphite_json } => { - self.executor.copy_svg_clipboard(graphite_json); + if let Some(active_document) = self.active_document() { + let selected_nodes: Vec = active_document.network_interface.shallowest_unique_layers(&[]).map(|layer| layer.to_node()).collect(); + self.executor.copy_svg_clipboard(graphite_json, selected_nodes); + } else { + self.executor.copy_svg_clipboard(graphite_json, Vec::new()); + } } } } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index c8b5f2dd9dd..c672aaa550a 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -52,6 +52,7 @@ pub enum NodeGraphUpdate { CompilationResponse(CompilationResponse), EyedropperPreview(Raster), NodeGraphUpdateMessage(NodeGraphUpdateMessage), + SvgTextCopyClipboard(String, String), } #[derive(Debug, Default)] @@ -813,9 +814,8 @@ impl NodeGraphExecutor { Ok(()) } - pub fn copy_svg_clipboard(&self, graphite_json: String) { - // TODO: See if to propagat ethe error here or move it up. - self.runtime_io.send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json)); + pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec) { + self.runtime_io.send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)); } } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 308dc7ffe2c..055f2ea05a7 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -109,6 +109,10 @@ impl InternalNodeGraphUpdateSender { fn send_eyedropper_preview(&self, raster: Raster) { self.0.send(NodeGraphUpdate::EyedropperPreview(raster)).expect("Failed to send response") } + + fn send_svg_text_clipboard(&self, svg_string: String, text_string: String) { + self.0.send(NodeGraphUpdate::SvgTextCopyClipboard(svg_string, text_string)).expect("Failed to send response") + } } impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender { From b897e65e6c86c03c2f7be40d713e1411570920e3 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 10:38:22 +0530 Subject: [PATCH 03/15] Fix: Extraction of nodes into svg and sending message --- editor/src/node_graph_executor/runtime.rs | 126 ++++++++++++++++++++-- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 055f2ea05a7..72258f25434 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -70,7 +70,7 @@ pub enum GraphRuntimeRequest { GraphUpdate(GraphUpdate), ExecutionRequest(ExecutionRequest), EditorPreferencesUpdate(EditorPreferences), - CopySvgTextClipboard(String), + CopySvgTextClipboard(String, Vec), } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -167,6 +167,7 @@ impl NodeRuntime { let mut graph = None; let mut eyedropper = None; let mut execution = None; + let mut svg_clipboard = None; for request in self.receiver.try_iter() { match request { GraphRuntimeRequest::GraphUpdate(_) => graph = Some(request), @@ -187,7 +188,7 @@ impl NodeRuntime { } } GraphRuntimeRequest::EditorPreferencesUpdate(_) => preferences = Some(request), - GraphRuntimeRequest::CopySvgTextClipboard(_) => todo!(), + GraphRuntimeRequest::CopySvgTextClipboard(..) => svg_clipboard = Some(request), } } @@ -199,7 +200,7 @@ impl NodeRuntime { eyedropper.render_config.pointer = execution.render_config.pointer; } - let requests = [preferences, graph, eyedropper, execution].into_iter().flatten(); + let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten(); for request in requests { match request { @@ -346,9 +347,91 @@ impl NodeRuntime { }); return texture; } - GraphRuntimeRequest::CopySvgTextClipboard(_) => { - todo!(); - } + GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => { + let mut combined_graphics = List::::new(); + + for monitor_node_path in &self.monitor_nodes { + // Skip inspect monitor node if active + if self.inspect_state.as_ref().is_some_and(|state| monitor_node_path.last().copied() == Some(state.monitor_node)) { + continue; + } + + let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { + continue; + }; + + if selected_node_ids.contains(&parent_network_node_id) { + // Introspect using the full monitor node path + if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) { + if let Some(io) = introspected_data.downcast_ref::>>() { + combined_graphics.extend(io.output.clone()); + } else if let Some(io) = introspected_data.downcast_ref::>>() { + combined_graphics.push(io.output.clone()); + } + } + } + } + + if combined_graphics.is_empty() { + self.sender.send_svg_text_clipboard(String::new(), text_string_clipboard); + return None; + } + + let bounds = graphene_std::renderer::graphic_list_bounding_box(&combined_graphics, DAffine2::IDENTITY); + let raw_bounds = match bounds { + RenderBoundingBox::Rectangle(bounds) if (bounds[1] - bounds[0]) != DVec2::ZERO => bounds, + _ => [DVec2::ZERO, DVec2::ONE], + }; + + let footprint = Footprint { + transform: DAffine2::from_translation(DVec2::new(raw_bounds[0].x, raw_bounds[0].y)), + resolution: UVec2::new((raw_bounds[1].x - raw_bounds[0].x).abs().ceil() as u32, (raw_bounds[1].y - raw_bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), + quality: RenderQuality::Full, + }; + + let render_params = RenderParams { + footprint, + thumbnail: false, + ..Default::default() + }; + let mut render = SvgRender::new(); + combined_graphics.render_svg(&mut render, &render_params); + render.format_svg(raw_bounds[0], raw_bounds[1]); + + self.sender.send_svg_text_clipboard(render.svg.to_svg_string(), text_string_clipboard); + } // // self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); + // // let mut uninspected_nodes = Vec::new(); + // // for monitor_node_path in &self.monitor_nodes { + // // if !self + // // .inspect_state + // // .as_ref() + // // .is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) + // // { + // // uninspected_nodes.push(monitor_node_path); + // // } + // // } + // for node in self.monitor_nodes.iter().flatten() { + // if selected_node_ids.contains(node) {} + // } + // for monitor_node_path in &self.monitor_nodes { + // // The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID + // let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { + // warn!("Monitor node has invalid node id"); + // continue; + // }; + // // Extract the monitor node's stored `Graphic` data + // let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else { + // // TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds) + // #[cfg(debug_assertions)] + // warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err()); + // continue; + // }; + // if let Some(io) = introspected_data.downcast_ref::>>() { + // let bounds = graphene_std::renderer::graphic_list_bounding_box(&io.output, DAffine2::IDENTITY); + // self.svg_clipboard_produce(text_string_clipboard, &io.output, bounds); + // } + // } + // } } } None @@ -538,6 +621,37 @@ impl NodeRuntime { *old_thumbnail_svg = new_thumbnail_svg; } } + + fn svg_clipboard_produce(&self, text_string_clipboard: String, graphic: &impl Render, bounds: RenderBoundingBox) { + let raw_bounds = match bounds { + RenderBoundingBox::Rectangle(bounds) if (bounds[1] - bounds[0]) != DVec2::ZERO => bounds, + _ => [DVec2::ZERO, DVec2::ONE], + }; + let bounds = expand_to_thumbnail_aspect(raw_bounds); + let new_thumbnail_svg = { + let footprint = Footprint { + transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), + resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32), + quality: RenderQuality::Full, + }; + + // Render the thumbnail from a `Graphic` into an SVG string + let render_params = RenderParams { + footprint, + thumbnail: true, + ..Default::default() + }; + let mut render = SvgRender::new(); + graphic.render_svg(&mut render, &render_params); + + // And give the SVG a viewbox and outer ... wrapper tag + render.format_svg(bounds[0], bounds[1]); + + render.svg + }; + + self.sender.send_svg_text_clipboard(new_thumbnail_svg.to_svg_string(), text_string_clipboard); + } } /// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the From eedb39ecb76b962ebd9e00eed70ba4fb9d814331 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 12:07:20 +0530 Subject: [PATCH 04/15] Fix: Removed comments and unused fn --- editor/src/node_graph_executor/runtime.rs | 65 +---------------------- 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 72258f25434..cf32b9d8e06 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -399,39 +399,7 @@ impl NodeRuntime { render.format_svg(raw_bounds[0], raw_bounds[1]); self.sender.send_svg_text_clipboard(render.svg.to_svg_string(), text_string_clipboard); - } // // self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); - // // let mut uninspected_nodes = Vec::new(); - // // for monitor_node_path in &self.monitor_nodes { - // // if !self - // // .inspect_state - // // .as_ref() - // // .is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) - // // { - // // uninspected_nodes.push(monitor_node_path); - // // } - // // } - // for node in self.monitor_nodes.iter().flatten() { - // if selected_node_ids.contains(node) {} - // } - // for monitor_node_path in &self.monitor_nodes { - // // The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID - // let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { - // warn!("Monitor node has invalid node id"); - // continue; - // }; - // // Extract the monitor node's stored `Graphic` data - // let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else { - // // TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds) - // #[cfg(debug_assertions)] - // warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err()); - // continue; - // }; - // if let Some(io) = introspected_data.downcast_ref::>>() { - // let bounds = graphene_std::renderer::graphic_list_bounding_box(&io.output, DAffine2::IDENTITY); - // self.svg_clipboard_produce(text_string_clipboard, &io.output, bounds); - // } - // } - // } + } } } None @@ -621,37 +589,6 @@ impl NodeRuntime { *old_thumbnail_svg = new_thumbnail_svg; } } - - fn svg_clipboard_produce(&self, text_string_clipboard: String, graphic: &impl Render, bounds: RenderBoundingBox) { - let raw_bounds = match bounds { - RenderBoundingBox::Rectangle(bounds) if (bounds[1] - bounds[0]) != DVec2::ZERO => bounds, - _ => [DVec2::ZERO, DVec2::ONE], - }; - let bounds = expand_to_thumbnail_aspect(raw_bounds); - let new_thumbnail_svg = { - let footprint = Footprint { - transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), - resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32), - quality: RenderQuality::Full, - }; - - // Render the thumbnail from a `Graphic` into an SVG string - let render_params = RenderParams { - footprint, - thumbnail: true, - ..Default::default() - }; - let mut render = SvgRender::new(); - graphic.render_svg(&mut render, &render_params); - - // And give the SVG a viewbox and outer ... wrapper tag - render.format_svg(bounds[0], bounds[1]); - - render.svg - }; - - self.sender.send_svg_text_clipboard(new_thumbnail_svg.to_svg_string(), text_string_clipboard); - } } /// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the From 32fbb6481308f67dccd06a34c1a25b909cfc4dda Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 13:41:21 +0530 Subject: [PATCH 05/15] Add: First support of svg compatible copy pasting --- editor/src/messages/frontend/frontend_message.rs | 4 ++++ editor/src/node_graph_executor.rs | 8 +++++++- frontend/src/managers/clipboard.ts | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 29daaa269b9..1336320e49d 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -153,6 +153,10 @@ pub enum FrontendMessage { TriggerClipboardWrite { content: String, }, + TriggerClipboardSvgWrite { + svg_string: String, + graphite_json: String, + }, TriggerSelectionRead { cut: bool, }, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index c672aaa550a..ea78e38ed92 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -467,6 +467,10 @@ impl NodeGraphExecutor { responses.add(EyedropperToolMessage::PreviewImage { data, width, height }); } NodeGraphUpdate::NodeGraphUpdateMessage(_) => {} + NodeGraphUpdate::SvgTextCopyClipboard(svg_string, graphite_json) => { + debug!("svg: {}", svg_string); + responses.add(FrontendMessage::TriggerClipboardSvgWrite { svg_string, graphite_json }); + } } } @@ -815,7 +819,9 @@ impl NodeGraphExecutor { } pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec) { - self.runtime_io.send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)); + self.runtime_io + .send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)) + .expect("Failed to send runtime request"); } } diff --git a/frontend/src/managers/clipboard.ts b/frontend/src/managers/clipboard.ts index 54dcda5eb2e..60ccecb145a 100644 --- a/frontend/src/managers/clipboard.ts +++ b/frontend/src/managers/clipboard.ts @@ -23,6 +23,20 @@ export function createClipboardManager(subscriptions: SubscriptionsRouter, edito subscriptions.subscribeFrontendMessage("TriggerSelectionWrite", async (data) => { insertAtCaret(data.content); }); + + subscriptions.subscribeFrontendMessage("TriggerClipboardSvgWrite", (data) => { + // Adopted from https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility + if (ClipboardItem.supports("image/svg+xml")) { + navigator.clipboard?.write?.([ + new ClipboardItem({ + "image/svg+xml": data.svg_string, + "text/plain": data.graphite_json, + }), + ]); + } else { + navigator.clipboard?.writeText?.(data.graphite_json); + } + }); } export function destroyClipboardManager() { @@ -30,6 +44,7 @@ export function destroyClipboardManager() { if (!subscriptions) return; subscriptions.unsubscribeFrontendMessage("TriggerClipboardWrite"); + subscriptions.unsubscribeFrontendMessage("TriggerClipboardSvgWrite"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionRead"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite"); } From a3dabc09627e379c0e736e335e60cd5eca93a202 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 14:52:23 +0530 Subject: [PATCH 06/15] Fix: Desktop copy intercept fix and unnecessary edits removal --- desktop/wrapper/src/intercept_frontend_message.rs | 3 +++ .../src/messages/clipboard/clipboard_message_handler.rs | 8 -------- .../src/messages/portfolio/portfolio_message_handler.rs | 3 +-- editor/src/node_graph_executor.rs | 1 - 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 6d13e4bc4c6..c2155077678 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -119,6 +119,9 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD FrontendMessage::TriggerClipboardWrite { content } => { dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content }); } + FrontendMessage::TriggerClipboardSvgWrite { graphite_json, .. } => { + dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content: graphite_json }); + } FrontendMessage::WindowPointerLock => { dispatcher.respond(DesktopFrontendMessage::PointerLock); } diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index e50888355d5..c8ba8544cdc 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -88,10 +88,6 @@ impl MessageHandler> for Clipboard ClipboardContent::Image { .. } => { log::error!("Image copying is not yet supported"); } - // THis is where the text/json is getting copied from - // Idea is to rather than copy it only as text, I want to - // move it to the node to get the svg preview and trhen from - // there send both the data as a single write item. ClipboardContent::Graphite(graphite) => { let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); @@ -104,7 +100,6 @@ impl MessageHandler> for Clipboard ClipboardMessage::CopyLayers => { if current_tool == &ToolType::Path { - log::debug!("Copying some path"); responses.add(PathToolMessage::Copy); return; } @@ -117,7 +112,6 @@ impl MessageHandler> for Clipboard responses.add(NodeGraphMessage::Copy); return; } - debug!("Copying something else"); let mut buffer = Vec::new(); @@ -218,14 +212,12 @@ impl MessageHandler> for Clipboard } if bytes_to_load.is_empty() { - log::debug!("Bytes to load are empty"); let mut items = items; items.extend(resources.into_iter().map(ClipboardItem::Resource)); if let Some(content) = serialize_clipboard(&items) { responses.add(ClipboardMessage::Write { content }); } } else { - log::debug!("Not empty instance of bytes"); // Load the embedded bytes from the resource storage, then write let load_handle = resource_storage.resources(); responses.add(async move { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 52b033727f5..3acb95b161d 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -198,8 +198,7 @@ impl MessageHandler> for Portfolio } } - // responses.add(PortfolioMessage::GarbageCollectResources); - // + responses.add(PortfolioMessage::GarbageCollectResources); } PortfolioMessage::AutoSaveDocument { document_id } => { let validate = preferences.validate_storage_round_trip; diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index ea78e38ed92..f55d5090f4c 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -468,7 +468,6 @@ impl NodeGraphExecutor { } NodeGraphUpdate::NodeGraphUpdateMessage(_) => {} NodeGraphUpdate::SvgTextCopyClipboard(svg_string, graphite_json) => { - debug!("svg: {}", svg_string); responses.add(FrontendMessage::TriggerClipboardSvgWrite { svg_string, graphite_json }); } } From c06bfacfb76230e7107b6153ede2c986fd5f2638 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Sun, 6 Sep 2026 14:59:29 +0530 Subject: [PATCH 07/15] Fix: removed package-lock.json --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index cabb54e6f1f..00000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Graphite", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From e96a66e991452c79b9fcd080c0d47e6621c12676 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 18:12:07 +0530 Subject: [PATCH 08/15] Fix: added todo and removed Svg branch from clipboard data type --- .../wrapper/src/intercept_frontend_message.rs | 1 + .../clipboard/clipboard_message_handler.rs | 28 ++++++++----------- .../src/messages/clipboard/utility_types.rs | 1 - 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index c2155077678..0e64204e6f2 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -120,6 +120,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content }); } FrontendMessage::TriggerClipboardSvgWrite { graphite_json, .. } => { + // TODO: Add support for svg after clipboard API change in desktop. dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content: graphite_json }); } FrontendMessage::WindowPointerLock => { diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index c8ba8544cdc..c5fc344dc4e 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -79,24 +79,18 @@ impl MessageHandler> for Clipboard responses.add(ClipboardMessage::CopyLayers); } } - ClipboardMessage::Write { content } => { - match content { - ClipboardContent::Svg(_) => { - log::error!("SVG copying is not yet supported"); - // Need to fix this. - } - ClipboardContent::Image { .. } => { - log::error!("Image copying is not yet supported"); - } - ClipboardContent::Graphite(graphite) => { - let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); - responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); - } - ClipboardContent::Text(text) => { - responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); - } + ClipboardMessage::Write { content } => match content { + ClipboardContent::Image { .. } => { + log::error!("Image copying is not yet supported"); } - } + ClipboardContent::Graphite(graphite) => { + let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); + responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); + } + ClipboardContent::Text(text) => { + responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); + } + }, ClipboardMessage::CopyLayers => { if current_tool == &ToolType::Path { diff --git a/editor/src/messages/clipboard/utility_types.rs b/editor/src/messages/clipboard/utility_types.rs index a4298222ca6..f47c14bc5f5 100644 --- a/editor/src/messages/clipboard/utility_types.rs +++ b/editor/src/messages/clipboard/utility_types.rs @@ -18,7 +18,6 @@ pub enum ClipboardContentRaw { pub enum ClipboardContent { Graphite(String), Text(String), - Svg(String), Image { data: Vec, width: u32, height: u32 }, } From 04dcb8494c11e3fbde966da3657774157f828e22 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 18:15:34 +0530 Subject: [PATCH 09/15] Fix: applied fix.patch to avoid message discarding --- .../clipboard/clipboard_message_handler.rs | 1 + .../document/document_message_handler.rs | 2 +- .../shapes/ellipse_shape.rs | 2 +- .../tool/tool_messages/artboard_tool.rs | 2 +- .../messages/tool/tool_messages/fill_tool.rs | 2 +- editor/src/node_graph_executor/runtime.rs | 2 +- editor/src/test_utils.rs | 17 +++++++++-------- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index c5fc344dc4e..5041830c884 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -524,6 +524,7 @@ mod test { .into_iter() .find_map(|message| match message { FrontendMessage::TriggerClipboardWrite { content } => Some(content), + FrontendMessage::TriggerClipboardSvgWrite { graphite_json, .. } => Some(graphite_json), _ => None, }) .expect("copying layers should write a payload to the clipboard") diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 5ed619fd178..d00edf33a2f 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -4317,7 +4317,7 @@ mod document_message_handler_tests { }) .await; - let instrumented = editor.eval_graph().await.unwrap(); + let (instrumented, _) = editor.eval_graph().await.unwrap(); // The emptiness guards keep these assertions honest: a wrong `Output` type on `grab_all_input` yields no records at all, which would otherwise pass without checking anything let base_lengths: Vec = instrumented diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index 5bc4745c879..945fd4a703a 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -64,7 +64,7 @@ mod test_ellipse { async fn get_ellipse(editor: &mut EditorTestUtils) -> Vec { let instrumented = match editor.eval_graph().await { - Ok(instrumented) => instrumented, + Ok((instrumented, _)) => instrumented, Err(e) => panic!("Failed to evaluate graph: {e}"), }; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 5ba52683c55..73451bba4b9 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -575,7 +575,7 @@ mod test_artboard { use graphene_std::list::List; async fn get_artboards(editor: &mut EditorTestUtils) -> List { - let instrumented = match editor.eval_graph().await { + let (instrumented, _) = match editor.eval_graph().await { Ok(instrumented) => instrumented, Err(e) => panic!("Failed to evaluate graph: {e}"), }; diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index e0755f0d6c8..272ad1c1255 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -269,7 +269,7 @@ mod test_fill { // The Fill tool writes solid colors, whose stored values the input monitor records as `Item` wires async fn get_fills(editor: &mut EditorTestUtils) -> Vec> { - let instrumented = match editor.eval_graph().await { + let (instrumented, _) = match editor.eval_graph().await { Ok(instrumented) => instrumented, Err(e) => panic!("Failed to evaluate graph: {e}"), }; diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index cf32b9d8e06..a9e03c91438 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -200,7 +200,7 @@ impl NodeRuntime { eyedropper.render_config.pointer = execution.render_config.pointer; } - let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten(); + let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten(); for request in requests { match request { diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 57c567b82c8..118bf2f83ae 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -33,9 +33,9 @@ impl EditorTestUtils { Self { editor, runtime } } - pub fn eval_graph<'a>(&'a mut self) -> impl std::future::Future> + 'a { + pub fn eval_graph<'a>(&'a mut self) -> impl std::future::Future), String>> + 'a { // An inner function is required since async functions in traits are a bit weird - async fn run<'a>(editor: &'a mut Editor, runtime: &'a mut NodeRuntime) -> Result { + async fn run<'a>(editor: &'a mut Editor, runtime: &'a mut NodeRuntime) -> Result<(Instrumented, Vec), String> { let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler; let document_id = portfolio.active_document_id.unwrap(); let (executor, documents) = (&mut portfolio.executor, &mut portfolio.documents); @@ -55,24 +55,25 @@ impl EditorTestUtils { if let Err(e) = editor.poll_node_graph_evaluation(&mut messages) { return Err(format!("Graph should render\n\n{e}")); } - let frontend_messages = messages.into_iter().flat_map(|message| editor.handle_message(message)); + let frontend_messages = messages.into_iter().flat_map(|message| editor.handle_message(message)).collect::>(); - for message in frontend_messages { + for message in &frontend_messages { message.check_node_graph_error(); } - Ok(instrumented) + Ok((instrumented, frontend_messages)) } run(&mut self.editor, &mut self.runtime) } pub async fn handle_message(&mut self, message: impl Into) -> Vec { - let frontend_messages_from_msg = self.editor.handle_message(message); + let mut frontend_messages_from_msg = self.editor.handle_message(message); // Required to process any buffered messages - if let Err(e) = self.eval_graph().await { - panic!("Failed to evaluate graph: {e}"); + match self.eval_graph().await { + Ok((_, new_messages)) => frontend_messages_from_msg.extend(new_messages), + Err(e) => panic!("Failed to evaluate graph: {e}"), } // Sweep the network interface's structural invariants so any desync fails at the message that caused it From 9ef419f7b4ce5d6d743c090aba6960ef14320bed Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 18:35:11 +0530 Subject: [PATCH 10/15] Fix: renamed TriggerClipboardSvgWrite to TriggerClipboardSvgAndJsonWrite --- desktop/wrapper/src/intercept_frontend_message.rs | 2 +- editor/src/messages/clipboard/clipboard_message_handler.rs | 2 +- editor/src/messages/frontend/frontend_message.rs | 2 +- editor/src/node_graph_executor.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 0e64204e6f2..c53efcd89d3 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -119,7 +119,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD FrontendMessage::TriggerClipboardWrite { content } => { dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content }); } - FrontendMessage::TriggerClipboardSvgWrite { graphite_json, .. } => { + FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => { // TODO: Add support for svg after clipboard API change in desktop. dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content: graphite_json }); } diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index 5041830c884..ff3e3f9087d 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -524,7 +524,7 @@ mod test { .into_iter() .find_map(|message| match message { FrontendMessage::TriggerClipboardWrite { content } => Some(content), - FrontendMessage::TriggerClipboardSvgWrite { graphite_json, .. } => Some(graphite_json), + FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => Some(graphite_json), _ => None, }) .expect("copying layers should write a payload to the clipboard") diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 1336320e49d..765366bf7da 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -153,7 +153,7 @@ pub enum FrontendMessage { TriggerClipboardWrite { content: String, }, - TriggerClipboardSvgWrite { + TriggerClipboardSvgAndJsonWrite { svg_string: String, graphite_json: String, }, diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index f55d5090f4c..b02af25ba54 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -468,7 +468,7 @@ impl NodeGraphExecutor { } NodeGraphUpdate::NodeGraphUpdateMessage(_) => {} NodeGraphUpdate::SvgTextCopyClipboard(svg_string, graphite_json) => { - responses.add(FrontendMessage::TriggerClipboardSvgWrite { svg_string, graphite_json }); + responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string, graphite_json }); } } } From 491a7ac0f5fe2ec9c4031e2553f66dd79c82634c Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 18:38:52 +0530 Subject: [PATCH 11/15] Fix: converted SvgTextCopyClipboard from typle to struct variant --- editor/src/node_graph_executor.rs | 4 ++-- editor/src/node_graph_executor/runtime.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index b02af25ba54..7f2cb5f481b 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -52,7 +52,7 @@ pub enum NodeGraphUpdate { CompilationResponse(CompilationResponse), EyedropperPreview(Raster), NodeGraphUpdateMessage(NodeGraphUpdateMessage), - SvgTextCopyClipboard(String, String), + SvgTextCopyClipboard { svg_string: String, graphite_json: String }, } #[derive(Debug, Default)] @@ -467,7 +467,7 @@ impl NodeGraphExecutor { responses.add(EyedropperToolMessage::PreviewImage { data, width, height }); } NodeGraphUpdate::NodeGraphUpdateMessage(_) => {} - NodeGraphUpdate::SvgTextCopyClipboard(svg_string, graphite_json) => { + NodeGraphUpdate::SvgTextCopyClipboard { svg_string, graphite_json } => { responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string, graphite_json }); } } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index a9e03c91438..67ae24be7d6 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -110,8 +110,8 @@ impl InternalNodeGraphUpdateSender { self.0.send(NodeGraphUpdate::EyedropperPreview(raster)).expect("Failed to send response") } - fn send_svg_text_clipboard(&self, svg_string: String, text_string: String) { - self.0.send(NodeGraphUpdate::SvgTextCopyClipboard(svg_string, text_string)).expect("Failed to send response") + fn send_svg_text_clipboard(&self, svg_string: String, graphite_json: String) { + self.0.send(NodeGraphUpdate::SvgTextCopyClipboard { svg_string, graphite_json }).expect("Failed to send response") } } From 3f07cbced2e160573bad0365630e8cd5c9c47506 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 19:03:05 +0530 Subject: [PATCH 12/15] Fix: Fix trigger name in clipboard.ts --- frontend/src/managers/clipboard.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/managers/clipboard.ts b/frontend/src/managers/clipboard.ts index 60ccecb145a..0ed845a7c7d 100644 --- a/frontend/src/managers/clipboard.ts +++ b/frontend/src/managers/clipboard.ts @@ -24,7 +24,7 @@ export function createClipboardManager(subscriptions: SubscriptionsRouter, edito insertAtCaret(data.content); }); - subscriptions.subscribeFrontendMessage("TriggerClipboardSvgWrite", (data) => { + subscriptions.subscribeFrontendMessage("TriggerClipboardSvgAndJsonWrite", (data) => { // Adopted from https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility if (ClipboardItem.supports("image/svg+xml")) { navigator.clipboard?.write?.([ @@ -44,7 +44,7 @@ export function destroyClipboardManager() { if (!subscriptions) return; subscriptions.unsubscribeFrontendMessage("TriggerClipboardWrite"); - subscriptions.unsubscribeFrontendMessage("TriggerClipboardSvgWrite"); + subscriptions.unsubscribeFrontendMessage("TriggerClipboardSvgAndJsonWrite"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionRead"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite"); } From be7c1436a3af3af709c9967368ec73eef68dbd3e Mon Sep 17 00:00:00 2001 From: VimYoung Date: Mon, 7 Sep 2026 21:22:56 +0530 Subject: [PATCH 13/15] Add: Fix network_interface to include TriggerClipboardSvgAndJsonWrite for fixing test --- .../portfolio/document/utility_types/network_interface.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 50a11fb0340..5b6a71bb9ad 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -127,6 +127,7 @@ mod network_interface_tests { .into_iter() .find_map(|msg| match msg { FrontendMessage::TriggerClipboardWrite { content } => Some(content), + FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => Some(graphite_json), _ => None, }) .expect("copy message should be dispatched"); From c54b027e94f25192ade34381a675bcd8d4e66668 Mon Sep 17 00:00:00 2001 From: VimYoung Date: Tue, 8 Sep 2026 17:20:50 +0530 Subject: [PATCH 14/15] Fix: merge text clipboard message and svgtext clipboard trigger into one --- desktop/wrapper/src/intercept_frontend_message.rs | 3 --- .../src/messages/clipboard/clipboard_message_handler.rs | 5 ++--- editor/src/messages/frontend/frontend_message.rs | 5 +---- .../portfolio/document/utility_types/network_interface.rs | 1 - editor/src/node_graph_executor.rs | 5 ++++- frontend/src/managers/clipboard.ts | 8 +------- 6 files changed, 8 insertions(+), 19 deletions(-) diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index c53efcd89d3..6cce36a2000 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -116,9 +116,6 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD FrontendMessage::TriggerClipboardRead => { dispatcher.respond(DesktopFrontendMessage::ClipboardRead); } - FrontendMessage::TriggerClipboardWrite { content } => { - dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content }); - } FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => { // TODO: Add support for svg after clipboard API change in desktop. dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content: graphite_json }); diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index ff3e3f9087d..a36d08daaa9 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -87,8 +87,8 @@ impl MessageHandler> for Clipboard let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); } - ClipboardContent::Text(text) => { - responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); + ClipboardContent::Text(graphite_json) => { + responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string: None, graphite_json }); } }, @@ -523,7 +523,6 @@ mod test { .await .into_iter() .find_map(|message| match message { - FrontendMessage::TriggerClipboardWrite { content } => Some(content), FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => Some(graphite_json), _ => None, }) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 765366bf7da..85462efc7f4 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -150,11 +150,8 @@ pub enum FrontendMessage { url: String, }, TriggerClipboardRead, - TriggerClipboardWrite { - content: String, - }, TriggerClipboardSvgAndJsonWrite { - svg_string: String, + svg_string: Option, graphite_json: String, }, TriggerSelectionRead { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 5b6a71bb9ad..f29c108a12c 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -126,7 +126,6 @@ mod network_interface_tests { let clipboard = frontend_messages .into_iter() .find_map(|msg| match msg { - FrontendMessage::TriggerClipboardWrite { content } => Some(content), FrontendMessage::TriggerClipboardSvgAndJsonWrite { graphite_json, .. } => Some(graphite_json), _ => None, }) diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 7f2cb5f481b..011a29b28df 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -468,7 +468,10 @@ impl NodeGraphExecutor { } NodeGraphUpdate::NodeGraphUpdateMessage(_) => {} NodeGraphUpdate::SvgTextCopyClipboard { svg_string, graphite_json } => { - responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { svg_string, graphite_json }); + responses.add(FrontendMessage::TriggerClipboardSvgAndJsonWrite { + svg_string: Some(svg_string), + graphite_json, + }); } } } diff --git a/frontend/src/managers/clipboard.ts b/frontend/src/managers/clipboard.ts index 0ed845a7c7d..831fdd89f9f 100644 --- a/frontend/src/managers/clipboard.ts +++ b/frontend/src/managers/clipboard.ts @@ -11,11 +11,6 @@ export function createClipboardManager(subscriptions: SubscriptionsRouter, edito subscriptionsRouter = subscriptions; editorWrapper = editor; - subscriptions.subscribeFrontendMessage("TriggerClipboardWrite", (data) => { - // If the Clipboard API is supported in the browser, copy text to the clipboard - navigator.clipboard?.writeText?.(data.content); - }); - subscriptions.subscribeFrontendMessage("TriggerSelectionRead", async (data) => { editor.readSelection(readAtCaret(data.cut), data.cut); }); @@ -26,7 +21,7 @@ export function createClipboardManager(subscriptions: SubscriptionsRouter, edito subscriptions.subscribeFrontendMessage("TriggerClipboardSvgAndJsonWrite", (data) => { // Adopted from https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility - if (ClipboardItem.supports("image/svg+xml")) { + if (ClipboardItem.supports("image/svg+xml") && data.svg_string !== undefined) { navigator.clipboard?.write?.([ new ClipboardItem({ "image/svg+xml": data.svg_string, @@ -43,7 +38,6 @@ export function destroyClipboardManager() { const subscriptions = subscriptionsRouter; if (!subscriptions) return; - subscriptions.unsubscribeFrontendMessage("TriggerClipboardWrite"); subscriptions.unsubscribeFrontendMessage("TriggerClipboardSvgAndJsonWrite"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionRead"); subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite"); From f85130e2e3d3437f64319ea881ad6180c2b56e8e Mon Sep 17 00:00:00 2001 From: VimYoung Date: Tue, 8 Sep 2026 18:23:11 +0530 Subject: [PATCH 15/15] Fix: function abstractions in runtime.rs --- editor/src/node_graph_executor/runtime.rs | 83 +++++++++---------- .../libraries/core-types/src/transform.rs | 8 ++ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 67ae24be7d6..e88fa4f8366 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -348,29 +348,7 @@ impl NodeRuntime { return texture; } GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => { - let mut combined_graphics = List::::new(); - - for monitor_node_path in &self.monitor_nodes { - // Skip inspect monitor node if active - if self.inspect_state.as_ref().is_some_and(|state| monitor_node_path.last().copied() == Some(state.monitor_node)) { - continue; - } - - let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { - continue; - }; - - if selected_node_ids.contains(&parent_network_node_id) { - // Introspect using the full monitor node path - if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) { - if let Some(io) = introspected_data.downcast_ref::>>() { - combined_graphics.extend(io.output.clone()); - } else if let Some(io) = introspected_data.downcast_ref::>>() { - combined_graphics.push(io.output.clone()); - } - } - } - } + let combined_graphics = self.collect_graphics(&selected_node_ids); if combined_graphics.is_empty() { self.sender.send_svg_text_clipboard(String::new(), text_string_clipboard); @@ -378,25 +356,16 @@ impl NodeRuntime { } let bounds = graphene_std::renderer::graphic_list_bounding_box(&combined_graphics, DAffine2::IDENTITY); - let raw_bounds = match bounds { + let final_bounds = match bounds { RenderBoundingBox::Rectangle(bounds) if (bounds[1] - bounds[0]) != DVec2::ZERO => bounds, _ => [DVec2::ZERO, DVec2::ONE], }; - let footprint = Footprint { - transform: DAffine2::from_translation(DVec2::new(raw_bounds[0].x, raw_bounds[0].y)), - resolution: UVec2::new((raw_bounds[1].x - raw_bounds[0].x).abs().ceil() as u32, (raw_bounds[1].y - raw_bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), - quality: RenderQuality::Full, - }; - - let render_params = RenderParams { - footprint, - thumbnail: false, - ..Default::default() - }; + let footprint = Footprint::from_bounds(final_bounds, RenderQuality::Full); + let render_params = RenderParams { footprint, ..Default::default() }; let mut render = SvgRender::new(); combined_graphics.render_svg(&mut render, &render_params); - render.format_svg(raw_bounds[0], raw_bounds[1]); + render.format_svg(final_bounds[0], final_bounds[1]); self.sender.send_svg_text_clipboard(render.svg.to_svg_string(), text_string_clipboard); } @@ -452,11 +421,7 @@ impl NodeRuntime { for monitor_node_path in &self.monitor_nodes { // Skip the inspect monitor node - if self - .inspect_state - .as_ref() - .is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) - { + if self.is_insepect_monitor_node(monitor_node_path) { continue; } @@ -558,11 +523,7 @@ impl NodeRuntime { }; let bounds = expand_to_thumbnail_aspect(raw_bounds); let new_thumbnail_svg = { - let footprint = Footprint { - transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), - resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32), - quality: RenderQuality::Full, - }; + let footprint = Footprint::from_bounds(bounds, RenderQuality::Full); // Render the thumbnail from a `Graphic` into an SVG string let render_params = RenderParams { @@ -589,6 +550,36 @@ impl NodeRuntime { *old_thumbnail_svg = new_thumbnail_svg; } } + + fn collect_graphics(&self, selected_node_ids: &Vec) -> List { + let mut combined_graphics = List::::new(); + for monitor_node_path in &self.monitor_nodes { + // Skip inspect monitor node if active + if self.is_insepect_monitor_node(monitor_node_path) { + continue; + } + + let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else { + continue; + }; + + if selected_node_ids.contains(&parent_network_node_id) { + // Introspect using the full monitor node path + if let Ok(introspected_data) = self.executor.introspect(monitor_node_path) + && let Some(io) = introspected_data.downcast_ref::>>() + { + combined_graphics.extend(io.output.clone()); + } else { + warn!("No graphic type is matched while extracting svg"); + } + } + } + combined_graphics + } + + fn is_insepect_monitor_node(&self, monitor_node_path: &Vec) -> bool { + self.inspect_state.as_ref().is_some_and(|state| monitor_node_path.last().copied() == Some(state.monitor_node)) + } } /// Returns the union of the artboards' clipping rectangles, used as the thumbnail bounds for an artboard layer so the diff --git a/node-graph/libraries/core-types/src/transform.rs b/node-graph/libraries/core-types/src/transform.rs index 7f2aa272a62..a6c6ce31130 100644 --- a/node-graph/libraries/core-types/src/transform.rs +++ b/node-graph/libraries/core-types/src/transform.rs @@ -189,6 +189,14 @@ impl Footprint { quality: RenderQuality::Full, }; + pub fn from_bounds(bounds: [DVec2; 2], quality: RenderQuality) -> Self { + Footprint { + transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)), + resolution: UVec2::new((bounds[1].x - bounds[0].x).abs().ceil() as u32, (bounds[1].y - bounds[0].y).abs().ceil() as u32).max(UVec2::ONE), + quality, + } + } + pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { let inverse = self.transform.inverse(); let res = self.resolution.as_dvec2();