Two-way selection
This commit is contained in:
Submodule litegraph updated: b9762fa6d5...5fe79f7397
@@ -88,7 +88,7 @@ export default class ComfyGraph extends LGraph {
|
|||||||
if (!("svelteComponentType" in node) && options.addedBy == null) {
|
if (!("svelteComponentType" in node) && options.addedBy == null) {
|
||||||
console.debug("[ComfyGraph] AutoAdd UI")
|
console.debug("[ComfyGraph] AutoAdd UI")
|
||||||
const comfyNode = node as ComfyGraphNode;
|
const comfyNode = node as ComfyGraphNode;
|
||||||
const widgetNodesAdded = []
|
const widgetNodesAdded: ComfyWidgetNode[] = []
|
||||||
for (let index = 0; index < comfyNode.inputs.length; index++) {
|
for (let index = 0; index < comfyNode.inputs.length; index++) {
|
||||||
const input = comfyNode.inputs[index];
|
const input = comfyNode.inputs[index];
|
||||||
if ("config" in input) {
|
if ("config" in input) {
|
||||||
@@ -109,10 +109,10 @@ export default class ComfyGraph extends LGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dragItems = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem).filter(di => di)
|
const dragItemIDs = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem?.id).filter(Boolean)
|
||||||
console.debug("[ComfyGraph] Group new widgets", dragItems)
|
console.debug("[ComfyGraph] Group new widgets", dragItemIDs)
|
||||||
|
|
||||||
layoutState.groupItems(dragItems, { title: node.title })
|
layoutState.groupItems(dragItemIDs, { title: node.title })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem, Subgraph, LLink } from "@litegraph-ts/core";
|
import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem, Subgraph, LLink, type NodeID } from "@litegraph-ts/core";
|
||||||
import type ComfyApp from "./components/ComfyApp";
|
import type ComfyApp from "./components/ComfyApp";
|
||||||
import queueState from "./stores/queueState";
|
import queueState from "./stores/queueState";
|
||||||
import { get } from "svelte/store";
|
import { get, type Unsubscriber } from "svelte/store";
|
||||||
import uiState from "./stores/uiState";
|
import uiState from "./stores/uiState";
|
||||||
import layoutState from "./stores/layoutState";
|
import layoutState from "./stores/layoutState";
|
||||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||||
import { ComfyReroute } from "./nodes";
|
import { ComfyReroute } from "./nodes";
|
||||||
|
import type { Progress } from "./components/ComfyApp";
|
||||||
|
import selectionState from "./stores/selectionState";
|
||||||
|
|
||||||
export type SerializedGraphCanvasState = {
|
export type SerializedGraphCanvasState = {
|
||||||
offset: Vector2,
|
offset: Vector2,
|
||||||
@@ -14,6 +16,7 @@ export type SerializedGraphCanvasState = {
|
|||||||
|
|
||||||
export default class ComfyGraphCanvas extends LGraphCanvas {
|
export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||||
app: ComfyApp | null;
|
app: ComfyApp | null;
|
||||||
|
private _unsubscribe: Unsubscriber;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
app: ComfyApp,
|
app: ComfyApp,
|
||||||
@@ -27,8 +30,22 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
) {
|
) {
|
||||||
super(canvas, app.lGraph, options);
|
super(canvas, app.lGraph, options);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this._unsubscribe = selectionState.subscribe(ss => {
|
||||||
|
for (const node of Object.values(this.selected_nodes)) {
|
||||||
|
node.is_selected = false;
|
||||||
|
}
|
||||||
|
this.selected_nodes = {}
|
||||||
|
for (const node of ss.currentSelectionNodes) {
|
||||||
|
this.selected_nodes[node.id] = node;
|
||||||
|
node.is_selected = true
|
||||||
|
}
|
||||||
|
this._selectedNodes = new Set()
|
||||||
|
this.setDirty(true, true);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_selectedNodes: Set<NodeID> = new Set();
|
||||||
|
|
||||||
serialize(): SerializedGraphCanvasState {
|
serialize(): SerializedGraphCanvasState {
|
||||||
return {
|
return {
|
||||||
offset: this.ds.offset,
|
offset: this.ds.offset,
|
||||||
@@ -58,51 +75,61 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
super.drawNodeShape(node, ctx, size, fgColor, bgColor, selected, mouseOver);
|
super.drawNodeShape(node, ctx, size, fgColor, bgColor, selected, mouseOver);
|
||||||
|
|
||||||
let state = get(queueState);
|
let state = get(queueState);
|
||||||
|
let ss = get(selectionState);
|
||||||
|
|
||||||
let color = null;
|
let color = null;
|
||||||
if (node.id === +state.runningNodeID) {
|
let thickness = 1;
|
||||||
|
// if (this._selectedNodes.has(node.id)) {
|
||||||
|
// color = "yellow";
|
||||||
|
// thickness = 5;
|
||||||
|
// }
|
||||||
|
if (ss.currentHoveredNodes.has(node.id)) {
|
||||||
|
color = "lightblue";
|
||||||
|
}
|
||||||
|
else if (node.id === +state.runningNodeID) {
|
||||||
color = "#0f0";
|
color = "#0f0";
|
||||||
// this.app can be null inside the constructor if rendering is taking place already
|
|
||||||
} else if (this.app && this.app.dragOverNode && node.id === this.app.dragOverNode.id) {
|
|
||||||
color = "dodgerblue";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (color) {
|
if (color) {
|
||||||
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
this.drawNodeOutline(node, ctx, state.progress, size, fgColor, bgColor, color, thickness)
|
||||||
ctx.lineWidth = 1;
|
}
|
||||||
ctx.globalAlpha = 0.8;
|
}
|
||||||
ctx.beginPath();
|
|
||||||
if (shape == BuiltInSlotShape.BOX_SHAPE)
|
|
||||||
ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
|
|
||||||
else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed))
|
|
||||||
ctx.roundRect(
|
|
||||||
-6,
|
|
||||||
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
12 + size[0] + 1,
|
|
||||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
this.round_radius * 2
|
|
||||||
);
|
|
||||||
else if (shape == BuiltInSlotShape.CARD_SHAPE)
|
|
||||||
ctx.roundRect(
|
|
||||||
-6,
|
|
||||||
-6 + LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
12 + size[0] + 1,
|
|
||||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
|
||||||
this.round_radius * 2,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
else if (shape == BuiltInSlotShape.CIRCLE_SHAPE)
|
|
||||||
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
|
||||||
ctx.strokeStyle = color;
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.strokeStyle = fgColor;
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
|
|
||||||
if (state.progress) {
|
private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, progress?: Progress, size: Vector2, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) {
|
||||||
ctx.fillStyle = "green";
|
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
||||||
ctx.fillRect(0, 0, size[0] * (state.progress.value / state.progress.max), 6);
|
ctx.lineWidth = outlineThickness;
|
||||||
ctx.fillStyle = bgColor;
|
ctx.globalAlpha = 0.8;
|
||||||
}
|
ctx.beginPath();
|
||||||
|
if (shape == BuiltInSlotShape.BOX_SHAPE)
|
||||||
|
ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
|
||||||
|
else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed))
|
||||||
|
ctx.roundRect(
|
||||||
|
-6,
|
||||||
|
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
12 + size[0] + 1,
|
||||||
|
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
this.round_radius * 2
|
||||||
|
);
|
||||||
|
else if (shape == BuiltInSlotShape.CARD_SHAPE)
|
||||||
|
ctx.roundRect(
|
||||||
|
-6,
|
||||||
|
-6 + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
12 + size[0] + 1,
|
||||||
|
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||||
|
this.round_radius * 2,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
else if (shape == BuiltInSlotShape.CIRCLE_SHAPE)
|
||||||
|
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5 + 6, 0, Math.PI * 2);
|
||||||
|
ctx.strokeStyle = outlineColor;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.strokeStyle = fgColor;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
|
||||||
|
if (progress) {
|
||||||
|
ctx.fillStyle = "green";
|
||||||
|
ctx.fillRect(0, 0, size[0] * (progress.value / progress.max), 6);
|
||||||
|
ctx.fillStyle = bgColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,10 +262,43 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onSelectionChange(nodes: Record<number, LGraphNode>) {
|
override onSelectionChange(nodes: Record<number, LGraphNode>) {
|
||||||
const ls = get(layoutState)
|
selectionState.update(ss => {
|
||||||
ls.currentSelectionNodes = Object.values(nodes)
|
ss.currentSelectionNodes = Object.values(nodes)
|
||||||
ls.currentSelection = []
|
ss.currentSelection = []
|
||||||
layoutState.set(ls)
|
const ls = get(layoutState)
|
||||||
|
for (const node of ss.currentSelectionNodes) {
|
||||||
|
const widget = ls.allItemsByNode[node.id]
|
||||||
|
if (widget)
|
||||||
|
ss.currentSelection.push(widget.dragItem.id)
|
||||||
|
}
|
||||||
|
return ss
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override onHoverChange(node: LGraphNode | null) {
|
||||||
|
selectionState.update(ss => {
|
||||||
|
ss.currentHoveredNodes.clear()
|
||||||
|
if (node) {
|
||||||
|
ss.currentHoveredNodes.add(node.id)
|
||||||
|
}
|
||||||
|
ss.currentHovered.clear()
|
||||||
|
const ls = get(layoutState)
|
||||||
|
for (const nodeID of ss.currentHoveredNodes) {
|
||||||
|
const widget = ls.allItemsByNode[nodeID]
|
||||||
|
if (widget)
|
||||||
|
ss.currentHovered.add(widget.dragItem.id)
|
||||||
|
}
|
||||||
|
return ss
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override clear() {
|
||||||
|
super.clear();
|
||||||
|
selectionState.update(ss => {
|
||||||
|
ss.currentSelectionNodes = [];
|
||||||
|
ss.currentHoveredNodes.clear()
|
||||||
|
return ss;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override onNodeMoved(node: LGraphNode) {
|
override onNodeMoved(node: LGraphNode) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import Accordion from "$lib/components/gradio/app/Accordion.svelte";
|
import Accordion from "$lib/components/gradio/app/Accordion.svelte";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import WidgetContainer from "./WidgetContainer.svelte"
|
import WidgetContainer from "./WidgetContainer.svelte"
|
||||||
|
|
||||||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
@@ -61,9 +62,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if container && children}
|
{#if container && children}
|
||||||
|
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||||
class:selected={$uiState.uiUnlocked && $layoutState.currentSelection.includes(container.id)}
|
class:selected
|
||||||
class:root-container={zIndex === 0}
|
class:root-container={zIndex === 0}
|
||||||
class:is-executing={container.isNodeExecuting}
|
class:is-executing={container.isNodeExecuting}
|
||||||
class:edit={edit}>
|
class:edit={edit}>
|
||||||
@@ -120,6 +122,10 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
|
&.selected {
|
||||||
|
background: var(--comfy-container-selected-background-fill) !important;
|
||||||
|
}
|
||||||
|
|
||||||
> :global(*) {
|
> :global(*) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import WidgetContainer from "./WidgetContainer.svelte"
|
import WidgetContainer from "./WidgetContainer.svelte"
|
||||||
|
|
||||||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
@@ -49,9 +50,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if container && children}
|
{#if container && children}
|
||||||
|
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||||
class:selected={$uiState.uiUnlocked && $layoutState.currentSelection.includes(container.id)}
|
class:selected
|
||||||
class:root-container={zIndex === 0}
|
class:root-container={zIndex === 0}
|
||||||
class:is-executing={container.isNodeExecuting}
|
class:is-executing={container.isNodeExecuting}
|
||||||
class:mobile={isMobile}
|
class:mobile={isMobile}
|
||||||
@@ -154,6 +156,10 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--comfy-container-selected-background-fill) !important;
|
||||||
|
}
|
||||||
|
|
||||||
> :global(*) {
|
> :global(*) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { Checkbox, TextBox } from "@gradio/form"
|
import { Checkbox, TextBox } from "@gradio/form"
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import layoutState from "$lib/stores/layoutState";
|
import layoutState from "$lib/stores/layoutState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
import { ImageViewer } from "$lib/ImageViewer";
|
||||||
import type { ComfyAPIStatus } from "$lib/api";
|
import type { ComfyAPIStatus } from "$lib/api";
|
||||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||||
@@ -54,12 +55,12 @@
|
|||||||
|
|
||||||
if (!$uiState.uiUnlocked) {
|
if (!$uiState.uiUnlocked) {
|
||||||
app.lCanvas.deselectAllNodes();
|
app.lCanvas.deselectAllNodes();
|
||||||
$layoutState.currentSelectionNodes = []
|
$selectionState.currentSelectionNodes = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if ($uiState.uiEditMode)
|
$: if ($uiState.uiEditMode)
|
||||||
$layoutState.currentSelection = []
|
$selectionState.currentSelection = []
|
||||||
|
|
||||||
let graphSize = 0;
|
let graphSize = 0;
|
||||||
let graphTransitioning = false;
|
let graphTransitioning = false;
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ export default class ComfyApp {
|
|||||||
dropZone: HTMLElement | null = null;
|
dropZone: HTMLElement | null = null;
|
||||||
nodeOutputs: Record<string, any> = {};
|
nodeOutputs: Record<string, any> = {};
|
||||||
|
|
||||||
dragOverNode: LGraphNode | null = null;
|
|
||||||
shiftDown: boolean = false;
|
shiftDown: boolean = false;
|
||||||
selectedGroupMoving: boolean = false;
|
selectedGroupMoving: boolean = false;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { LGraphNode } from "@litegraph-ts/core"
|
import { LGraphNode } from "@litegraph-ts/core"
|
||||||
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
||||||
import uiState from "$lib/stores/uiState"
|
import uiState from "$lib/stores/uiState"
|
||||||
|
import selectionState from "$lib/stores/selectionState"
|
||||||
import { get, type Writable, writable } from "svelte/store"
|
import { get, type Writable, writable } from "svelte/store"
|
||||||
import type { ComfyWidgetNode } from "$lib/nodes";
|
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||||
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
||||||
@@ -17,8 +18,8 @@
|
|||||||
|
|
||||||
$: refreshPropsPanel = $layoutState.refreshPropsPanel;
|
$: refreshPropsPanel = $layoutState.refreshPropsPanel;
|
||||||
|
|
||||||
$: if ($layoutState.currentSelection.length > 0) {
|
$: if ($selectionState.currentSelection.length > 0) {
|
||||||
const targetId = $layoutState.currentSelection.slice(-1)[0]
|
const targetId = $selectionState.currentSelection.slice(-1)[0]
|
||||||
target = $layoutState.allItems[targetId].dragItem
|
target = $layoutState.allItems[targetId].dragItem
|
||||||
attrsChanged = target.attrsChanged;
|
attrsChanged = target.attrsChanged;
|
||||||
if (target.type === "widget") {
|
if (target.type === "widget") {
|
||||||
@@ -28,9 +29,9 @@
|
|||||||
node = null;
|
node = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ($layoutState.currentSelectionNodes.length > 0) {
|
else if ($selectionState.currentSelectionNodes.length > 0) {
|
||||||
target = null;
|
target = null;
|
||||||
node = $layoutState.currentSelectionNodes[0]
|
node = $selectionState.currentSelectionNodes[0]
|
||||||
attrsChanged = null;
|
attrsChanged = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import WidgetContainer from "./WidgetContainer.svelte";
|
import WidgetContainer from "./WidgetContainer.svelte";
|
||||||
import layoutState, { type ContainerLayout, type DragItem, type IDragItem } from "$lib/stores/layoutState";
|
import layoutState, { type ContainerLayout, type DragItem, type IDragItem } from "$lib/stores/layoutState";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
|
|
||||||
import Menu from './menu/Menu.svelte';
|
import Menu from './menu/Menu.svelte';
|
||||||
import MenuOption from './menu/MenuOption.svelte';
|
import MenuOption from './menu/MenuOption.svelte';
|
||||||
@@ -30,18 +31,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function groupWidgets(horizontal: boolean) {
|
function groupWidgets(horizontal: boolean) {
|
||||||
const items = layoutState.getCurrentSelection()
|
const items = $selectionState.currentSelection
|
||||||
$layoutState.currentSelection = []
|
$selectionState.currentSelection = []
|
||||||
layoutState.groupItems(items, { direction: horizontal ? "horizontal" : "vertical" })
|
layoutState.groupItems(items, { direction: horizontal ? "horizontal" : "vertical" })
|
||||||
}
|
}
|
||||||
|
|
||||||
let canUngroup = false;
|
let canUngroup = false;
|
||||||
let isDeleteGroup = false;
|
let isDeleteGroup = false;
|
||||||
$: canUngroup = $layoutState.currentSelection.length === 1
|
$: {
|
||||||
&& layoutState.getCurrentSelection()[0].type === "container"
|
canUngroup = false;
|
||||||
|
if ($selectionState.currentSelection.length === 1) {
|
||||||
|
const item = $layoutState.allItems[$selectionState.currentSelection[0]].dragItem;
|
||||||
|
canUngroup = item.type === "container"
|
||||||
|
}
|
||||||
|
}
|
||||||
$: if (canUngroup) {
|
$: if (canUngroup) {
|
||||||
const dragItem = layoutState.getCurrentSelection()[0];
|
const dragItemID = $selectionState.currentSelection[0];
|
||||||
const entry = $layoutState.allItems[dragItem.id];
|
const entry = $layoutState.allItems[dragItemID];
|
||||||
isDeleteGroup = entry.children.length === 0
|
isDeleteGroup = entry.children.length === 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -49,11 +55,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ungroup() {
|
function ungroup() {
|
||||||
const item = layoutState.getCurrentSelection()[0]
|
const itemID = $selectionState.currentSelection[0]
|
||||||
if (!item || item.type !== "container")
|
if (itemID == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$layoutState.currentSelection = []
|
const item = $layoutState.allItems[$selectionState.currentSelection[0]].dragItem;
|
||||||
|
if(item.type !== "container")
|
||||||
|
return
|
||||||
|
|
||||||
|
$selectionState.currentSelection = []
|
||||||
layoutState.ungroup(item as ContainerLayout)
|
layoutState.ungroup(item as ContainerLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +104,11 @@
|
|||||||
{#if showMenu}
|
{#if showMenu}
|
||||||
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||||
<MenuOption
|
<MenuOption
|
||||||
isDisabled={$layoutState.currentSelection.length === 0}
|
isDisabled={$selectionState.currentSelection.length === 0}
|
||||||
on:click={() => groupWidgets(false)}
|
on:click={() => groupWidgets(false)}
|
||||||
text="Group" />
|
text="Group" />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
isDisabled={$layoutState.currentSelection.length === 0}
|
isDisabled={$selectionState.currentSelection.length === 0}
|
||||||
on:click={() => groupWidgets(true)}
|
on:click={() => groupWidgets(true)}
|
||||||
text="Group Horizontally" />
|
text="Group Horizontally" />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import WidgetContainer from "./WidgetContainer.svelte"
|
import WidgetContainer from "./WidgetContainer.svelte"
|
||||||
import BlockContainer from "./BlockContainer.svelte"
|
import BlockContainer from "./BlockContainer.svelte"
|
||||||
import AccordionContainer from "./AccordionContainer.svelte"
|
import AccordionContainer from "./AccordionContainer.svelte"
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
|
|
||||||
{#if container}
|
{#if container}
|
||||||
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
{@const dragDisabled = zIndex === 0 || $layoutState.currentSelection.length > 2 || !$uiState.uiUnlocked}
|
{@const dragDisabled = zIndex === 0 || $selectionState.currentSelection.length > 2 || !$uiState.uiUnlocked}
|
||||||
{#key $attrsChanged}
|
{#key $attrsChanged}
|
||||||
{#if edit || !isHidden(container)}
|
{#if edit || !isHidden(container)}
|
||||||
{#if container.attrs.variant === "tabs"}
|
{#if container.attrs.variant === "tabs"}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
import { Tabs, TabItem } from "@gradio/tabs";
|
import { Tabs, TabItem } from "@gradio/tabs";
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import WidgetContainer from "./WidgetContainer.svelte"
|
import WidgetContainer from "./WidgetContainer.svelte"
|
||||||
|
|
||||||
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
@@ -68,9 +69,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if container && children}
|
{#if container && children}
|
||||||
|
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||||
class:selected={$uiState.uiUnlocked && $layoutState.currentSelection.includes(container.id)}
|
class:selected
|
||||||
class:root-container={zIndex === 0}
|
class:root-container={zIndex === 0}
|
||||||
class:is-executing={container.isNodeExecuting}
|
class:is-executing={container.isNodeExecuting}
|
||||||
class:edit={edit}>
|
class:edit={edit}>
|
||||||
@@ -133,6 +135,10 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--comfy-container-selected-background-fill) !important;
|
||||||
|
}
|
||||||
|
|
||||||
> :global(*) {
|
> :global(*) {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
|
|
||||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||||
|
import selectionState from "$lib/stores/selectionState";
|
||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import Container from "./Container.svelte"
|
import Container from "./Container.svelte"
|
||||||
import { type Writable } from "svelte/store"
|
import { type Writable } from "svelte/store"
|
||||||
@@ -67,21 +68,24 @@
|
|||||||
{:else if widget && widget.node}
|
{:else if widget && widget.node}
|
||||||
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
{@const hidden = isHidden(widget)}
|
{@const hidden = isHidden(widget)}
|
||||||
|
{@const hovered = $uiState.uiUnlocked && $selectionState.currentHovered.has(widget.id)}
|
||||||
|
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(widget.id)}
|
||||||
{#key $attrsChanged}
|
{#key $attrsChanged}
|
||||||
{#key $propsChanged}
|
{#key $propsChanged}
|
||||||
<div class="widget {widget.attrs.classes} {getWidgetClass()}"
|
<div class="widget {widget.attrs.classes} {getWidgetClass()}"
|
||||||
class:edit={edit}
|
class:edit={edit}
|
||||||
class:selected={$uiState.uiUnlocked && $layoutState.currentSelection.includes(widget.id)}
|
class:hovered
|
||||||
class:is-executing={$queueState.runningNodeID && $queueState.runningNodeId == widget.node.id}
|
class:selected
|
||||||
class:hidden={hidden}
|
class:is-executing={$queueState.runningNodeID && $queueState.runningNodeID == widget.node.id}
|
||||||
>
|
class:hidden={hidden}
|
||||||
|
>
|
||||||
<svelte:component this={widget.node.svelteComponentType} {widget} {isMobile} />
|
<svelte:component this={widget.node.svelteComponentType} {widget} {isMobile} />
|
||||||
</div>
|
</div>
|
||||||
{#if hidden && edit}
|
{#if hidden && edit}
|
||||||
<div class="handle handle-hidden" class:hidden={!edit} />
|
<div class="handle handle-hidden" class:hidden={!edit} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if showHandles}
|
{#if showHandles || hovered}
|
||||||
<div class="handle handle-widget" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-widget" class:hovered data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
{/key}
|
{/key}
|
||||||
@@ -92,12 +96,9 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background: var(--color-yellow-200);
|
background: var(--comfy-widget-selected-background-fill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.container.selected {
|
|
||||||
background: var(--color-yellow-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-executing {
|
.is-executing {
|
||||||
border: 3px dashed var(--color-green-600) !important;
|
border: 3px dashed var(--color-green-600) !important;
|
||||||
@@ -123,8 +124,10 @@
|
|||||||
background-color: #40404080;
|
background-color: #40404080;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle-widget:hover {
|
.handle-widget {
|
||||||
background-color: #add8e680;
|
&:hover, &.hovered {
|
||||||
|
background-color: #add8e680;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-type {
|
.node-type {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import ComfyGraph from '$lib/ComfyGraph';
|
import ComfyGraph from '$lib/ComfyGraph';
|
||||||
import { LGraphCanvas, LiteGraph, Subgraph } from '@litegraph-ts/core';
|
import { LGraphCanvas, LiteGraph, Subgraph } from '@litegraph-ts/core';
|
||||||
|
import layoutState from './stores/layoutState';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
export function configureLitegraph(isMobile: boolean = false) {
|
export function configureLitegraph(isMobile: boolean = false) {
|
||||||
LiteGraph.catch_exceptions = false;
|
LiteGraph.catch_exceptions = false;
|
||||||
@@ -16,4 +18,5 @@ export function configureLitegraph(isMobile: boolean = false) {
|
|||||||
|
|
||||||
(window as any).LiteGraph = LiteGraph;
|
(window as any).LiteGraph = LiteGraph;
|
||||||
(window as any).LGraphCanvas = LGraphCanvas;
|
(window as any).LGraphCanvas = LGraphCanvas;
|
||||||
|
(window as any).layoutState = get(layoutState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,15 +115,15 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addPropertyAsOutput(propertyName: string, type: string) {
|
addPropertyAsOutput(propertyName: string, type: string) {
|
||||||
if (this.shownOutputProperties[propertyName])
|
if (this.shownOutputProperties["@" + propertyName])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!(propertyName in this.properties)) {
|
if (!(propertyName in this.properties)) {
|
||||||
throw `No property named ${propertyName} found!`
|
throw `No property named ${propertyName} found!`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shownOutputProperties[propertyName] = { type, index: this.outputs.length }
|
this.shownOutputProperties["@" + propertyName] = { type, index: this.outputs.length }
|
||||||
this.addOutput(propertyName, type)
|
this.addOutput("@" + propertyName, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(value: any): string {
|
formatValue(value: any): string {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import type ComfyApp from "$lib/components/ComfyApp"
|
import type ComfyApp from "$lib/components/ComfyApp"
|
||||||
import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions, type UUID } from "@litegraph-ts/core"
|
import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions, type UUID, type NodeID } from "@litegraph-ts/core"
|
||||||
import { SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
import { SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||||
import type { ComfyWidgetNode } from '$lib/nodes';
|
import type { ComfyWidgetNode } from '$lib/nodes';
|
||||||
import type { ComfyNodeID } from '$lib/api';
|
import type { ComfyNodeID } from '$lib/api';
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import IComfyInputSlot from '$lib/IComfyInputSlot';
|
||||||
|
|
||||||
type DragItemEntry = {
|
type DragItemEntry = {
|
||||||
/*
|
/*
|
||||||
@@ -62,16 +63,6 @@ export type LayoutState = {
|
|||||||
*/
|
*/
|
||||||
allItemsByNode: Record<ComfyNodeID, DragItemEntry>,
|
allItemsByNode: Record<ComfyNodeID, DragItemEntry>,
|
||||||
|
|
||||||
/*
|
|
||||||
* Selected drag items.
|
|
||||||
*/
|
|
||||||
currentSelection: DragItemID[],
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Selected LGraphNodes inside the litegraph canvas.
|
|
||||||
*/
|
|
||||||
currentSelectionNodes: LGraphNode[],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If true, a saved workflow is being deserialized, so ignore any
|
* If true, a saved workflow is being deserialized, so ignore any
|
||||||
* nodeAdded/nodeRemoved events.
|
* nodeAdded/nodeRemoved events.
|
||||||
@@ -660,9 +651,8 @@ type LayoutStateOps = {
|
|||||||
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
||||||
nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void,
|
nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void,
|
||||||
nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void,
|
nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void,
|
||||||
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||||
ungroup: (container: ContainerLayout) => void,
|
ungroup: (container: ContainerLayout) => void,
|
||||||
getCurrentSelection: () => IDragItem[],
|
|
||||||
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null,
|
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null,
|
||||||
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null,
|
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null,
|
||||||
serialize: () => SerializedLayoutState,
|
serialize: () => SerializedLayoutState,
|
||||||
@@ -676,8 +666,6 @@ const store: Writable<LayoutState> = writable({
|
|||||||
root: null,
|
root: null,
|
||||||
allItems: {},
|
allItems: {},
|
||||||
allItemsByNode: {},
|
allItemsByNode: {},
|
||||||
currentSelection: [],
|
|
||||||
currentSelectionNodes: [],
|
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: true,
|
isConfiguring: true,
|
||||||
refreshPropsPanel: writable(0),
|
refreshPropsPanel: writable(0),
|
||||||
@@ -858,33 +846,29 @@ function moveItem(target: IDragItem, to: ContainerLayout, index?: number) {
|
|||||||
store.set(state)
|
store.set(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentSelection(): IDragItem[] {
|
function groupItems(dragItemIDs: DragItemID[], attrs: Partial<Attributes> = {}): ContainerLayout {
|
||||||
const state = get(store)
|
if (dragItemIDs.length === 0)
|
||||||
return state.currentSelection.map(id => state.allItems[id].dragItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupItems(dragItems: IDragItem[], attrs: Partial<Attributes> = {}): ContainerLayout {
|
|
||||||
if (dragItems.length === 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const state = get(store)
|
const state = get(store)
|
||||||
const parent = state.allItems[dragItems[0].id].parent || findDefaultContainerForInsertion();
|
const parent = state.allItems[dragItemIDs[0]].parent || findDefaultContainerForInsertion();
|
||||||
|
|
||||||
if (parent === null || parent.type !== "container")
|
if (parent === null || parent.type !== "container")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let index = undefined;
|
let index = undefined;
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const indexFound = state.allItems[parent.id].children.findIndex(c => c.id === dragItems[0].id)
|
const indexFound = state.allItems[parent.id].children.findIndex(c => c.id === dragItemIDs[0])
|
||||||
if (indexFound !== -1)
|
if (indexFound !== -1)
|
||||||
index = indexFound
|
index = indexFound
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = dragItems.length <= 1 ? "" : "Group";
|
const title = dragItemIDs.length <= 1 ? "" : "Group";
|
||||||
|
|
||||||
const container = addContainer(parent as ContainerLayout, { title, ...attrs }, index)
|
const container = addContainer(parent as ContainerLayout, { title, ...attrs }, index)
|
||||||
|
|
||||||
for (const item of dragItems) {
|
for (const itemID of dragItemIDs) {
|
||||||
|
const item = state.allItems[itemID].dragItem;
|
||||||
moveItem(item, container)
|
moveItem(item, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,8 +930,6 @@ function initDefaultLayout() {
|
|||||||
root: null,
|
root: null,
|
||||||
allItems: {},
|
allItems: {},
|
||||||
allItemsByNode: {},
|
allItemsByNode: {},
|
||||||
currentSelection: [],
|
|
||||||
currentSelectionNodes: [],
|
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false,
|
isConfiguring: false,
|
||||||
refreshPropsPanel: writable(0),
|
refreshPropsPanel: writable(0),
|
||||||
@@ -1061,8 +1043,6 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
root,
|
root,
|
||||||
allItems,
|
allItems,
|
||||||
allItemsByNode,
|
allItemsByNode,
|
||||||
currentSelection: [],
|
|
||||||
currentSelectionNodes: [],
|
|
||||||
isMenuOpen: false,
|
isMenuOpen: false,
|
||||||
isConfiguring: false,
|
isConfiguring: false,
|
||||||
refreshPropsPanel: writable(0),
|
refreshPropsPanel: writable(0),
|
||||||
@@ -1093,7 +1073,6 @@ const layoutStateStore: WritableLayoutStateStore =
|
|||||||
updateChildren,
|
updateChildren,
|
||||||
nodeAdded,
|
nodeAdded,
|
||||||
nodeRemoved,
|
nodeRemoved,
|
||||||
getCurrentSelection,
|
|
||||||
groupItems,
|
groupItems,
|
||||||
findLayoutEntryForNode,
|
findLayoutEntryForNode,
|
||||||
findLayoutForNode,
|
findLayoutForNode,
|
||||||
|
|||||||
57
src/lib/stores/selectionState.ts
Normal file
57
src/lib/stores/selectionState.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import type { Readable, Writable } from 'svelte/store';
|
||||||
|
import type { DragItemID, IDragItem } from './layoutState';
|
||||||
|
import type { LGraphNode, NodeID } from '@litegraph-ts/core';
|
||||||
|
|
||||||
|
export type SelectionState = {
|
||||||
|
/*
|
||||||
|
* Selected drag items.
|
||||||
|
* NOTE: Order is important, for node grouping actions.
|
||||||
|
*/
|
||||||
|
currentSelection: DragItemID[],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hovered drag items.
|
||||||
|
*/
|
||||||
|
currentHovered: Set<DragItemID>,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Selected LGraphNodes inside the litegraph canvas.
|
||||||
|
* NOTE: Order is important, for node grouping actions.
|
||||||
|
*/
|
||||||
|
currentSelectionNodes: LGraphNode[],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Currently hovered nodes.
|
||||||
|
*/
|
||||||
|
currentHoveredNodes: Set<NodeID>
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectionStateOps = {
|
||||||
|
clear: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WritableSelectionStateStore = Writable<SelectionState> & SelectionStateOps;
|
||||||
|
const store: Writable<SelectionState> = writable(
|
||||||
|
{
|
||||||
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
|
currentHovered: new Set(),
|
||||||
|
currentHoveredNodes: new Set(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
store.set({
|
||||||
|
currentSelection: [],
|
||||||
|
currentSelectionNodes: [],
|
||||||
|
currentHovered: new Set(),
|
||||||
|
currentHoveredNodes: new Set(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiStateStore: WritableSelectionStateStore =
|
||||||
|
{
|
||||||
|
...store,
|
||||||
|
clear
|
||||||
|
}
|
||||||
|
export default uiStateStore;
|
||||||
@@ -3,7 +3,8 @@ import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
|||||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import layoutState from "$lib/stores/layoutState"
|
import layoutState, { type WidgetLayout } from "$lib/stores/layoutState"
|
||||||
|
import selectionState from "$lib/stores/selectionState"
|
||||||
import type { SvelteComponentDev } from "svelte/internal";
|
import type { SvelteComponentDev } from "svelte/internal";
|
||||||
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, GraphInput } from "@litegraph-ts/core";
|
import { Subgraph, type LGraph, type LGraphNode, type LLink, type SerializedLGraph, type UUID, GraphInput } from "@litegraph-ts/core";
|
||||||
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
import type { ComfyExecutionResult, ComfyImageLocation } from "./nodes/ComfyWidgetNodes";
|
||||||
@@ -45,11 +46,12 @@ export function download(filename: string, text: string, type: string = "text/pl
|
|||||||
|
|
||||||
export function startDrag(evt: MouseEvent) {
|
export function startDrag(evt: MouseEvent) {
|
||||||
const dragItemId: string = evt.target.dataset["dragItemId"];
|
const dragItemId: string = evt.target.dataset["dragItemId"];
|
||||||
|
const ss = get(selectionState)
|
||||||
const ls = get(layoutState)
|
const ls = get(layoutState)
|
||||||
|
|
||||||
if (evt.button !== 0) {
|
if (evt.button !== 0) {
|
||||||
if (ls.currentSelection.length <= 1 && !ls.isMenuOpen)
|
if (ss.currentSelection.length <= 1 && !ls.isMenuOpen)
|
||||||
ls.currentSelection = [dragItemId]
|
ss.currentSelection = [dragItemId]
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,19 +60,29 @@ export function startDrag(evt: MouseEvent) {
|
|||||||
console.debug("startDrag", item)
|
console.debug("startDrag", item)
|
||||||
|
|
||||||
if (evt.ctrlKey) {
|
if (evt.ctrlKey) {
|
||||||
const index = ls.currentSelection.indexOf(item.id)
|
const index = ss.currentSelection.indexOf(item.id)
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
ls.currentSelection.push(item.id);
|
ss.currentSelection.push(item.id);
|
||||||
else
|
else
|
||||||
ls.currentSelection.splice(index, 1);
|
ss.currentSelection.splice(index, 1);
|
||||||
ls.currentSelection = ls.currentSelection;
|
ss.currentSelection = ss.currentSelection;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ls.currentSelection = [item.id]
|
ss.currentSelection = [item.id]
|
||||||
|
}
|
||||||
|
ss.currentSelectionNodes = [];
|
||||||
|
for (const id of ss.currentSelection) {
|
||||||
|
const item = ls.allItems[id].dragItem
|
||||||
|
if (item.type === "widget") {
|
||||||
|
const node = (item as WidgetLayout).node;
|
||||||
|
if (node) {
|
||||||
|
ss.currentSelectionNodes.push(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ls.currentSelectionNodes = [];
|
|
||||||
|
|
||||||
layoutState.set(ls)
|
layoutState.set(ls)
|
||||||
|
selectionState.set(ss)
|
||||||
};
|
};
|
||||||
|
|
||||||
export function stopDrag(evt: MouseEvent) {
|
export function stopDrag(evt: MouseEvent) {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ body {
|
|||||||
--color-blue-500: #3985f5;
|
--color-blue-500: #3985f5;
|
||||||
|
|
||||||
--comfy-accent-soft: var(--neutral-300);
|
--comfy-accent-soft: var(--neutral-300);
|
||||||
|
--comfy-widget-selected-background-fill: var(--color-yellow-100);
|
||||||
|
--comfy-widget-hovered-background-fill: var(--secondary-200);
|
||||||
|
--comfy-container-selected-background-fill: var(--color-yellow-300);
|
||||||
|
--comfy-container-hovered-background-fill: var(--secondary-300);
|
||||||
--comfy-disabled-label-color: var(--neutral-400);
|
--comfy-disabled-label-color: var(--neutral-400);
|
||||||
--comfy-disabled-textbox-background-fill: var(--neutral-200);
|
--comfy-disabled-textbox-background-fill: var(--neutral-200);
|
||||||
--comfy-disabled-textbox-border-color: var(--neutral-300);
|
--comfy-disabled-textbox-border-color: var(--neutral-300);
|
||||||
@@ -38,6 +42,10 @@ body {
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
--comfy-accent-soft: var(--neutral-600);
|
--comfy-accent-soft: var(--neutral-600);
|
||||||
|
--comfy-widget-selected-background-fill: var(--primary-500);
|
||||||
|
--comfy-widget-hovered-background-fill: var(--neutral-600);
|
||||||
|
--comfy-container-selected-background-fill: var(--primary-700);
|
||||||
|
--comfy-container-hovered-background-fill: var(--neutral-500);
|
||||||
--comfy-disabled-label-color: var(--neutral-500);
|
--comfy-disabled-label-color: var(--neutral-500);
|
||||||
--comfy-disabled-textbox-background-fill: var(--neutral-800);
|
--comfy-disabled-textbox-background-fill: var(--neutral-800);
|
||||||
--comfy-disabled-textbox-border-color: var(--neutral-700);
|
--comfy-disabled-textbox-border-color: var(--neutral-700);
|
||||||
|
|||||||
Reference in New Issue
Block a user