Improve context menu data

This commit is contained in:
Adam
2025-11-17 03:04:15 -08:00
parent 6dfb685d17
commit 84fe7739a8
4 changed files with 40 additions and 65 deletions
@@ -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 }
}; };
@@ -2594,24 +2599,24 @@ impl NodeGraphMessageHandler {
} }
fn node_graph_error(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<NodeGraphError> { fn node_graph_error(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Option<NodeGraphError> {
let error = network_interface let graph_error = network_interface
.resolved_types .resolved_types
.node_graph_errors .node_graph_errors
.iter() .iter()
.filter(|error| error.node_path.starts_with(breadcrumb_network_path) && error.node_path.len() > breadcrumb_network_path.len()) .filter(|error| error.node_path.starts_with(breadcrumb_network_path) && error.node_path.len() > breadcrumb_network_path.len())
.next()?; .next()?;
let error_node = error.node_path[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)?; let mut position = network_interface.position(&error_node, breadcrumb_network_path)?;
// Convert to graph space // Convert to graph space
position *= 24; position *= 24;
if network_interface.is_layer(&error_node, breadcrumb_network_path) { if network_interface.is_layer(&error_node, breadcrumb_network_path) {
position += IVec2::new(12, -12) 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 }) Some(NodeGraphError { position: position.into(), error })
} }
@@ -153,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 = "canBeLayer")]
node_id: NodeId, can_be_layer: bool,
#[serde(rename = "currentlyIsNode")] #[serde(rename = "currentlyIsNode")]
currently_is_node: bool, currently_is_node: bool,
#[serde(rename = "nodeId")]
node_id: NodeId,
}, },
CreateNode { CreateNode {
#[serde(rename = "compatibleType")] #[serde(rename = "compatibleType")]
#[serde(default)]
compatible_type: Option<String>, compatible_type: Option<String>,
}, },
} }
+19 -38
View File
@@ -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;
@@ -176,24 +164,24 @@
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`; return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
} }
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string { // function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
return `Data Type: ${value.resolvedType}`; // return `Data Type: ${value.resolvedType}`;
} // }
// function validTypesText(value: FrontendGraphInput): string { // function validTypesText(value: FrontendGraphInput): string {
// const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None"; // const validTypes = value.validTypes.length > 0 ? value.validTypes.map((x) => `• ${x}`).join("\n") : "None";
// return `Valid Types:\n${validTypes}`; // return `Valid Types:\n${validTypes}`;
// } // }
function outputConnectedToText(output: FrontendGraphOutput): string { // function outputConnectedToText(output: FrontendGraphOutput): string {
if (output.connectedTo.length === 0) return "Connected to nothing"; // if (output.connectedTo.length === 0) return "Connected to nothing";
return `Connected to:\n${output.connectedTo.join("\n")}`; // return `Connected to:\n${output.connectedTo.join("\n")}`;
} // }
function inputConnectedToText(input: FrontendGraphInput): string { // function inputConnectedToText(input: FrontendGraphInput): string {
return `Connected to:\n${input.connectedTo}`; // return `Connected to:\n${input.connectedTo}`;
} // }
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) { function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
const maxLength = Math.max(arr1.length, arr2.length); const maxLength = Math.max(arr1.length, arr2.length);
@@ -224,29 +212,26 @@
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: () => editor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, false),
}, },
{ {
value: "layer", value: "layer",
label: "Layer", label: "Layer",
action: () => toggleLayerDisplay(true, contextMenuData.nodeId), action: () => 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" />
@@ -264,7 +249,6 @@
style={`left: ${$nodeGraph.error.position.x}px; style={`left: ${$nodeGraph.error.position.x}px;
top: ${$nodeGraph.error.position.y}px;`} top: ${$nodeGraph.error.position.y}px;`}
transition:fade={FADE_TRANSITION} transition:fade={FADE_TRANSITION}
title=""
data-node-error>{$nodeGraph.error.error}</span data-node-error>{$nodeGraph.error.error}</span
> >
<span <span
@@ -272,7 +256,6 @@
style={`left: ${$nodeGraph.error.position.x}px; style={`left: ${$nodeGraph.error.position.x}px;
top: ${$nodeGraph.error.position.y}px;`} top: ${$nodeGraph.error.position.y}px;`}
transition:fade={FADE_TRANSITION} transition:fade={FADE_TRANSITION}
title=""
data-node-error>{$nodeGraph.error.error}</span data-node-error>{$nodeGraph.error.error}</span
> >
</div> </div>
@@ -338,7 +321,6 @@
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}
> >
<title>{`${dataTypeTooltip(frontendOutput)}\n\n${outputConnectedToText(frontendOutput)}`}</title>
{#if frontendOutput.connectedTo.length > 0} {#if frontendOutput.connectedTo.length > 0}
<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)" /> <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} {:else}
@@ -382,7 +364,7 @@
}} }}
/> />
{#if index > 0} {#if index > 0}
<div class="reorder-drag-grip" title="Reorder this export" /> <div class="reorder-drag-grip" />
{/if} {/if}
{/if} {/if}
</div> </div>
@@ -410,7 +392,6 @@
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24} style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index} style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index}
> >
<title>{`${dataTypeTooltip(frontendInput)}\n\n${inputConnectedToText(frontendInput)}`}</title>
{#if frontendInput.connectedTo !== "nothing"} {#if frontendInput.connectedTo !== "nothing"}
<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)" /> <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} {:else}
@@ -428,7 +409,7 @@
> >
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport} {#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
{#if index > 0} {#if index > 0}
<div class="reorder-drag-grip" title="Reorder this export" /> <div class="reorder-drag-grip" />
{/if} {/if}
<IconButton <IconButton
size={16} size={16}
+1 -14
View File
@@ -33,20 +33,7 @@ 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 = data.obj.contextMenuInformation.contextMenuCoordinates;
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 { export class UpdateContextMenuInformation extends JsMessage {
@ContextTupleToVec2
readonly contextMenuInformation!: ContextMenuInformation | undefined; readonly contextMenuInformation!: ContextMenuInformation | undefined;
} }
@@ -183,7 +170,7 @@ 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 type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color" | "Invalid"; export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color" | "Invalid";