import { nextTick } from 'vue' import type { EdgeAddChange, EdgeChange, EdgeRemoveChange, EdgeSelectionChange, ElementChange, FlowElement, FlowElements, GraphEdge, GraphNode, NodeAddChange, NodeChange, NodeRemoveChange, NodeSelectionChange, StyleFunc, Styles, } from '../types' import { isDef, isGraphNode } from '.' function handleParentExpand(updateItem: GraphNode, parent: GraphNode) { if (parent) { const extendWidth = updateItem.position.x + updateItem.dimensions.width - parent.dimensions.width const extendHeight = updateItem.position.y + updateItem.dimensions.height - parent.dimensions.height if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) { let parentStyles: Styles = {} if (typeof parent.style === 'function') { parentStyles = { ...parent.style(parent) } } else if (parent.style) { parentStyles = { ...parent.style } } parentStyles.width = parentStyles.width ?? `${parent.dimensions.width}px` parentStyles.height = parentStyles.height ?? `${parent.dimensions.height}px` if (extendWidth > 0) { if (typeof parentStyles.width === 'string') { const currWidth = Number(parentStyles.width.replace('px', '')) parentStyles.width = `${currWidth + extendWidth}px` } else { parentStyles.width += extendWidth } } if (extendHeight > 0) { if (typeof parentStyles.height === 'string') { const currWidth = Number(parentStyles.height.replace('px', '')) parentStyles.height = `${currWidth + extendHeight}px` } else { parentStyles.height += extendHeight } } if (updateItem.position.x < 0) { const xDiff = Math.abs(updateItem.position.x) parent.position.x = parent.position.x - xDiff if (typeof parentStyles.width === 'string') { const currWidth = Number(parentStyles.width.replace('px', '')) parentStyles.width = `${currWidth + xDiff}px` } else { parentStyles.width += xDiff } updateItem.position.x = 0 } if (updateItem.position.y < 0) { const yDiff = Math.abs(updateItem.position.y) parent.position.y = parent.position.y - yDiff if (typeof parentStyles.height === 'string') { const currWidth = Number(parentStyles.height.replace('px', '')) parentStyles.height = `${currWidth + yDiff}px` } else { parentStyles.height += yDiff } updateItem.position.y = 0 } parent.dimensions.width = Number(parentStyles.width.toString().replace('px', '')) parent.dimensions.height = Number(parentStyles.height.toString().replace('px', '')) if (typeof parent.style === 'function') { parent.style = (p) => { const styleFunc = parent.style as StyleFunc return { ...styleFunc(p), ...parentStyles, } } } else { parent.style = { ...parent.style, ...parentStyles, } } } } } export function applyChanges< T extends FlowElement = FlowElement, C extends ElementChange = T extends GraphNode ? NodeChange : EdgeChange, >(changes: C[], elements: T[]): T[] { const addRemoveChanges = changes.filter((c) => c.type === 'add' || c.type === 'remove') as ( | NodeAddChange | EdgeAddChange | NodeRemoveChange | EdgeRemoveChange )[] for (const change of addRemoveChanges) { if (change.type === 'add') { const index = elements.findIndex((el) => el.id === change.item.id) if (index === -1) { elements.push(change.item) } } else if (change.type === 'remove') { const index = elements.findIndex((el) => el.id === change.id) if (index !== -1) { elements.splice(index, 1) } } } const elementIds = elements.map((el) => el.id) for (const element of elements) { for (const currentChange of changes) { if ((currentChange).id !== element.id) { continue } switch (currentChange.type) { case 'select': element.selected = currentChange.selected break case 'position': if (isGraphNode(element)) { if (typeof currentChange.position !== 'undefined') { element.position = currentChange.position } if (typeof currentChange.dragging !== 'undefined') { element.dragging = currentChange.dragging } if (element.expandParent && element.parentNode) { const parent = elements[elementIds.indexOf(element.parentNode)] if (parent && isGraphNode(parent)) { handleParentExpand(element, parent) } } } break case 'dimensions': if (isGraphNode(element)) { if (typeof currentChange.dimensions !== 'undefined') { element.dimensions = currentChange.dimensions } if (typeof currentChange.updateStyle !== 'undefined') { element.style = { ...(element.style || {}), width: `${currentChange.dimensions?.width}px`, height: `${currentChange.dimensions?.height}px`, } } if (typeof currentChange.resizing !== 'undefined') { element.resizing = currentChange.resizing } if (element.expandParent && element.parentNode) { const parent = elements[elementIds.indexOf(element.parentNode)] if (parent && isGraphNode(parent)) { if (!parent.initialized) { nextTick(() => { handleParentExpand(element, parent) }) } else { handleParentExpand(element, parent) } } } if (!element.initialized) { element.initialized = true } } break } } } return elements } /** @deprecated Use store instance and call `applyChanges` with template-ref or the one received by `onPaneReady` instead */ export function applyEdgeChanges(changes: EdgeChange[], edges: GraphEdge[]) { return applyChanges(changes, edges) } /** @deprecated Use store instance and call `applyChanges` with template-ref or the one received by `onPaneReady` instead */ export function applyNodeChanges(changes: NodeChange[], nodes: GraphNode[]) { return applyChanges(changes, nodes) } export function createSelectionChange(id: string, selected: boolean): NodeSelectionChange | EdgeSelectionChange { return { id, type: 'select', selected, } } export function createAdditionChange< T extends GraphNode | GraphEdge = GraphNode, C extends NodeAddChange | EdgeAddChange = T extends GraphNode ? NodeAddChange : EdgeAddChange, >(item: T): C { return { item, type: 'add', } } export function createNodeRemoveChange(id: string): NodeRemoveChange { return { id, type: 'remove', } } export function createEdgeRemoveChange( id: string, source: string, target: string, sourceHandle?: string | null, targetHandle?: string | null, ): EdgeRemoveChange { return { id, source, target, sourceHandle: sourceHandle || null, targetHandle: targetHandle || null, type: 'remove', } } export function getSelectionChanges(elements: FlowElements, selectedIds: string[]) { return elements.reduce( (res, item) => { let willBeSelected = selectedIds.includes(item.id) if (isDef(item.selectable) && !item.selectable) { willBeSelected = false } const key = isGraphNode(item) ? 'changedNodes' : 'changedEdges' if (!item.selected && willBeSelected) { res[key].push(createSelectionChange(item.id, true)) } else if (item.selected && !willBeSelected) { res[key].push(createSelectionChange(item.id, false)) } return res }, { changedNodes: [], changedEdges: [] } as { changedNodes: NodeSelectionChange[]; changedEdges: EdgeSelectionChange[] }, ) }