mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 17:08:12 +08:00
Separate graph error diagnostics from frontend node metadata (#3385)
* Separate error popup from node * Improve context menu data * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
548e0df1a1
commit
7afbeaa1f9
@@ -2,7 +2,7 @@ use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
|
|||||||
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
||||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||||
@@ -289,6 +289,9 @@ pub enum FrontendMessage {
|
|||||||
UpdateNodeGraphNodes {
|
UpdateNodeGraphNodes {
|
||||||
nodes: Vec<FrontendNode>,
|
nodes: Vec<FrontendNode>,
|
||||||
},
|
},
|
||||||
|
UpdateNodeGraphErrorDiagnostic {
|
||||||
|
error: Option<NodeGraphErrorDiagnostic>,
|
||||||
|
},
|
||||||
UpdateVisibleNodes {
|
UpdateVisibleNodes {
|
||||||
nodes: Vec<NodeId>,
|
nodes: Vec<NodeId>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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::document_message_handler::navigation_controls;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
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::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, NodeGraphErrorDiagnostic};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
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::misc::GroupFolderType;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{
|
use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||||
@@ -776,8 +776,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context_menu_data = if let Some(node_id) = clicked_id {
|
let context_menu_data = if let Some(node_id) = clicked_id {
|
||||||
let currently_is_node = !network_interface.is_layer(&node_id, selection_network_path);
|
let currently_is_node = !network_interface.is_layer(&node_id, breadcrumb_network_path);
|
||||||
ContextMenuData::ToggleLayer { node_id, currently_is_node }
|
let can_be_layer = network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path);
|
||||||
|
ContextMenuData::ModifyNode {
|
||||||
|
can_be_layer,
|
||||||
|
currently_is_node,
|
||||||
|
node_id,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ContextMenuData::CreateNode { compatible_type: None }
|
ContextMenuData::CreateNode { compatible_type: None }
|
||||||
};
|
};
|
||||||
@@ -793,10 +798,8 @@ 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
|
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);
|
|
||||||
|
|
||||||
self.context_menu = Some(ContextMenuInformation {
|
self.context_menu = Some(ContextMenuInformation {
|
||||||
context_menu_coordinates,
|
context_menu_coordinates: (node_graph_point + node_graph_shift).into(),
|
||||||
context_menu_data,
|
context_menu_data,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1222,7 +1225,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
|
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
|
||||||
|
|
||||||
self.context_menu = Some(ContextMenuInformation {
|
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: (point + node_graph_shift).into(),
|
||||||
context_menu_data: ContextMenuData::CreateNode { compatible_type },
|
context_menu_data: ContextMenuData::CreateNode { compatible_type },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1646,6 +1649,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
||||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||||
|
|
||||||
|
let error = self.node_graph_error(network_interface, breadcrumb_network_path);
|
||||||
|
responses.add(FrontendMessage::UpdateNodeGraphErrorDiagnostic { error });
|
||||||
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
@@ -2509,8 +2514,6 @@ impl NodeGraphMessageHandler {
|
|||||||
};
|
};
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
|
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_connector = InputConnector::node(node_id, 0);
|
||||||
|
|
||||||
let primary_input = if network_interface
|
let primary_input = if network_interface
|
||||||
@@ -2552,20 +2555,6 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
let locked = network_interface.is_locked(&node_id, breadcrumb_network_path);
|
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 {
|
nodes.push(FrontendNode {
|
||||||
id: node_id,
|
id: node_id,
|
||||||
is_layer: network_interface
|
is_layer: network_interface
|
||||||
@@ -2584,7 +2573,6 @@ impl NodeGraphMessageHandler {
|
|||||||
previewed,
|
previewed,
|
||||||
visible,
|
visible,
|
||||||
locked,
|
locked,
|
||||||
errors,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2606,6 +2594,29 @@ impl NodeGraphMessageHandler {
|
|||||||
Some(subgraph_names)
|
Some(subgraph_names)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_graph_error(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<NodeGraphErrorDiagnostic> {
|
||||||
|
let graph_error = network_interface
|
||||||
|
.resolved_types
|
||||||
|
.node_graph_errors
|
||||||
|
.iter()
|
||||||
|
.find(|error| error.node_path.starts_with(breadcrumb_network_path) && error.node_path.len() > breadcrumb_network_path.len())?;
|
||||||
|
let error = if graph_error.node_path.len() == breadcrumb_network_path.len() + 1 {
|
||||||
|
format!("{:?}", graph_error.error)
|
||||||
|
} else {
|
||||||
|
"Node graph type error within this node".to_string()
|
||||||
|
};
|
||||||
|
let error_node = graph_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)
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(NodeGraphErrorDiagnostic { 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>) {
|
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 {
|
if !layers_panel_open {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use glam::IVec2;
|
use glam::{DVec2, IVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Type;
|
use graphene_std::Type;
|
||||||
@@ -90,15 +90,14 @@ pub struct FrontendNode {
|
|||||||
pub primary_output: Option<FrontendGraphOutput>,
|
pub primary_output: Option<FrontendGraphOutput>,
|
||||||
#[serde(rename = "exposedOutputs")]
|
#[serde(rename = "exposedOutputs")]
|
||||||
pub exposed_outputs: Vec<FrontendGraphOutput>,
|
pub exposed_outputs: Vec<FrontendGraphOutput>,
|
||||||
#[serde(rename = "primaryOutputConnectedToLayer")]
|
|
||||||
pub primary_output_connected_to_layer: bool,
|
|
||||||
#[serde(rename = "primaryInputConnectedToLayer")]
|
#[serde(rename = "primaryInputConnectedToLayer")]
|
||||||
pub primary_input_connected_to_layer: bool,
|
pub primary_input_connected_to_layer: bool,
|
||||||
|
#[serde(rename = "primaryOutputConnectedToLayer")]
|
||||||
|
pub primary_output_connected_to_layer: bool,
|
||||||
pub position: IVec2,
|
pub position: IVec2,
|
||||||
|
pub previewed: bool,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub locked: bool,
|
pub locked: bool,
|
||||||
pub previewed: bool,
|
|
||||||
pub errors: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -154,16 +153,18 @@ pub struct BoxSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
#[serde(tag = "type", content = "data")]
|
||||||
pub enum ContextMenuData {
|
pub enum ContextMenuData {
|
||||||
ToggleLayer {
|
ModifyNode {
|
||||||
#[serde(rename = "nodeId")]
|
#[serde(rename = "nodeId")]
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
#[serde(rename = "canBeLayer")]
|
||||||
|
can_be_layer: bool,
|
||||||
#[serde(rename = "currentlyIsNode")]
|
#[serde(rename = "currentlyIsNode")]
|
||||||
currently_is_node: bool,
|
currently_is_node: bool,
|
||||||
},
|
},
|
||||||
CreateNode {
|
CreateNode {
|
||||||
#[serde(rename = "compatibleType")]
|
#[serde(rename = "compatibleType")]
|
||||||
#[serde(default)]
|
|
||||||
compatible_type: Option<String>,
|
compatible_type: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -172,11 +173,17 @@ pub enum ContextMenuData {
|
|||||||
pub struct ContextMenuInformation {
|
pub struct ContextMenuInformation {
|
||||||
// Stores whether the context menu is open and its position in graph coordinates
|
// Stores whether the context menu is open and its position in graph coordinates
|
||||||
#[serde(rename = "contextMenuCoordinates")]
|
#[serde(rename = "contextMenuCoordinates")]
|
||||||
pub context_menu_coordinates: (i32, i32),
|
pub context_menu_coordinates: FrontendXY,
|
||||||
#[serde(rename = "contextMenuData")]
|
#[serde(rename = "contextMenuData")]
|
||||||
pub context_menu_data: ContextMenuData,
|
pub context_menu_data: ContextMenuData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct NodeGraphErrorDiagnostic {
|
||||||
|
pub position: FrontendXY,
|
||||||
|
pub error: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct FrontendClickTargets {
|
pub struct FrontendClickTargets {
|
||||||
#[serde(rename = "nodeClickTargets")]
|
#[serde(rename = "nodeClickTargets")]
|
||||||
@@ -200,3 +207,22 @@ pub enum Direction {
|
|||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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, y: v.y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -326,12 +326,9 @@ pub trait FrontendMessageTestUtils {
|
|||||||
|
|
||||||
impl FrontendMessageTestUtils for FrontendMessage {
|
impl FrontendMessageTestUtils for FrontendMessage {
|
||||||
fn check_node_graph_error(&self) {
|
fn check_node_graph_error(&self) {
|
||||||
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
|
let FrontendMessage::UpdateNodeGraphErrorDiagnostic { error } = self else { return };
|
||||||
|
if let Some(error) = error {
|
||||||
for node in nodes {
|
panic!("error: {:?}", error);
|
||||||
if let Some(error) = &node.errors {
|
|
||||||
panic!("error on {}: {}", node.display_name, error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,6 @@
|
|||||||
|
|
||||||
let graph: HTMLDivElement | undefined;
|
let graph: HTMLDivElement | undefined;
|
||||||
|
|
||||||
// Key value is node id + input/output index
|
|
||||||
// Imports/Export are stored at a key value of 0
|
|
||||||
|
|
||||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
||||||
$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
||||||
|
|
||||||
@@ -115,15 +112,6 @@
|
|||||||
return iconMap[icon] || "NodeNodes";
|
return iconMap[icon] || "NodeNodes";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
|
|
||||||
let node = $nodeGraph.nodes.get(toggleId);
|
|
||||||
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function canBeToggledBetweenNodeAndLayer(toggleDisplayAsLayerNodeId: bigint) {
|
|
||||||
return $nodeGraph.nodes.get(toggleDisplayAsLayerNodeId)?.canBeLayer || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNode(nodeType: string) {
|
function createNode(nodeType: string) {
|
||||||
if ($nodeGraph.contextMenuInformation === undefined) return;
|
if ($nodeGraph.contextMenuInformation === undefined) return;
|
||||||
|
|
||||||
@@ -224,29 +212,30 @@
|
|||||||
top: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates.y * $nodeGraph.transform.scale + $nodeGraph.transform.y}px`,
|
top: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates.y * $nodeGraph.transform.scale + $nodeGraph.transform.y}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if typeof $nodeGraph.contextMenuInformation.contextMenuData === "string" && $nodeGraph.contextMenuInformation.contextMenuData === "CreateNode"}
|
{#if $nodeGraph.contextMenuInformation.contextMenuData.type === "CreateNode"}
|
||||||
<NodeCatalog on:selectNodeType={(e) => createNode(e.detail)} />
|
<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.data.compatibleType || ""} on:selectNodeType={(e) => createNode(e.detail)} />
|
||||||
{:else if $nodeGraph.contextMenuInformation.contextMenuData && "compatibleType" in $nodeGraph.contextMenuInformation.contextMenuData}
|
{:else if $nodeGraph.contextMenuInformation.contextMenuData.type === "ModifyNode"}
|
||||||
<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.compatibleType || ""} on:selectNodeType={(e) => createNode(e.detail)} />
|
|
||||||
{:else}
|
|
||||||
{@const contextMenuData = $nodeGraph.contextMenuInformation.contextMenuData}
|
|
||||||
<LayoutRow class="toggle-layer-or-node">
|
<LayoutRow class="toggle-layer-or-node">
|
||||||
<TextLabel>Display as</TextLabel>
|
<TextLabel>Display as</TextLabel>
|
||||||
<RadioInput
|
<RadioInput
|
||||||
selectedIndex={contextMenuData.currentlyIsNode ? 0 : 1}
|
selectedIndex={$nodeGraph.contextMenuInformation.contextMenuData.data.currentlyIsNode ? 0 : 1}
|
||||||
entries={[
|
entries={[
|
||||||
{
|
{
|
||||||
value: "node",
|
value: "node",
|
||||||
label: "Node",
|
label: "Node",
|
||||||
action: () => toggleLayerDisplay(false, contextMenuData.nodeId),
|
action: () =>
|
||||||
|
$nodeGraph.contextMenuInformation?.contextMenuData.type === "ModifyNode" &&
|
||||||
|
editor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, false),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "layer",
|
value: "layer",
|
||||||
label: "Layer",
|
label: "Layer",
|
||||||
action: () => toggleLayerDisplay(true, contextMenuData.nodeId),
|
action: () =>
|
||||||
|
$nodeGraph.contextMenuInformation?.contextMenuData.type === "ModifyNode" &&
|
||||||
|
editor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, true),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
disabled={!canBeToggledBetweenNodeAndLayer(contextMenuData.nodeId)}
|
disabled={!$nodeGraph.contextMenuInformation.contextMenuData.data.canBeLayer}
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<Separator type="Section" direction="Vertical" />
|
<Separator type="Section" direction="Vertical" />
|
||||||
@@ -257,6 +246,17 @@
|
|||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
{/if}
|
{/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`} style:top={`${$nodeGraph.error.position.y}px`} transition:fade={FADE_TRANSITION}>
|
||||||
|
{$nodeGraph.error.error}
|
||||||
|
</span>
|
||||||
|
<span class="node-error hover" style:left={`${$nodeGraph.error.position.x}px`} style:top={`${$nodeGraph.error.position.y}px`} transition:fade={FADE_TRANSITION}>
|
||||||
|
{$nodeGraph.error.error}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Click target debug visualizations -->
|
<!-- Click target debug visualizations -->
|
||||||
{#if $nodeGraph.clickTargets}
|
{#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})`}>
|
<div class="click-targets" style:transform-origin="0 0" style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||||
@@ -333,7 +333,7 @@
|
|||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
|
||||||
>
|
>
|
||||||
{#if editingNameImportIndex == index}
|
{#if editingNameImportIndex === index}
|
||||||
<input
|
<input
|
||||||
class="import-text-input"
|
class="import-text-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if $nodeGraph.updateImportsExports.addImportExport == true}
|
{#if $nodeGraph.updateImportsExports.addImportExport}
|
||||||
<div
|
<div
|
||||||
class="plus"
|
class="plus"
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 12) / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 12) / 24}
|
||||||
@@ -508,10 +508,6 @@
|
|||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||||
data-node={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">
|
<div class="thumbnail">
|
||||||
{#if $nodeGraph.thumbnails.has(node.id)}
|
{#if $nodeGraph.thumbnails.has(node.id)}
|
||||||
{@html $nodeGraph.thumbnails.get(node.id)}
|
{@html $nodeGraph.thumbnails.get(node.id)}
|
||||||
@@ -657,10 +653,6 @@
|
|||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||||
data-node={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 -->
|
<!-- Primary row -->
|
||||||
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
|
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-secondary-section={exposedInputsOutputs.length === 0}>
|
||||||
<IconLabel icon={nodeIcon(node.reference)} />
|
<IconLabel icon={nodeIcon(node.reference)} />
|
||||||
@@ -775,7 +767,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Box selection widget -->
|
<!-- Box selection widget -->
|
||||||
<!-- TODO: Make its initial corner stay put (in graph space) when panning around -->
|
|
||||||
{#if $nodeGraph.box}
|
{#if $nodeGraph.box}
|
||||||
<div
|
<div
|
||||||
class="box-selection"
|
class="box-selection"
|
||||||
@@ -837,6 +828,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 {
|
.click-targets {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -1016,68 +1073,6 @@
|
|||||||
// backdrop-filter: blur(4px);
|
// backdrop-filter: blur(4px);
|
||||||
background: rgba(var(--color-0-black-rgb), 0.33);
|
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 {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
+18
-23
@@ -33,23 +33,6 @@ export class UpdateClickTargets extends JsMessage {
|
|||||||
readonly clickTargets!: FrontendClickTargets | undefined;
|
readonly clickTargets!: FrontendClickTargets | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
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] };
|
|
||||||
let contextMenuData = data.obj.contextMenuInformation.contextMenuData;
|
|
||||||
if (contextMenuData.ToggleLayer !== undefined) {
|
|
||||||
contextMenuData = { nodeId: contextMenuData.ToggleLayer.nodeId, currentlyIsNode: contextMenuData.ToggleLayer.currentlyIsNode };
|
|
||||||
} else if (contextMenuData.CreateNode !== undefined) {
|
|
||||||
contextMenuData = { type: "CreateNode", compatibleType: contextMenuData.CreateNode.compatibleType };
|
|
||||||
}
|
|
||||||
return { contextMenuCoordinates, contextMenuData };
|
|
||||||
});
|
|
||||||
|
|
||||||
export class UpdateContextMenuInformation extends JsMessage {
|
|
||||||
@ContextTupleToVec2
|
|
||||||
readonly contextMenuInformation!: ContextMenuInformation | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateImportsExports extends JsMessage {
|
export class UpdateImportsExports extends JsMessage {
|
||||||
readonly imports!: (FrontendGraphOutput | undefined)[];
|
readonly imports!: (FrontendGraphOutput | undefined)[];
|
||||||
|
|
||||||
@@ -94,6 +77,15 @@ export class UpdateNodeGraphNodes extends JsMessage {
|
|||||||
readonly nodes!: FrontendNode[];
|
readonly nodes!: FrontendNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateNodeGraphErrorDiagnostic extends JsMessage {
|
||||||
|
readonly error!: NodeGraphError | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeGraphError {
|
||||||
|
readonly position!: XY;
|
||||||
|
readonly error!: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class UpdateVisibleNodes extends JsMessage {
|
export class UpdateVisibleNodes extends JsMessage {
|
||||||
readonly nodes!: bigint[];
|
readonly nodes!: bigint[];
|
||||||
}
|
}
|
||||||
@@ -173,9 +165,13 @@ export type FrontendClickTargets = {
|
|||||||
|
|
||||||
export type ContextMenuInformation = {
|
export type ContextMenuInformation = {
|
||||||
contextMenuCoordinates: XY;
|
contextMenuCoordinates: XY;
|
||||||
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
|
contextMenuData: { type: "CreateNode"; data: { compatibleType: string | undefined } } | { type: "ModifyNode"; data: { canBeLayer: boolean; currentlyIsNode: boolean; nodeId: bigint } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class UpdateContextMenuInformation extends JsMessage {
|
||||||
|
readonly contextMenuInformation!: ContextMenuInformation | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color" | "Invalid";
|
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color" | "Invalid";
|
||||||
|
|
||||||
export class FrontendGraphInput {
|
export class FrontendGraphInput {
|
||||||
@@ -205,12 +201,12 @@ export class FrontendGraphOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNode {
|
export class FrontendNode {
|
||||||
|
readonly id!: bigint;
|
||||||
|
|
||||||
readonly isLayer!: boolean;
|
readonly isLayer!: boolean;
|
||||||
|
|
||||||
readonly canBeLayer!: boolean;
|
readonly canBeLayer!: boolean;
|
||||||
|
|
||||||
readonly id!: bigint;
|
|
||||||
|
|
||||||
readonly reference!: string | undefined;
|
readonly reference!: string | undefined;
|
||||||
|
|
||||||
readonly displayName!: string;
|
readonly displayName!: string;
|
||||||
@@ -236,9 +232,7 @@ export class FrontendNode {
|
|||||||
|
|
||||||
readonly visible!: boolean;
|
readonly visible!: boolean;
|
||||||
|
|
||||||
readonly unlocked!: boolean;
|
readonly locked!: boolean;
|
||||||
|
|
||||||
readonly errors!: string | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNodeType {
|
export class FrontendNodeType {
|
||||||
@@ -1700,6 +1694,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateNodeGraphControlBarLayout,
|
UpdateNodeGraphControlBarLayout,
|
||||||
UpdateNodeGraphNodes,
|
UpdateNodeGraphNodes,
|
||||||
|
UpdateNodeGraphErrorDiagnostic,
|
||||||
UpdateNodeGraphSelection,
|
UpdateNodeGraphSelection,
|
||||||
UpdateNodeGraphTransform,
|
UpdateNodeGraphTransform,
|
||||||
UpdateNodeGraphWires,
|
UpdateNodeGraphWires,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
|
import type { NodeGraphError } from "@graphite/messages";
|
||||||
import {
|
import {
|
||||||
type Box,
|
type Box,
|
||||||
type FrontendClickTargets,
|
type FrontendClickTargets,
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
UpdateNodeGraphTransform,
|
UpdateNodeGraphTransform,
|
||||||
UpdateNodeThumbnail,
|
UpdateNodeThumbnail,
|
||||||
UpdateWirePathInProgress,
|
UpdateWirePathInProgress,
|
||||||
|
UpdateNodeGraphErrorDiagnostic,
|
||||||
} from "@graphite/messages";
|
} from "@graphite/messages";
|
||||||
|
|
||||||
export function createNodeGraphState(editor: Editor) {
|
export function createNodeGraphState(editor: Editor) {
|
||||||
@@ -32,6 +34,7 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
box: undefined as Box | undefined,
|
box: undefined as Box | undefined,
|
||||||
clickTargets: undefined as FrontendClickTargets | undefined,
|
clickTargets: undefined as FrontendClickTargets | undefined,
|
||||||
contextMenuInformation: undefined as ContextMenuInformation | undefined,
|
contextMenuInformation: undefined as ContextMenuInformation | undefined,
|
||||||
|
error: undefined as NodeGraphError | undefined,
|
||||||
layerWidths: new Map<bigint, number>(),
|
layerWidths: new Map<bigint, number>(),
|
||||||
chainWidths: new Map<bigint, number>(),
|
chainWidths: new Map<bigint, number>(),
|
||||||
hasLeftInputWire: new Map<bigint, boolean>(),
|
hasLeftInputWire: new Map<bigint, boolean>(),
|
||||||
@@ -118,6 +121,12 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphErrorDiagnostic, (updateNodeGraphErrorDiagnostic) => {
|
||||||
|
update((state) => {
|
||||||
|
state.error = updateNodeGraphErrorDiagnostic.error;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
|
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
|
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
|
||||||
|
|||||||
Reference in New Issue
Block a user