Jump to node from widget properties button

This commit is contained in:
space-nuko
2023-05-28 11:49:00 -05:00
parent 3be662c598
commit 0bc9d06910
9 changed files with 333 additions and 204 deletions

View File

@@ -11,6 +11,7 @@ import queueState from "./stores/queueState";
import selectionState from "./stores/selectionState";
import templateState from "./stores/templateState";
import { calcNodesBoundingBox } from "./utils";
import interfaceState from "./stores/interfaceState";
export type SerializedGraphCanvasState = {
offset: Vector2,
@@ -118,13 +119,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
// color = "yellow";
// thickness = 5;
// }
if (ss.currentHoveredNodes.has(node.id)) {
color = "lightblue";
}
else if (isRunningNode) {
color = "#0f0";
}
else if (nodeErrors) {
if (nodeErrors) {
const hasExecutionError = nodeErrors.find(e => e.errorType === "execution");
if (hasExecutionError) {
blink = true;
@@ -139,6 +134,12 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
color = "cyan";
thickness = 2
}
else if (ss.currentHoveredNodes.has(node.id)) {
color = "lightblue";
}
else if (isRunningNode) {
color = "#0f0";
}
if (blink) {
if (nodeErrors && nodeErrors.includes(this.blinkError) && this.blinkErrorTime > 0) {
@@ -700,6 +701,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
}
jumpToNode(node: LGraphNode) {
interfaceState.update(s => { s.isJumpingToNode = true; return s; })
this.closeAllSubgraphs();
const subgraphs = Array.from(node.iterateParentSubgraphNodes()).reverse();
@@ -709,6 +712,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
}
this.centerOnNode(node);
this.selectNode(node);
}
jumpToNodeAndInput(node: LGraphNode, slotIndex: number) {

View File

@@ -220,6 +220,26 @@
}
}
async function openGraph(cb: () => void) {
const newGraphSize = Math.max(50, graphSize);
const willOpenPane = newGraphSize != graphSize
graphSize = newGraphSize
if (willOpenPane) {
const graphPane = getGraphPane();
if (graphPane) {
graphPane.addEventListener("transitionend", cb, { once: true })
await tick()
}
else {
cb()
}
}
else {
cb()
}
}
async function showError(promptIDWithError: PromptID) {
hideError();
@@ -247,23 +267,7 @@
app.lCanvas.jumpToFirstError();
}
const newGraphSize = Math.max(50, graphSize);
const willOpenPane = newGraphSize != graphSize
graphSize = newGraphSize
if (willOpenPane) {
const graphPane = getGraphPane();
if (graphPane) {
graphPane.addEventListener("transitionend", jumpToError, { once: true })
await tick()
}
else {
jumpToError()
}
}
else {
jumpToError()
}
await openGraph(jumpToError)
}
function hideError() {
@@ -273,7 +277,8 @@
}
setContext(WORKFLOWS_VIEW, {
showError
showError,
openGraph
});
</script>

View File

@@ -16,6 +16,7 @@
import ComfyQueue from "./ComfyQueue.svelte";
import ComfyTemplates from "./ComfyTemplates.svelte";
import { SvelteComponent } from "svelte";
import { capitalize } from "$lib/utils";
export let app: ComfyApp
export let mode: ComfyPaneMode = "none";
@@ -40,7 +41,7 @@
{:else if mode === "graph"}
<ComfyGraphView {app} />
{:else if mode === "properties"}
<ComfyProperties workflow={$workflowState.activeWorkflow} />
<ComfyProperties {app} workflow={$workflowState.activeWorkflow} />
{:else if mode === "templates"}
<ComfyTemplates {app} />
{:else if mode === "queue"}
@@ -55,6 +56,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<button class="mode-button ternary"
disabled={mode === theMode}
title={capitalize(theMode)}
class:selected={mode === theMode}
on:click={() => switchMode(theMode)}>
<svelte:component this={icon} width="100%" height="100%" />

View File

@@ -4,6 +4,7 @@
import { LGraphNode } from "@litegraph-ts/core"
import { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec, type WritableLayoutStateStore } from "$lib/stores/layoutStates"
import uiState from "$lib/stores/uiState"
import interfaceState from "$lib/stores/interfaceState"
import workflowState from "$lib/stores/workflowState"
import layoutStates from "$lib/stores/layoutStates"
import selectionState from "$lib/stores/selectionState"
@@ -12,7 +13,11 @@
import ComfyComboProperty from "./ComfyComboProperty.svelte";
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
import type { ComfyBoxWorkflow } from "$lib/stores/workflowState";
import { Diagram3 } from "svelte-bootstrap-icons";
import { getContext } from "svelte";
import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte";
export let app: ComfyApp
export let workflow: ComfyBoxWorkflow | null;
let layoutState: WritableLayoutStateStore | null = null
@@ -22,7 +27,12 @@
let target: IDragItem | null = null;
let node: LGraphNode | null = null;
$: if (layoutState) {
$: {
if ($interfaceState.isJumpingToNode) {
$interfaceState.isJumpingToNode = false;
}
{
if (layoutState) {
if ($selectionState.currentSelection.length > 0) {
node = null;
const targetId = $selectionState.currentSelection.slice(-1)[0]
@@ -37,6 +47,13 @@
else if ($selectionState.currentSelectionNodes.length > 0) {
target = null;
node = $selectionState.currentSelectionNodes[0]
if (node != null && layoutState != null) {
const dragItem = layoutState.findLayoutForNode(node.id);
if (dragItem != null) {
target = dragItem;
}
}
}
else {
target = null
@@ -47,6 +64,8 @@
target = null;
node = null;
}
}
}
$: if (target) {
for (const cat of Object.values(ALL_ATTRIBUTES)) {
@@ -291,18 +310,52 @@
console.warn("[ComfyProperties] doRefreshPanel")
$layoutStates.refreshPropsPanel += 1;
}
const workflowsViewContext = getContext(WORKFLOWS_VIEW) as any;
async function jumpToNode() {
if (!workflowsViewContext) {
// strange svelte bug caused by HMR
// https://github.com/sveltejs/svelte/issues/8655
console.error("[ComfyProperties] No workflows view context!")
return;
}
if (app?.lCanvas == null || workflow == null || node == null)
return;
const activeWorkflow = workflowState.setActiveWorkflow(app.lCanvas, workflow.id);
if (activeWorkflow == null || !activeWorkflow.graph.getNodeByIdRecursive(node.id))
return;
await workflowsViewContext.openGraph(() => {
app.lCanvas.jumpToNode(node);
})
}
</script>
<div class="props">
<div class="props-scroller">
<div class="top">
<div class="target-name">
<span>
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}<span>
<div class="target-title-wrapper">
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}</span>
{#if targetType !== ""}
<span class="type">({targetType})</span>
{/if}
</span>
</span>
</div>
{#if node != null}
<div class="target-name-button">
<button class="mode-button ternary"
disabled={node == null}
title="View in Graph"
on:click={jumpToNode}
>
<Diagram3 width="100%" height="100%" />
</button>
</div>
{/if}
</div>
</div>
<div class="props-entries">
@@ -478,8 +531,23 @@
{/key}
{/if}
</div>
</div>
</div>
<style lang="scss">
$bottom-bar-height: 2.5rem;
.props {
width: 100%;
height: 100%;
}
.props-scroller {
width: 100%;
height: calc(100% - $bottom-bar-height);
overflow-x: hidden;
overflow-y: auto;
}
.props-entry {
padding-bottom: 0.5rem;
@@ -491,7 +559,6 @@
.target-name {
background: var(--input-background-fill);
border-color: var(--input-border-color);
border-color: var(--input-border-color);
.title {
@@ -501,6 +568,48 @@
padding-left: 0.25rem;
font-weight: normal;
}
}
width: 100%;
display: flex;
flex-direction: row;
> .target-title-wrapper {
padding: 0.8rem 0 0.8rem 1.0rem;
display: flex;
flex-direction: row;
width: 100%;
text-align: center;
> span {
display: flex;
flex-direction: column;
justify-content: center;
}
}
> .target-name-button {
padding: 0.5rem;
.mode-button {
color: var(--comfy-accent-soft);
height: $bottom-bar-height;
width: 2.5rem;
height: 2.5rem;
margin: 1.0rem;
padding: 0.5rem;
margin-left: auto;
@include square-button;
color: var(--neutral-300);
&:hover:not(:disabled) {
filter: brightness(120%) !important;
}
&:active:not(:disabled) {
filter: brightness(50%) !important;
}
}
}
}
@@ -518,13 +627,5 @@
color: var(--neutral-500);
}
}
.bottom {
/* width: 100%;
height: auto;
position: absolute;
bottom: 0;
padding: 0.5em; */
}
@include disable-inputs;

View File

@@ -1,5 +1,5 @@
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
import ComfyGraphNode from "./ComfyGraphNode";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import ComfyWidgets from "$lib/widgets"
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
import { BuiltInSlotShape, BuiltInSlotType, LiteGraph, type SerializedLGraphNode } from "@litegraph-ts/core";
@@ -8,10 +8,19 @@ import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
import { iterateNodeDefOutputs, type ComfyNodeDef, iterateNodeDefInputs } from "$lib/ComfyNodeDef";
import type { SerializedPromptOutput } from "$lib/utils";
export interface ComfyBackendNodeProperties extends ComfyGraphNodeProperties {
noOutputDisplay: boolean
}
/*
* Base class for any node with configuration sent by the backend.
*/
export class ComfyBackendNode extends ComfyGraphNode {
override properties: ComfyBackendNodeProperties = {
tags: [],
noOutputDisplay: false
}
comfyClass: string;
comfyNodeDef: ComfyNodeDef;
displayName: string | null;
@@ -37,6 +46,10 @@ export class ComfyBackendNode extends ComfyGraphNode {
}
}
get isOutputNode(): boolean {
return this.comfyNodeDef.output_node;
}
// comfy class -> input name -> input config
private static defaultInputConfigs: Record<string, Record<string, ComfyInputConfig>> = {}

View File

@@ -11,6 +11,7 @@ export type InterfaceState = {
indicatorValue: any,
graphTransitioning: boolean
isJumpingToNode: boolean
}
type InterfaceStateOps = {
@@ -25,7 +26,8 @@ const store: Writable<InterfaceState> = writable(
showIndicator: false,
indicatorValue: null,
graphTransitioning: false
graphTransitioning: false,
isJumpingToNode: false,
})
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)

View File

@@ -812,8 +812,8 @@ type LayoutStateOps = {
moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void,
groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout,
ungroup: (container: ContainerLayout) => void,
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null,
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null,
findLayoutEntryForNode: (nodeId: NodeID) => DragItemEntry | null,
findLayoutForNode: (nodeId: NodeID) => IDragItem | null,
iterateBreadthFirst: (id?: DragItemID | null) => Iterable<DragItemEntry>,
serialize: () => SerializedLayoutState,
serializeAtRoot: (rootID: DragItemID) => SerializedLayoutState,
@@ -1216,7 +1216,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat
store.set(state)
}
function findLayoutEntryForNode(nodeId: ComfyNodeID): DragItemEntry | null {
function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
const state = get(store)
const found = Object.entries(state.allItems).find(pair =>
pair[1].dragItem.type === "widget"
@@ -1226,7 +1226,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat
return null;
}
function findLayoutForNode(nodeId: ComfyNodeID): WidgetLayout | null {
function findLayoutForNode(nodeId: NodeID): WidgetLayout | null {
const found = findLayoutEntryForNode(nodeId);
if (!found)
return null;
@@ -1511,16 +1511,12 @@ function getLayoutByDragItemID(dragItemID: DragItemID): WritableLayoutStateStore
return Object.values(get(layoutStates).all).find(l => get(l).allItems[dragItemID] != null)
}
function getDragItemByNode(node: LGraphNode): WidgetLayout | null {
function getDragItemByNode(node: LGraphNode): IDragItem | null {
const layout = getLayoutByNode(node);
if (layout == null)
return null;
const entry = get(layout).allItemsByNode[node.id]
if (entry && entry.dragItem.type === "widget")
return entry.dragItem as WidgetLayout;
return null;
return layout.findLayoutForNode(node.id);
}
export type LayoutStateStores = {
@@ -1543,7 +1539,7 @@ export type LayoutStateStoresOps = {
getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null,
getLayoutByNode: (node: LGraphNode) => WritableLayoutStateStore | null,
getLayoutByDragItemID: (dragItemID: DragItemID) => WritableLayoutStateStore | null,
getDragItemByNode: (node: LGraphNode) => WidgetLayout | null,
getDragItemByNode: (node: LGraphNode) => IDragItem | null,
}
export type WritableLayoutStateStores = Writable<LayoutStateStores> & LayoutStateStoresOps;

View File

@@ -404,7 +404,7 @@ function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowIns
const workflow = state.openedWorkflows[index]
if (workflow.id === state.activeWorkflowID)
return;
return state.activeWorkflow;
if (state.activeWorkflow != null)
state.activeWorkflow.stop("app")

View File

@@ -92,34 +92,40 @@ body {
&.primary {
background: var(--button-primary-background-fill);
&:hover {
&:hover:not(:disabled) {
background: var(--button-primary-background-fill-hover);
}
}
&.secondary {
background: var(--button-secondary-background-fill);
&:hover {
&:hover:not(:disabled) {
background: var(--button-secondary-background-fill-hover);
}
}
&.ternary {
background: var(--panel-background-fill);
&:hover {
&:hover:not(:disabled) {
background: var(--block-background-fill);
}
}
&:hover {
&:hover:not(:disabled) {
filter: brightness(85%);
}
&:active {
&:active:not(:disabled) {
filter: brightness(50%)
}
&.selected {
&.selected:not(:disabled) {
filter: brightness(80%)
}
&:disabled {
background: var(--neutral-700);
color: var(--neutral-400);
opacity: 50%;
}
}
@mixin disable-input {