UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

180 lines (179 loc) 7.51 kB
import { batch } from "@preact/signals-core"; import { ESelectionStrategy } from "../services/selection/types"; import { selectBlockById } from "../store/block/selectors"; import { selectConnectionById } from "../store/connection/selectors"; import { getBlocksRect, getElementsRect, startAnimation } from "../utils/functions"; export class PublicGraphApi { constructor(graph) { this.graph = graph; // noop } /** * Zooms to blocks * @param blockIds - block ids to zoom to * @param zoomConfig - {@link ZoomConfig} zoom config * @returns {boolean} true if zoom is successful, false otherwise * * @example * ```typescript * graph.zoomToBlocks([block1.id, block2.id]); * graph.zoomToBlocks([block1.id, block2.id], { transition: 1000, padding: 50 }); * ``` */ zoomToBlocks(blockIds, zoomConfig) { const blocks = this.graph.rootStore.blocksList.getBlocks(blockIds); if (blocks.length === 0) { return false; } const blocksRect = getBlocksRect(blocks); this.zoomToRect(blocksRect, zoomConfig); return true; } /** * Zooms to GraphComponent instances * @param instances - {@link GraphComponent} instances to zoom to * @param zoomConfig - {@link ZoomConfig} zoom config * @returns {boolean} true if zoom is successful, false otherwise * * @example * ```typescript * graph.zoomToElements([component1, component2]); * graph.zoomToElements([component1, component2], { transition: 1000, padding: 50 }); * ``` */ zoomToElements(elements, zoomConfig) { if (elements.length === 0) { return false; } const elementsRect = getElementsRect(elements); this.zoomToRect(elementsRect, zoomConfig); return true; } /** * Zooms to fit all blocks in the viewport. This method is asynchronous and waits * for the usableRect to be ready before performing the zoom operation. * * @param zoomConfig - Configuration for zoom transition and padding * @returns Promise that resolves when zoom operation is complete */ zoomToViewPort(zoomConfig) { this.graph.hitTest.waitUsableRectUpdate((rect) => { let zoomRect = rect; // if rect is empty we need apply usable rect gap // or zoom to center will be applied at position {0,0} with width/height 0/0 if (rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0) { zoomRect = { x: 0 - this.graph.graphConstants.system.USABLE_RECT_GAP, y: 0 - this.graph.graphConstants.system.USABLE_RECT_GAP, width: 0 + this.graph.graphConstants.system.USABLE_RECT_GAP * 2, height: 0 + this.graph.graphConstants.system.USABLE_RECT_GAP * 2, }; } this.zoomToRect(zoomRect, zoomConfig); }); } /** * Zooms to the specified rectangle in camera coordinates * * Zooms the camera to fit the specified rectangle in the viewport. The rectangle will be scaled * to fill the visible area (respecting camera insets) and centered in the viewport. * * @param rect - {@link TRect} rectangle to zoom to in camera coordinates * @param zoomConfig - {@link ZoomConfig} zoom config * @returns {undefined} * * @example * ```typescript * graph.zoomToRect({ x: 0, y: 0, width: 100, height: 100 }); * graph.zoomToRect({ x: 0, y: 0, width: 100, height: 100 }, { transition: 1000, padding: 50 }); */ zoomToRect(rect, zoomConfig) { const transition = zoomConfig?.transition || 0; const padding = zoomConfig?.padding || 0; const cameraRectInit = this.graph.cameraService.getCameraRect(); const cameraScaleInit = this.graph.cameraService.getCameraScale(); // Compute scale against visible viewport (respectInsets) so content fits actual visible area const endScale = this.graph.cameraService.getScaleRelativeDimensions(rect.width + padding * 2, rect.height + padding * 2, { respectInsets: true }); const xyPosition = this.graph.cameraService.getXYRelativeCenterDimensions({ x: rect.x - padding, y: rect.y - padding, width: rect.width + padding * 2, height: rect.height + padding * 2, }, endScale, { respectInsets: true }); if (!transition) { this.graph.cameraService.set({ ...xyPosition, scale: endScale }); return; } startAnimation(transition, (progress) => { const x = cameraRectInit.x + (xyPosition.x - cameraRectInit.x) * progress; const y = cameraRectInit.y + (xyPosition.y - cameraRectInit.y) * progress; const scale = cameraScaleInit + (endScale - cameraScaleInit) * progress; this.graph.cameraService.set({ x, y, scale }); }); } getGraphColors() { return this.graph.graphColors; } updateGraphColors(colors) { this.graph.setColors(colors); } getGraphConstants() { return this.graph.graphConstants; } updateGraphConstants(constants) { this.graph.setConstants(constants); } isGraphEmpty() { return this.graph.rootStore.blocksList.$blocksMap.value.size === 0; } setSetting(flagPath, value) { this.graph.rootStore.settings.setConfigFlag(flagPath, value); } setCurrentConfigurationName(newName) { this.graph.rootStore.configurationName = newName; } deleteSelected() { batch(() => { this.graph.rootStore.connectionsList.deleteSelectedConnections(); this.graph.rootStore.blocksList.deleteSelectedBlocks(); }); } selectBlocks(blockIds, selected, strategy = ESelectionStrategy.REPLACE) { this.graph.rootStore.blocksList.updateBlocksSelection(blockIds, selected, strategy); } updateBlock(block) { const blockStore = selectBlockById(this.graph, block.id); blockStore?.updateBlock(block); } addBlock(block, selectionOptions) { const newBlockId = this.graph.rootStore.blocksList.addBlock(block); if (selectionOptions !== undefined) { this.graph.rootStore.blocksList.updateBlocksSelection([newBlockId], selectionOptions.selected !== undefined ? selectionOptions.selected : true, selectionOptions.strategy); } return newBlockId; } setAnchorSelection(blockId, anchorId, selected) { this.graph.rootStore.blocksList.setAnchorSelection(blockId, anchorId, selected); } selectConnections(connectionIds, selected, strategy = ESelectionStrategy.REPLACE) { batch(() => { this.graph.rootStore.connectionsList.setConnectionsSelection(connectionIds, selected, strategy); }); } updateConnection(id, connection) { const connectionStore = selectConnectionById(this.graph, id); connectionStore.updateConnection(connection); } addConnection(connection) { return this.graph.rootStore.connectionsList.addConnection(connection); } getBlockById(blockId) { return selectBlockById(this.graph, blockId)?.asTBlock(); } getUsableRect() { return this.graph.hitTest.getUsableRect(); } unsetSelection() { this.graph.rootStore.selectionService.resetAllSelections(); } }