mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 00:20:07 +00:00
* feat: Implement CRDT-based layout system for Vue nodes Major refactor to solve snap-back issues and create single source of truth for node positions: - Add Yjs-based CRDT layout store for conflict-free position management - Implement layout mutations service with clean API - Create Vue composables for layout access and node dragging - Add one-way sync from layout store to LiteGraph - Disable LiteGraph dragging when Vue nodes mode is enabled - Add z-index management with bring-to-front on node interaction - Add comprehensive TypeScript types for layout system - Include unit tests for layout store operations - Update documentation to reflect CRDT architecture This provides a solid foundation for both single-user performance and future real-time collaboration features. Co-Authored-By: Claude <noreply@anthropic.com> * style: Apply linter fixes to layout system * fix: Remove unnecessary README files and revert services README - Remove unnecessary types/README.md file - Revert unrelated changes to services/README.md - Keep only relevant documentation for the layout system implementation These were issues identified during PR review that needed to be addressed. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Clean up layout store and implement proper CRDT operations - Created dedicated layoutOperations.ts with production-grade CRDT interfaces - Integrated existing QuadTree spatial index instead of simple cache - Split composables into separate files (useLayout, useNodeLayout, useLayoutSync) - Cleaned up operation handlers using specific types instead of Extract - Added proper operation interfaces with type guards and extensibility - Updated all type references to use new operation structure The layout store now properly uses the existing QuadTree infrastructure for efficient spatial queries and follows CRDT best practices with well-defined operation interfaces. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Extract services and split composables for better organization - Created SpatialIndexManager to handle QuadTree operations separately - Added LayoutAdapter interface for CRDT abstraction (Yjs, mock implementations) - Split GraphNodeManager into focused composables: - useNodeWidgets: Widget state and callback management - useNodeChangeDetection: RAF-based geometry change detection - useNodeState: Node visibility and reactive state management - Extracted constants for magic numbers and configuration values - Updated layout store to use SpatialIndexManager and constants This improves code organization, testability, and makes it easier to swap CRDT implementations or mock services for testing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add node slots to layout tree * Revert "Add node slots to layout tree" This reverts commit460493a620. * Remove slots from layoutTypes * Totally not scuffed renderer and adapter * Revert "Totally not scuffed renderer and adapter" This reverts commit2b9d83efb8. * Revert "Remove slots from layoutTypes" This reverts commit18f78ff786. * Reapply "Add node slots to layout tree" This reverts commit236fecb549. * Revert "Add node slots to layout tree" This reverts commit460493a620. * docs: Replace architecture docs with comprehensive ADR - Add ADR-0002 for CRDT-based layout system decision - Follow established ADR template with persuasive reasoning - Include performance benefits, collaboration readiness, and architectural advantages - Update ADR index --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
/**
|
|
* Composable for syncing LiteGraph with the Layout system
|
|
*
|
|
* Implements one-way sync from Layout Store to LiteGraph.
|
|
* The layout store is the single source of truth.
|
|
*/
|
|
import log from 'loglevel'
|
|
import { onUnmounted } from 'vue'
|
|
|
|
import { layoutStore } from '@/stores/layoutStore'
|
|
|
|
// Create a logger for layout debugging
|
|
const logger = log.getLogger('layout')
|
|
// In dev mode, always show debug logs
|
|
if (import.meta.env.DEV) {
|
|
logger.setLevel('debug')
|
|
}
|
|
|
|
/**
|
|
* Composable for syncing LiteGraph with the Layout system
|
|
* This replaces the bidirectional sync with a one-way sync
|
|
*/
|
|
export function useLayoutSync() {
|
|
let unsubscribe: (() => void) | null = null
|
|
|
|
/**
|
|
* Start syncing from Layout system to LiteGraph
|
|
* This is one-way: Layout → LiteGraph only
|
|
*/
|
|
function startSync(canvas: any) {
|
|
if (!canvas?.graph) return
|
|
|
|
// Subscribe to layout changes
|
|
unsubscribe = layoutStore.onChange((change) => {
|
|
logger.debug('Layout sync received change:', {
|
|
source: change.source,
|
|
nodeIds: change.nodeIds,
|
|
type: change.type
|
|
})
|
|
|
|
// Apply changes to LiteGraph regardless of source
|
|
// The layout store is the single source of truth
|
|
for (const nodeId of change.nodeIds) {
|
|
const layout = layoutStore.getNodeLayoutRef(nodeId).value
|
|
if (!layout) continue
|
|
|
|
const liteNode = canvas.graph.getNodeById(parseInt(nodeId))
|
|
if (!liteNode) continue
|
|
|
|
// Update position if changed
|
|
if (
|
|
liteNode.pos[0] !== layout.position.x ||
|
|
liteNode.pos[1] !== layout.position.y
|
|
) {
|
|
logger.debug(`Updating LiteGraph node ${nodeId} position:`, {
|
|
from: { x: liteNode.pos[0], y: liteNode.pos[1] },
|
|
to: layout.position
|
|
})
|
|
liteNode.pos[0] = layout.position.x
|
|
liteNode.pos[1] = layout.position.y
|
|
}
|
|
|
|
// Update size if changed
|
|
if (
|
|
liteNode.size[0] !== layout.size.width ||
|
|
liteNode.size[1] !== layout.size.height
|
|
) {
|
|
liteNode.size[0] = layout.size.width
|
|
liteNode.size[1] = layout.size.height
|
|
}
|
|
}
|
|
|
|
// Trigger single redraw for all changes
|
|
canvas.setDirty(true, true)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Stop syncing
|
|
*/
|
|
function stopSync() {
|
|
if (unsubscribe) {
|
|
unsubscribe()
|
|
unsubscribe = null
|
|
}
|
|
}
|
|
|
|
// Auto-cleanup on unmount
|
|
onUnmounted(() => {
|
|
stopSync()
|
|
})
|
|
|
|
return {
|
|
startSync,
|
|
stopSync
|
|
}
|
|
}
|