/* * Copyright 2025 coze-dev Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable @coze-arch/max-line-per-function */ import { useEffect, useMemo } from 'react'; import { type InputVariable } from '@coze-workflow/base/types'; import { REF_VARIABLE_ID_PREFIX, type FabricClickEvent, type FabricObjectWithCustomProps, type FabricSchema, type VariableRef, } from '../typings'; import { useViewport } from './use-viewport'; import { useSnapMove } from './use-snap-move'; import { useRedoUndo } from './use-redo-undo'; import { usePosition } from './use-position'; import { useMousePosition } from './use-mouse-position'; import { useInlineTextAdd } from './use-inline-text-add'; import { useInitCanvas } from './use-init-canvas'; import { useImagAdd } from './use-img-add'; import { useGroup } from './use-group'; import { useFreePencil } from './use-free-pencil'; import { useDragAdd } from './use-drag-add'; import { useCopyPaste } from './use-copy-paste'; import { useCommonOperation } from './use-common-operation'; import { useCanvasResize } from './use-canvas-resize'; import { useCanvasChange } from './use-canvas-change'; import { useBackground } from './use-background'; import { useAlign } from './use-align'; import { useActiveObjectChange } from './use-active-object-change'; export const useFabricEditor = ({ ref, schema: _schema, onChange, maxWidth, maxHeight, startInit, maxZoom = 3, minZoom = 0.3, readonly = false, onShapeAdded, variables, id, helpLineLayerId, onClick, }: { id?: string; helpLineLayerId: string; ref: React.RefObject; schema: FabricSchema; onChange?: (schema: FabricSchema) => void; maxWidth: number; maxHeight: number; startInit: boolean; maxZoom?: number; minZoom?: number; readonly?: boolean; onShapeAdded?: (data: { element: FabricObjectWithCustomProps }) => void; variables?: InputVariable[]; onClick?: (e: FabricClickEvent) => void; }) => { const schema: FabricSchema = useMemo(() => { /** * 兼容历史数据 * 删除时机,见 apps/fabric-canvas-node-render/utils/replace-ref-value.ts 注释 */ if ( !_schema?.customVariableRefs && (_schema?.objects?.filter(d => d.customVariableName)?.length ?? 0) > 0 ) { const refObjects = _schema?.objects?.filter(d => d.customVariableName); const newRefs: VariableRef[] = refObjects?.map(d => ({ variableId: d.customId .replace(`${REF_VARIABLE_ID_PREFIX}-img-`, '') .replace(`${REF_VARIABLE_ID_PREFIX}-text-`, ''), objectId: d.customId, variableName: d.customVariableName as string, })) ?? []; return { ..._schema, customVariableRefs: newRefs, }; } return _schema; }, [_schema]); const objectLength = useMemo(() => schema.objects.length, [schema]); /** * 最大可添加元素数量限制 */ const MAX_OBJECT_LENGTH = 50; const couldAddNewObject = useMemo( () => objectLength < MAX_OBJECT_LENGTH, [objectLength], ); const { resize, scale } = useCanvasResize({ maxWidth, maxHeight, width: schema.width, height: schema.height, }); // 初始化 fabric canvas const { canvas, loadFromJSON } = useInitCanvas({ startInit, ref: ref.current, schema, resize, scale, readonly, onClick, }); const { viewport, setViewport, zoomToPoint } = useViewport({ canvas, minZoom, maxZoom, schema, }); const { mousePosition } = useMousePosition({ canvas }); const { group, unGroup } = useGroup({ canvas, }); const { startListen, stopListen, addRefObjectByVariable, updateRefByObjectId, customVariableRefs, } = useCanvasChange({ variables, canvas, schema, onChange: json => { onChange?.(json); pushOperation(json); }, }); useSnapMove({ canvas, helpLineLayerId, scale }); const { copy, paste, disabledPaste } = useCopyPaste({ canvas, mousePosition, couldAddNewObject, customVariableRefs, addRefObjectByVariable, variables, }); const { pushOperation, undo, redo, disabledRedo, disabledUndo, redoUndoing } = useRedoUndo({ id, schema, loadFromJSON, startListen, stopListen, onChange, }); const { activeObjects, activeObjectsPopPosition, setActiveObjectsProps, isActiveObjectsInBack, isActiveObjectsInFront, } = useActiveObjectChange({ canvas, scale, }); const { alignLeft, alignRight, alignCenter, alignTop, alignBottom, alignMiddle, verticalAverage, horizontalAverage, } = useAlign({ canvas, selectObjects: activeObjects, }); useEffect(() => { if (canvas) { resize(canvas); } }, [resize, canvas]); const { backgroundColor, setBackgroundColor } = useBackground({ canvas, schema, }); const { moveActiveObject, removeActiveObjects, moveTo, discardActiveObject, resetWidthHeight, } = useCommonOperation({ canvas, }); const { addImage } = useImagAdd({ canvas, onShapeAdded, }); const { enterDragAddElement, exitDragAddElement } = useDragAdd({ canvas, onShapeAdded, }); const { enterFreePencil, exitFreePencil } = useFreePencil({ canvas, }); const { enterAddInlineText, exitAddInlineText } = useInlineTextAdd({ canvas, onShapeAdded, }); const { allObjectsPositionInScreen } = usePosition({ canvas, scale, viewport, }); return { canvas, canvasSettings: { width: schema.width, height: schema.height, backgroundColor, }, state: { viewport, cssScale: scale, activeObjects, activeObjectsPopPosition, objectLength, couldAddNewObject, disabledUndo, disabledRedo, redoUndoing, disabledPaste, isActiveObjectsInBack, isActiveObjectsInFront, canvasWidth: canvas?.getElement().getBoundingClientRect().width, canvasHeight: canvas?.getElement().getBoundingClientRect().height, customVariableRefs, allObjectsPositionInScreen, }, sdk: { discardActiveObject, setActiveObjectsProps, setBackgroundColor, moveToFront: () => { moveTo('front'); }, moveToBackend: () => { moveTo('backend'); }, moveToFrontOne: () => { moveTo('front-one'); }, moveToBackendOne: () => { moveTo('backend-one'); }, zoomToPoint, setViewport, moveActiveObject, removeActiveObjects, enterDragAddElement, exitDragAddElement, enterFreePencil, exitFreePencil, enterAddInlineText, exitAddInlineText, addImage, undo, redo, copy, paste, group, unGroup, alignLeft, alignRight, alignCenter, alignTop, alignBottom, alignMiddle, verticalAverage, horizontalAverage, resetWidthHeight, addRefObjectByVariable, updateRefByObjectId, }, }; };