coze-studio/frontend/packages/workflow/fabric-canvas/src/hooks/use-fabric-editor.tsx

328 lines
7.5 KiB
TypeScript

/*
* 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<HTMLCanvasElement>;
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,
},
};
};