Move errors to popup, remove tooltips

This commit is contained in:
Adam
2025-11-15 15:26:43 -08:00
parent 9bfa58b442
commit 6dfb685d17
6 changed files with 173 additions and 117 deletions

View File

@@ -2,7 +2,7 @@ use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphError, Transform
};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
@@ -289,6 +289,9 @@ pub enum FrontendMessage {
UpdateNodeGraphNodes {
nodes: Vec<FrontendNode>,
},
UpdateNodeGraphError {
error: Option<NodeGraphError>,
},
UpdateVisibleNodes {
nodes: Vec<NodeId>,
},

View File

@@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType};
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, NodeGraphError};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::network_interface::{
@@ -793,10 +793,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x
};
let context_menu_coordinates = ((node_graph_point.x + node_graph_shift.x) as i32, (node_graph_point.y + node_graph_shift.y) as i32);
let context_menu_coordinates = node_graph_point + node_graph_shift;
self.context_menu = Some(ContextMenuInformation {
context_menu_coordinates,
context_menu_coordinates: context_menu_coordinates.into(),
context_menu_data,
});
@@ -1218,9 +1217,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
let context_menu_coordinates = point + node_graph_shift;
self.context_menu = Some(ContextMenuInformation {
context_menu_coordinates: ((point.x + node_graph_shift.x) as i32, (point.y + node_graph_shift.y) as i32),
context_menu_coordinates: context_menu_coordinates.into(),
context_menu_data: ContextMenuData::CreateNode { compatible_type },
});
@@ -1640,6 +1640,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
responses.add(NodeGraphMessage::UpdateVisibleNodes);
let error = self.node_graph_error(network_interface, breadcrumb_network_path);
responses.add(FrontendMessage::UpdateNodeGraphError { error });
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports);
@@ -2511,8 +2513,6 @@ impl NodeGraphMessageHandler {
};
let mut nodes = Vec::new();
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
let node_id_path = [breadcrumb_network_path, &[node_id]].concat();
let primary_input_connector = InputConnector::node(node_id, 0);
let primary_input = if network_interface
@@ -2554,20 +2554,6 @@ impl NodeGraphMessageHandler {
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path);
let errors = network_interface
.resolved_types
.node_graph_errors
.iter()
.find(|error| error.node_path == node_id_path)
.map(|error| format!("{:?}", error.error.clone()))
.or_else(|| {
if network_interface.resolved_types.node_graph_errors.iter().any(|error| error.node_path.starts_with(&node_id_path)) {
Some("Node graph type error within this node".to_string())
} else {
None
}
});
nodes.push(FrontendNode {
id: node_id,
is_layer: network_interface
@@ -2586,7 +2572,6 @@ impl NodeGraphMessageHandler {
previewed,
visible,
locked,
errors,
});
}
@@ -2608,6 +2593,28 @@ impl NodeGraphMessageHandler {
Some(subgraph_names)
}
fn node_graph_error(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<NodeGraphError> {
let error = network_interface
.resolved_types
.node_graph_errors
.iter()
.filter(|error| error.node_path.starts_with(breadcrumb_network_path) && error.node_path.len() > breadcrumb_network_path.len())
.next()?;
let error_node = error.node_path[breadcrumb_network_path.len()];
let mut position = network_interface.position(&error_node, breadcrumb_network_path)?;
// Convert to graph space
position *= 24;
if network_interface.is_layer(&error_node, breadcrumb_network_path) {
position += IVec2::new(12, -12)
}
let error = if error.node_path.len() == breadcrumb_network_path.len() + 1 {
format!("{:?}", error.error)
} else {
"Node graph type error within this node".to_string()
};
Some(NodeGraphError { position: position.into(), error })
}
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
if !layers_panel_open {
return;

View File

@@ -1,4 +1,4 @@
use glam::IVec2;
use glam::{DVec2, IVec2};
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Type;
@@ -98,7 +98,6 @@ pub struct FrontendNode {
pub visible: bool,
pub locked: bool,
pub previewed: bool,
pub errors: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -172,7 +171,7 @@ pub enum ContextMenuData {
pub struct ContextMenuInformation {
// Stores whether the context menu is open and its position in graph coordinates
#[serde(rename = "contextMenuCoordinates")]
pub context_menu_coordinates: (i32, i32),
pub context_menu_coordinates: FrontendXY,
#[serde(rename = "contextMenuData")]
pub context_menu_data: ContextMenuData,
}
@@ -202,3 +201,28 @@ pub enum Direction {
Left,
Right,
}
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct NodeGraphError {
pub position: FrontendXY,
pub error: String,
}
/// Stores node graph coordinates which are then transformed in svelte based on the node graph transform
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendXY {
pub x: i32,
pub y: i32,
}
impl From<DVec2> for FrontendXY {
fn from(v: DVec2) -> Self {
FrontendXY { x: v.x as i32, y: v.y as i32 }
}
}
impl From<IVec2> for FrontendXY {
fn from(v: IVec2) -> Self {
FrontendXY { x: v.x as i32, y: v.y as i32 }
}
}

View File

@@ -180,10 +180,10 @@
return `Data Type: ${value.resolvedType}`;
}
function validTypesText(value: FrontendGraphInput): string {
const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None";
return `Valid Types:\n${validTypes}`;
}
// function validTypesText(value: FrontendGraphInput): string {
// const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None";
// return `Valid Types:\n${validTypes}`;
// }
function outputConnectedToText(output: FrontendGraphOutput): string {
if (output.connectedTo.length === 0) return "Connected to nothing";
@@ -257,6 +257,27 @@
</LayoutCol>
{/if}
{#if $nodeGraph.error}
<div class="node-error-container" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
<span
class="node-error faded"
style={`left: ${$nodeGraph.error.position.x}px;
top: ${$nodeGraph.error.position.y}px;`}
transition:fade={FADE_TRANSITION}
title=""
data-node-error>{$nodeGraph.error.error}</span
>
<span
class="node-error hover"
style={`left: ${$nodeGraph.error.position.x}px;
top: ${$nodeGraph.error.position.y}px;`}
transition:fade={FADE_TRANSITION}
title=""
data-node-error>{$nodeGraph.error.error}</span
>
</div>
{/if}
<!-- Click target debug visualizations -->
{#if $nodeGraph.clickTargets}
<div class="click-targets" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
@@ -492,7 +513,6 @@
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
{@const layerChainWidth = $nodeGraph.chainWidths.get(node.id) || 0}
{@const hasLeftInputWire = $nodeGraph.hasLeftInputWire.get(node.id) || false}
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
<div
class="layer"
class:selected={$nodeGraph.selected.includes(node.id)}
@@ -506,13 +526,8 @@
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--layer-area-width={layerAreaWidth}
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
data-node={node.id}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
{/if}
<div class="thumbnail">
{#if $nodeGraph.thumbnails.has(node.id)}
{@html $nodeGraph.thumbnails.get(node.id)}
@@ -528,7 +543,6 @@
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo.length > 0}
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
{#if node.primaryOutputConnectedToLayer}
@@ -549,9 +563,6 @@
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{/if}
{#if node.primaryInput?.connectedTo !== "nothing"}
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
{#if node.primaryInputConnectedToLayer}
@@ -574,7 +585,6 @@
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
{#if stackDataInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@@ -587,7 +597,7 @@
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel>{node.displayName}</TextLabel>
</div>
<div class="solo-drag-grip" title="Drag only this layer without pushing others outside the stack"></div>
<div class="solo-drag-grip" />
<IconButton
class="visibility"
data-visibility-button
@@ -644,7 +654,6 @@
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
{@const clipPathId = String(Math.random()).substring(2)}
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
<div
class="node"
class:selected={$nodeGraph.selected.includes(node.id)}
@@ -655,13 +664,8 @@
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
data-node={node.id}
>
{#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
<span class="node-error hover" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
{/if}
<!-- Primary row -->
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.reference)} />
@@ -673,7 +677,7 @@
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
{#each exposedInputsOutputs as [input, output]}
<div class={`secondary-row expanded ${input !== undefined ? "input" : "output"}`}>
<TextLabel tooltip={(input !== undefined ? `${input.name}\n\n${input.description}` : `${output.name}\n\n${output.description}`).trim()}>
<TextLabel>
{input !== undefined ? input.name : output.name}
</TextLabel>
</div>
@@ -692,7 +696,6 @@
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{#if node.primaryInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@@ -711,7 +714,6 @@
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@@ -733,7 +735,6 @@
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@@ -751,7 +752,6 @@
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@@ -776,7 +776,6 @@
</div>
<!-- Box selection widget -->
<!-- TODO: Make its initial corner stay put (in graph space) when panning around -->
{#if $nodeGraph.box}
<div
class="box-selection"
@@ -838,6 +837,72 @@
}
}
.node-error-container {
position: absolute;
z-index: 1;
.node-error {
position: absolute;
width: max-content;
white-space: pre-wrap;
max-width: 600px;
line-height: 18px;
color: var(--color-2-mildblack);
background: var(--color-error-red);
padding: 8px;
border-radius: 4px;
transition: opacity 0.2s;
opacity: 0.5;
transform: translateY(-100%);
// Tail
&::after {
content: "";
position: absolute;
left: 6px;
bottom: -8px;
width: 0;
height: 0;
border-style: solid;
border-width: 8px 6px 0 6px;
border-color: var(--color-error-red) transparent transparent transparent;
}
&.hover {
opacity: 0;
z-index: 1;
pointer-events: none;
}
&.faded:hover + .hover {
opacity: 1;
}
&.faded:hover {
z-index: 2;
opacity: 1;
-webkit-user-select: text;
user-select: text;
transition:
opacity 0.2s,
z-index 0s 0.2s;
&::selection {
background-color: var(--color-e-nearwhite);
// Target only Safari
@supports (background: -webkit-named-image(i)) {
& {
// Setting an alpha value opts out of Safari's "fancy" (but not visible on dark backgrounds) selection highlight rendering
// https://stackoverflow.com/a/71753552/775283
background-color: rgba(var(--color-e-nearwhite-rgb), calc(254 / 255));
}
}
}
}
}
}
.click-targets {
position: absolute;
pointer-events: none;
@@ -1017,68 +1082,6 @@
// backdrop-filter: blur(4px);
background: rgba(var(--color-0-black-rgb), 0.33);
.node-error {
position: absolute;
width: max-content;
white-space: pre-wrap;
max-width: 600px;
line-height: 18px;
color: var(--color-2-mildblack);
background: var(--color-error-red);
padding: 8px;
border-radius: 4px;
bottom: calc(100% + 12px);
z-index: -1;
transition: opacity 0.2s;
opacity: 0.5;
// Tail
&::after {
content: "";
position: absolute;
left: 6px;
bottom: -8px;
width: 0;
height: 0;
border-style: solid;
border-width: 8px 6px 0 6px;
border-color: var(--color-error-red) transparent transparent transparent;
}
&.hover {
opacity: 0;
z-index: 1;
pointer-events: none;
}
&.faded:hover + .hover {
opacity: 1;
}
&.faded:hover {
z-index: 2;
opacity: 1;
-webkit-user-select: text;
user-select: text;
transition:
opacity 0.2s,
z-index 0s 0.2s;
&::selection {
background-color: var(--color-e-nearwhite);
// Target only Safari
@supports (background: -webkit-named-image(i)) {
& {
// Setting an alpha value opts out of Safari's "fancy" (but not visible on dark backgrounds) selection highlight rendering
// https://stackoverflow.com/a/71753552/775283
background-color: rgba(var(--color-e-nearwhite-rgb), calc(254 / 255));
}
}
}
}
}
&::after {
content: "";
position: absolute;

View File

@@ -35,7 +35,7 @@ export class UpdateClickTargets extends JsMessage {
const ContextTupleToVec2 = Transform((data) => {
if (data.obj.contextMenuInformation === undefined) return undefined;
const contextMenuCoordinates = { x: data.obj.contextMenuInformation.contextMenuCoordinates[0], y: data.obj.contextMenuInformation.contextMenuCoordinates[1] };
const contextMenuCoordinates = data.obj.contextMenuInformation.contextMenuCoordinates;
let contextMenuData = data.obj.contextMenuInformation.contextMenuData;
if (contextMenuData.ToggleLayer !== undefined) {
contextMenuData = { nodeId: contextMenuData.ToggleLayer.nodeId, currentlyIsNode: contextMenuData.ToggleLayer.currentlyIsNode };
@@ -94,6 +94,15 @@ export class UpdateNodeGraphNodes extends JsMessage {
readonly nodes!: FrontendNode[];
}
export class UpdateNodeGraphError extends JsMessage {
readonly error!: NodeGraphError | undefined;
}
export class NodeGraphError {
readonly position!: XY;
readonly error!: string;
}
export class UpdateVisibleNodes extends JsMessage {
readonly nodes!: bigint[];
}
@@ -1691,6 +1700,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateMouseCursor,
UpdateNodeGraphControlBarLayout,
UpdateNodeGraphNodes,
UpdateNodeGraphError,
UpdateNodeGraphSelection,
UpdateNodeGraphTransform,
UpdateNodeGraphWires,

View File

@@ -1,6 +1,7 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import type { NodeGraphError } from "@graphite/messages";
import {
type Box,
type FrontendClickTargets,
@@ -25,6 +26,7 @@ import {
UpdateNodeGraphTransform,
UpdateNodeThumbnail,
UpdateWirePathInProgress,
UpdateNodeGraphError,
} from "@graphite/messages";
export function createNodeGraphState(editor: Editor) {
@@ -32,6 +34,7 @@ export function createNodeGraphState(editor: Editor) {
box: undefined as Box | undefined,
clickTargets: undefined as FrontendClickTargets | undefined,
contextMenuInformation: undefined as ContextMenuInformation | undefined,
error: undefined as NodeGraphError | undefined,
layerWidths: new Map<bigint, number>(),
chainWidths: new Map<bigint, number>(),
hasLeftInputWire: new Map<bigint, boolean>(),
@@ -118,6 +121,12 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphError, (updateNodeGraphError) => {
update((state) => {
state.error = updateNodeGraphError.error;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
update((state) => {
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);