mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
Make the node catalog, originating from a wire dropped in the graph, filter for valid types (#2423)
* Add InputType based filtering capabilites to NodeCatalog. Send InputTypes through SendUiMetadata under odeTypes. Update NodeCatalog.svelte component to support ype based filtering. Update ContextMenuData to support compatibleType as an input to the searchTerm for the NodeCatalog. Update Graph.svelte component to support new ContextMenuData enum types. Send CompatibleType data from rust backend on wire drag and release to NodeCatalog to already show filtered data. * Add InputType based filtering capabilites to NodeCatalog. Send InputTypes through SendUiMetadata under odeTypes. Update NodeCatalog.svelte component to support ype based filtering. Update ContextMenuData to support compatibleType as an input to the searchTerm for the NodeCatalog. Update Graph.svelte component to support new ContextMenuData enum types. Send CompatibleType data from rust backend on wire drag and release to NodeCatalog to already show filtered data. * Open NodeCatalog on DoubleClick in empty node graph area * Capture Node implementations and filter out uncatogrised nodes before sending metadata. Update NodeCatalog Search filter to support single type search alongside name and category search * Take union of DocumentNodeTypes and registered node implementations, Update missing categories and make sure to remove nodes with empty categories * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -12,9 +12,10 @@
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
export let disabled = false;
|
||||
export let initialSearchTerm = "";
|
||||
|
||||
let nodeSearchInput: TextInput | undefined = undefined;
|
||||
let searchTerm = "";
|
||||
let searchTerm = initialSearchTerm;
|
||||
|
||||
$: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm);
|
||||
|
||||
@@ -25,33 +26,60 @@
|
||||
|
||||
function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, NodeCategoryDetails][] {
|
||||
const categories = new Map<string, NodeCategoryDetails>();
|
||||
const isTypeSearch = searchTerm.toLowerCase().startsWith("type:");
|
||||
let typeSearchTerm = "";
|
||||
let remainingSearchTerms = [searchTerm.toLowerCase()];
|
||||
|
||||
if (isTypeSearch) {
|
||||
// Extract the first word after "type:" as the type search
|
||||
const searchParts = searchTerm.substring(5).trim().split(/\s+/);
|
||||
typeSearchTerm = searchParts[0].toLowerCase();
|
||||
|
||||
remainingSearchTerms = searchParts.slice(1).map((term) => term.toLowerCase());
|
||||
}
|
||||
|
||||
nodeTypes.forEach((node) => {
|
||||
let nameIncludesSearchTerm = node.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
let matchesTypeSearch = true;
|
||||
let matchesRemainingTerms = true;
|
||||
|
||||
// Quick and dirty hack to alias "Layer" to "Merge" in the search
|
||||
if (node.name === "Merge") {
|
||||
nameIncludesSearchTerm = nameIncludesSearchTerm || "Layer".toLowerCase().includes(searchTerm.toLowerCase());
|
||||
if (isTypeSearch && typeSearchTerm) {
|
||||
matchesTypeSearch = node.inputTypes?.some((inputType) => inputType.toLowerCase().includes(typeSearchTerm)) || false;
|
||||
}
|
||||
|
||||
if (searchTerm.length > 0 && !nameIncludesSearchTerm && !node.category.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
if (remainingSearchTerms.length > 0) {
|
||||
matchesRemainingTerms = remainingSearchTerms.every((term) => {
|
||||
const nameMatch = node.name.toLowerCase().includes(term);
|
||||
const categoryMatch = node.category.toLowerCase().includes(term);
|
||||
|
||||
// Quick and dirty hack to alias "Layer" to "Merge" in the search
|
||||
const layerAliasMatch = node.name === "Merge" && "layer".includes(term);
|
||||
|
||||
return nameMatch || categoryMatch || layerAliasMatch;
|
||||
});
|
||||
}
|
||||
|
||||
// Node matches if it passes both type search and remaining terms filters
|
||||
const includesSearchTerm = matchesTypeSearch && matchesRemainingTerms;
|
||||
|
||||
if (searchTerm.length > 0 && !includesSearchTerm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const category = categories.get(node.category);
|
||||
let open = nameIncludesSearchTerm;
|
||||
let open = includesSearchTerm;
|
||||
if (searchTerm.length === 0) {
|
||||
open = false;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
category.open = open;
|
||||
category.open = category.open || open;
|
||||
category.nodes.push(node);
|
||||
} else
|
||||
} else {
|
||||
categories.set(node.category, {
|
||||
open,
|
||||
nodes: [node],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const START_CATEGORIES_ORDER = ["UNCATEGORIZED", "General", "Value", "Math", "Style"];
|
||||
|
||||
@@ -653,8 +653,10 @@
|
||||
top: `${$nodeGraph.contextMenuInformation.contextMenuCoordinates.y * $nodeGraph.transform.scale + $nodeGraph.transform.y}px`,
|
||||
}}
|
||||
>
|
||||
{#if $nodeGraph.contextMenuInformation.contextMenuData === "CreateNode"}
|
||||
{#if typeof $nodeGraph.contextMenuInformation.contextMenuData === "string" && $nodeGraph.contextMenuInformation.contextMenuData === "CreateNode"}
|
||||
<NodeCatalog on:selectNodeType={(e) => createNode(e.detail)} />
|
||||
{:else if $nodeGraph.contextMenuInformation.contextMenuData && "compatibleType" in $nodeGraph.contextMenuInformation.contextMenuData}
|
||||
<NodeCatalog initialSearchTerm={$nodeGraph.contextMenuInformation.contextMenuData.compatibleType || ""} on:selectNodeType={(e) => createNode(e.detail)} />
|
||||
{:else}
|
||||
{@const contextMenuData = $nodeGraph.contextMenuInformation.contextMenuData}
|
||||
<LayoutRow class="toggle-layer-or-node">
|
||||
|
||||
@@ -46,6 +46,8 @@ const ContextTupleToVec2 = Transform((data) => {
|
||||
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 };
|
||||
});
|
||||
@@ -185,8 +187,7 @@ export type FrontendClickTargets = {
|
||||
|
||||
export type ContextMenuInformation = {
|
||||
contextMenuCoordinates: XY;
|
||||
|
||||
contextMenuData: "CreateNode" | { nodeId: bigint; currentlyIsNode: boolean };
|
||||
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
|
||||
};
|
||||
|
||||
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Group" | "Artboard";
|
||||
@@ -337,6 +338,8 @@ export class FrontendNodeType {
|
||||
readonly name!: string;
|
||||
|
||||
readonly category!: string;
|
||||
|
||||
readonly inputTypes!: string[];
|
||||
}
|
||||
|
||||
export class NodeGraphTransform {
|
||||
|
||||
Reference in New Issue
Block a user