- Convert class-based LayoutMutations to useLayoutMutations() composable (#5346)

- Remove unnecessary useLayout wrapper that added boilerplate
- Use LayoutMutations interface directly in LGraph instead of redefining types
- Update all components to use composable pattern consistently
This commit is contained in:
Christian Byrne
2025-09-04 10:48:33 -07:00
committed by GitHub
parent 969c8e6325
commit f83801e998
11 changed files with 132 additions and 121 deletions

View File

@@ -139,8 +139,8 @@ import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence'
import { CORE_SETTINGS } from '@/constants/coreSettings'
import { i18n, t } from '@/i18n'
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import { useLayout } from '@/renderer/core/layout/sync/useLayout'
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
import { useLinkLayoutSync } from '@/renderer/core/layout/sync/useLinkLayoutSync'
import { useSlotLayoutSync } from '@/renderer/core/layout/sync/useSlotLayoutSync'
@@ -177,7 +177,7 @@ const workspaceStore = useWorkspaceStore()
const canvasStore = useCanvasStore()
const executionStore = useExecutionStore()
const toastStore = useToastStore()
const { mutations: layoutMutations } = useLayout()
const layoutMutations = useLayoutMutations()
const betaMenuEnabled = computed(
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
)

View File

@@ -4,7 +4,7 @@
*/
import { nextTick, reactive, readonly } from 'vue'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { WidgetValue } from '@/types/simplifiedWidget'
import type { SpatialIndexDebugInfo } from '@/types/spatialIndex'
@@ -99,6 +99,9 @@ export interface GraphNodeManager {
}
export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
// Get layout mutations composable
const { moveNode, resizeNode, createNode, deleteNode, setSource } =
useLayoutMutations()
// Safe reactive data extracted from LiteGraph nodes
const vueNodeData = reactive(new Map<string, VueNodeData>())
const nodeState = reactive(new Map<string, NodeState>())
@@ -487,7 +490,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
// Push position change to layout store
// Source is already set to 'canvas' in detectChangesInRAF
void layoutMutations.moveNode(id, { x: node.pos[0], y: node.pos[1] })
void moveNode(id, { x: node.pos[0], y: node.pos[1] })
return true
}
@@ -509,7 +512,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
// Push size change to layout store
// Source is already set to 'canvas' in detectChangesInRAF
void layoutMutations.resizeNode(id, {
void resizeNode(id, {
width: node.size[0],
height: node.size[1]
})
@@ -554,7 +557,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
}
/**
* Main RAF change detection function - now simplified with extracted helpers
* Main RAF change detection function
*/
const detectChangesInRAF = () => {
const startTime = performance.now()
@@ -565,7 +568,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
let sizeUpdates = 0
// Set source for all canvas-driven updates
layoutMutations.setSource(LayoutSource.Canvas)
setSource(LayoutSource.Canvas)
// Process each node for changes
for (const node of graph._nodes) {
@@ -625,8 +628,8 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
spatialIndex.insert(id, bounds, id)
// Add node to layout store
layoutMutations.setSource(LayoutSource.Canvas)
void layoutMutations.createNode(id, {
setSource(LayoutSource.Canvas)
void createNode(id, {
position: { x: node.pos[0], y: node.pos[1] },
size: { width: node.size[0], height: node.size[1] },
zIndex: node.order || 0,
@@ -652,8 +655,8 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
spatialIndex.remove(id)
// Remove node from layout store
layoutMutations.setSource(LayoutSource.Canvas)
void layoutMutations.deleteNode(id)
setSource(LayoutSource.Canvas)
void deleteNode(id)
// Clean up all tracking references
nodeRefs.delete(id)

View File

@@ -6,7 +6,8 @@ import {
} from '@/lib/litegraph/src/constants'
import type { UUID } from '@/lib/litegraph/src/utils/uuid'
import { createUuidv4, zeroUuid } from '@/lib/litegraph/src/utils/uuid'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import type { LayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { DragAndScaleState } from './DragAndScale'
@@ -190,6 +191,9 @@ export class LGraph
nodes_executedAction: string[] = []
extra: LGraphExtra = {}
/** Layout mutations instance for this graph */
layoutMutations!: LayoutMutations
/** @deprecated Deserialising a workflow sets this unused property. */
version?: number
@@ -276,6 +280,9 @@ export class LGraph
constructor(o?: ISerialisedGraph | SerialisableGraph) {
if (LiteGraph.debug) console.log('Graph created')
// Get layout mutations composable
this.layoutMutations = useLayoutMutations()
/** @see MapProxyHandler */
const links = this._links
MapProxyHandler.bindAllMethods(links)
@@ -1353,8 +1360,8 @@ export class LGraph
this.reroutes.set(rerouteId, reroute)
// Register reroute in Layout Store for spatial tracking
layoutMutations.setSource(LayoutSource.Canvas)
layoutMutations.createReroute(
this.layoutMutations.setSource(LayoutSource.Canvas)
this.layoutMutations.createReroute(
String(rerouteId),
{ x: pos[0], y: pos[1] },
before.parentId ? String(before.parentId) : undefined,
@@ -1436,8 +1443,8 @@ export class LGraph
reroutes.delete(id)
// Delete reroute from Layout Store
layoutMutations.setSource(LayoutSource.Canvas)
layoutMutations.deleteReroute(id)
this.layoutMutations.setSource(LayoutSource.Canvas)
this.layoutMutations.deleteReroute(id)
// This does not belong here; it should be handled by the caller, or run by a remove-many API.
// https://github.com/Comfy-Org/litegraph.js/issues/898
@@ -2263,8 +2270,8 @@ export class LGraph
if (!reroute.validateLinks(this._links, this.floatingLinks)) {
this.reroutes.delete(reroute.id)
// Clean up layout store
layoutMutations.setSource(LayoutSource.Canvas)
layoutMutations.deleteReroute(reroute.id)
this.layoutMutations.setSource(LayoutSource.Canvas)
this.layoutMutations.deleteReroute(reroute.id)
}
}

View File

@@ -5,7 +5,6 @@ import {
calculateInputSlotPosFromSlot,
calculateOutputSlotPos
} from '@/renderer/core/canvas/litegraph/SlotCalculations'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { DragAndScale } from './DragAndScale'
@@ -2845,8 +2844,8 @@ export class LGraphNode
graph._links.set(link.id, link)
// Register link in Layout Store for spatial tracking
layoutMutations.setSource(LayoutSource.Canvas)
layoutMutations.createLink(
graph.layoutMutations.setSource(LayoutSource.Canvas)
graph.layoutMutations.createLink(
link.id,
this.id,
outputIndex,

View File

@@ -2,7 +2,7 @@ import {
SUBGRAPH_INPUT_ID,
SUBGRAPH_OUTPUT_ID
} from '@/lib/litegraph/src/constants'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { LGraphNode, NodeId } from './LGraphNode'
@@ -22,6 +22,8 @@ import type {
SubgraphIO
} from './types/serialisation'
const layoutMutations = useLayoutMutations()
export type LinkId = number
export type SerialisedLLinkArray = [

View File

@@ -1,4 +1,4 @@
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import { LGraphBadge } from './LGraphBadge'
@@ -18,6 +18,8 @@ import type {
import { distance, isPointInRect } from './measure'
import type { Serialisable, SerialisableReroute } from './types/serialisation'
const layoutMutations = useLayoutMutations()
export type RerouteId = number
/** The input or output slot that an incomplete reroute link is connected to. */

View File

@@ -8,7 +8,6 @@ import log from 'loglevel'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import {
type LayoutMutations,
LayoutSource,
type NodeId,
type NodeLayout,
@@ -18,25 +17,70 @@ import {
const logger = log.getLogger('LayoutMutations')
class LayoutMutationsImpl implements LayoutMutations {
export interface LayoutMutations {
// Single node operations (synchronous, CRDT-ready)
moveNode(nodeId: NodeId, position: Point): void
resizeNode(nodeId: NodeId, size: Size): void
setNodeZIndex(nodeId: NodeId, zIndex: number): void
// Node lifecycle operations
createNode(nodeId: NodeId, layout: Partial<NodeLayout>): void
deleteNode(nodeId: NodeId): void
// Link operations
createLink(
linkId: string | number,
sourceNodeId: string | number,
sourceSlot: number,
targetNodeId: string | number,
targetSlot: number
): void
deleteLink(linkId: string | number): void
// Reroute operations
createReroute(
rerouteId: string | number,
position: Point,
parentId?: string | number,
linkIds?: (string | number)[]
): void
deleteReroute(rerouteId: string | number): void
moveReroute(
rerouteId: string | number,
position: Point,
previousPosition: Point
): void
// Stacking operations
bringNodeToFront(nodeId: NodeId): void
// Source tracking
setSource(source: LayoutSource): void
setActor(actor: string): void
}
/**
* Composable for accessing layout mutations with clean destructuring API
*/
export function useLayoutMutations(): LayoutMutations {
/**
* Set the current mutation source
*/
setSource(source: LayoutSource): void {
const setSource = (source: LayoutSource): void => {
layoutStore.setSource(source)
}
/**
* Set the current actor (for CRDT)
*/
setActor(actor: string): void {
const setActor = (actor: string): void => {
layoutStore.setActor(actor)
}
/**
* Move a node to a new position
*/
moveNode(nodeId: NodeId, position: Point): void {
const moveNode = (nodeId: NodeId, position: Point): void => {
const existing = layoutStore.getNodeLayoutRef(nodeId).value
if (!existing) return
@@ -55,7 +99,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Resize a node
*/
resizeNode(nodeId: NodeId, size: Size): void {
const resizeNode = (nodeId: NodeId, size: Size): void => {
const existing = layoutStore.getNodeLayoutRef(nodeId).value
if (!existing) return
@@ -74,7 +118,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Set node z-index
*/
setNodeZIndex(nodeId: NodeId, zIndex: number): void {
const setNodeZIndex = (nodeId: NodeId, zIndex: number): void => {
const existing = layoutStore.getNodeLayoutRef(nodeId).value
if (!existing) return
@@ -93,7 +137,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Create a new node
*/
createNode(nodeId: NodeId, layout: Partial<NodeLayout>): void {
const createNode = (nodeId: NodeId, layout: Partial<NodeLayout>): void => {
const fullLayout: NodeLayout = {
id: nodeId,
position: layout.position ?? { x: 0, y: 0 },
@@ -122,7 +166,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Delete a node
*/
deleteNode(nodeId: NodeId): void {
const deleteNode = (nodeId: NodeId): void => {
const existing = layoutStore.getNodeLayoutRef(nodeId).value
if (!existing) return
@@ -140,7 +184,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Bring a node to the front (highest z-index)
*/
bringNodeToFront(nodeId: NodeId): void {
const bringNodeToFront = (nodeId: NodeId): void => {
// Get all nodes to find the highest z-index
const allNodes = layoutStore.getAllNodes().value
let maxZIndex = 0
@@ -152,19 +196,19 @@ class LayoutMutationsImpl implements LayoutMutations {
}
// Set this node's z-index to be one higher than the current max
this.setNodeZIndex(nodeId, maxZIndex + 1)
setNodeZIndex(nodeId, maxZIndex + 1)
}
/**
* Create a new link
*/
createLink(
const createLink = (
linkId: string | number,
sourceNodeId: string | number,
sourceSlot: number,
targetNodeId: string | number,
targetSlot: number
): void {
): void => {
// Normalize node IDs to strings
const normalizedSourceNodeId = String(sourceNodeId)
const normalizedTargetNodeId = String(targetNodeId)
@@ -191,7 +235,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Delete a link
*/
deleteLink(linkId: string | number): void {
const deleteLink = (linkId: string | number): void => {
logger.debug('Deleting link:', Number(linkId))
layoutStore.applyOperation({
type: 'deleteLink',
@@ -206,12 +250,12 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Create a new reroute
*/
createReroute(
const createReroute = (
rerouteId: string | number,
position: Point,
parentId?: string | number,
linkIds: (string | number)[] = []
): void {
): void => {
logger.debug('Creating reroute:', {
rerouteId: Number(rerouteId),
position,
@@ -234,7 +278,7 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Delete a reroute
*/
deleteReroute(rerouteId: string | number): void {
const deleteReroute = (rerouteId: string | number): void => {
logger.debug('Deleting reroute:', Number(rerouteId))
layoutStore.applyOperation({
type: 'deleteReroute',
@@ -249,11 +293,11 @@ class LayoutMutationsImpl implements LayoutMutations {
/**
* Move a reroute
*/
moveReroute(
const moveReroute = (
rerouteId: string | number,
position: Point,
previousPosition: Point
): void {
): void => {
logger.debug('Moving reroute:', {
rerouteId: Number(rerouteId),
from: previousPosition,
@@ -270,7 +314,20 @@ class LayoutMutationsImpl implements LayoutMutations {
actor: layoutStore.getCurrentActor()
})
}
}
// Create singleton instance
export const layoutMutations = new LayoutMutationsImpl()
return {
setSource,
setActor,
moveNode,
resizeNode,
setNodeZIndex,
createNode,
deleteNode,
bringNodeToFront,
createLink,
deleteLink,
createReroute,
deleteReroute,
moveReroute
}
}

View File

@@ -1,31 +0,0 @@
/**
* Main composable for accessing the layout system
*
* Provides unified access to the layout store and mutation API.
*/
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import type { Bounds, NodeId, Point } from '@/renderer/core/layout/types'
/**
* Main composable for accessing the layout system
*/
export function useLayout() {
return {
// Store access
store: layoutStore,
// Mutation API
mutations: layoutMutations,
// Reactive accessors
getNodeLayoutRef: (nodeId: NodeId) => layoutStore.getNodeLayoutRef(nodeId),
getAllNodes: () => layoutStore.getAllNodes(),
getNodesInBounds: (bounds: Bounds) => layoutStore.getNodesInBounds(bounds),
// Non-reactive queries (for performance)
queryNodeAtPoint: (point: Point) => layoutStore.queryNodeAtPoint(point),
queryNodesInBounds: (bounds: Bounds) =>
layoutStore.queryNodesInBounds(bounds)
}
}

View File

@@ -468,49 +468,6 @@ export interface LayoutStore {
getCurrentActor(): string
}
// Simplified mutation API
export interface LayoutMutations {
// Single node operations (synchronous, CRDT-ready)
moveNode(nodeId: NodeId, position: Point): void
resizeNode(nodeId: NodeId, size: Size): void
setNodeZIndex(nodeId: NodeId, zIndex: number): void
// Node lifecycle operations
createNode(nodeId: NodeId, layout: Partial<NodeLayout>): void
deleteNode(nodeId: NodeId): void
// Link operations
createLink(
linkId: string | number,
sourceNodeId: string | number,
sourceSlot: number,
targetNodeId: string | number,
targetSlot: number
): void
deleteLink(linkId: string | number): void
// Reroute operations
createReroute(
rerouteId: string | number,
position: Point,
parentId?: string | number,
linkIds?: (string | number)[]
): void
deleteReroute(rerouteId: string | number): void
moveReroute(
rerouteId: string | number,
position: Point,
previousPosition: Point
): void
// Stacking operations
bringNodeToFront(nodeId: NodeId): void
// Source tracking
setSource(source: LayoutSource): void
setActor(actor: string): void // For CRDT
}
// CRDT-ready operation log (for future CRDT integration)
export interface OperationLog {
operations: LayoutOperation[]

View File

@@ -6,7 +6,7 @@
*/
import { computed, inject } from 'vue'
import { layoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/LayoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/LayoutStore'
import { LayoutSource, type Point } from '@/renderer/core/layout/types'
@@ -16,7 +16,7 @@ import { LayoutSource, type Point } from '@/renderer/core/layout/types'
*/
export function useNodeLayout(nodeId: string) {
const store = layoutStore
const mutations = layoutMutations
const mutations = useLayoutMutations()
// Get transform utilities from TransformPane if available
const transformState = inject('transformState') as

View File

@@ -281,6 +281,21 @@ LGraph {
"id": "b4e984f1-b421-4d24-b8b4-ff895793af13",
"iteration": 0,
"last_update_time": 0,
"layoutMutations": {
"bringNodeToFront": [Function],
"createLink": [Function],
"createNode": [Function],
"createReroute": [Function],
"deleteLink": [Function],
"deleteNode": [Function],
"deleteReroute": [Function],
"moveNode": [Function],
"moveReroute": [Function],
"resizeNode": [Function],
"setActor": [Function],
"setNodeZIndex": [Function],
"setSource": [Function],
},
"links": Map {},
"list_of_graphcanvas": null,
"nodes_actioning": [],