chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -42,7 +42,7 @@ import {
|
||||
import { setImageFixed } from '../share';
|
||||
import { useCanvasChange } from './use-canvas-change';
|
||||
|
||||
// 设置元素属性
|
||||
// Set element properties
|
||||
const setElementProps = async ({
|
||||
element,
|
||||
props,
|
||||
@@ -52,7 +52,7 @@ const setElementProps = async ({
|
||||
props: Partial<FabricObjectSchema>;
|
||||
canvas?: Canvas;
|
||||
}): Promise<void> => {
|
||||
// 特化一:img 的属性设置需要设置到 img 元素上,而不是外层包裹的 group
|
||||
// Specialization 1: The attribute settings of img need to be set to the img element, not the outer wrapped group
|
||||
if (
|
||||
element?.isType('group') &&
|
||||
(element as Group)?.getObjects()?.[0]?.isType('image')
|
||||
@@ -62,21 +62,21 @@ const setElementProps = async ({
|
||||
const img = group.getObjects()[0] as FabricImage;
|
||||
const borderRect = group.getObjects()[1] as Rect;
|
||||
|
||||
// 边框颜色设置到 borderRect 上
|
||||
// Set the border color to borderRect
|
||||
if (stroke) {
|
||||
borderRect.set({
|
||||
stroke,
|
||||
});
|
||||
}
|
||||
|
||||
// 边框粗细设置到 borderRect 上
|
||||
// The border thickness is set to borderRect
|
||||
if (typeof strokeWidth === 'number') {
|
||||
borderRect.set({
|
||||
strokeWidth,
|
||||
});
|
||||
}
|
||||
|
||||
// 替换图片
|
||||
// Replace image
|
||||
if (src) {
|
||||
const newImg = document.createElement('img');
|
||||
await new Promise((done, reject) => {
|
||||
@@ -94,7 +94,7 @@ const setElementProps = async ({
|
||||
|
||||
setImageFixed({ element: group });
|
||||
} else {
|
||||
// 特化二:文本与段落切换,需要特化处理
|
||||
// Specialization 2: Text and paragraph switching requires specialized processing
|
||||
const { customType, ...rest } = props;
|
||||
if (
|
||||
customType &&
|
||||
@@ -143,11 +143,11 @@ const setElementProps = async ({
|
||||
elementProps: {
|
||||
...extendsProps,
|
||||
...(props.customType === Mode.INLINE_TEXT
|
||||
? // 块状 -> 单行
|
||||
? // Block - > single row
|
||||
{}
|
||||
: // 单行 -> 块状
|
||||
: // Single Row - > Block
|
||||
{
|
||||
// 单行切块状,尽量保持字体大小不变化
|
||||
// Cut the block in a single line, and try to keep the font size unchanged.
|
||||
fontSize: newFontSize,
|
||||
padding: newFontSize / 4,
|
||||
width: 200,
|
||||
@@ -156,30 +156,30 @@ const setElementProps = async ({
|
||||
},
|
||||
});
|
||||
|
||||
// 如果还有别的属性,设置到新 element 上
|
||||
// If there are other properties, set them to the new element
|
||||
if (Object.keys(rest).length > 0) {
|
||||
newElement?.set(rest);
|
||||
}
|
||||
|
||||
// 添加新的,顺序不能错,否则删除时引用关系会被判定为无用关系而被删除掉
|
||||
// Add new ones in the correct order, otherwise the reference relationship will be determined to be useless and deleted when deleting.
|
||||
canvas?.add(newElement as FabricObject);
|
||||
// 删掉老的
|
||||
// Delete the old one
|
||||
canvas?.remove(oldElement);
|
||||
|
||||
canvas?.discardActiveObject();
|
||||
canvas?.setActiveObject(newElement as FabricObject);
|
||||
canvas?.requestRenderAll();
|
||||
|
||||
// 普通的属性设置
|
||||
// Normal property settings
|
||||
} else {
|
||||
const { fontFamily } = props;
|
||||
// 特化三: 字体需要异步加载
|
||||
// Specialization 3: Fonts need to be loaded asynchronously
|
||||
if (fontFamily) {
|
||||
await loadFont(fontFamily);
|
||||
}
|
||||
/**
|
||||
* textBox 比较恶心,不知道什么时机会给每个字都生成样式文件(对应 styles)
|
||||
* 这里主动清除下,否则字体相关的设置(fontSize、fontFamily...)不生效
|
||||
* textBox is disgusting. I don't know when to generate a style file for each word (corresponding styles).
|
||||
* Take the initiative to clear it here, otherwise the font-related settings (fontSize, fontFamily...) will not take effect
|
||||
*/
|
||||
if (element?.isType('textbox')) {
|
||||
element?.set({
|
||||
@@ -187,7 +187,7 @@ const setElementProps = async ({
|
||||
});
|
||||
}
|
||||
|
||||
// 特化四:padding = fontSize/2 , 避免文本上下被截断
|
||||
// Specialization 4: padding = fontSize/2, to avoid text being truncated up and down
|
||||
if (element?.isType('textbox') && typeof props.fontSize === 'number') {
|
||||
element?.set({
|
||||
padding: props.fontSize / 4,
|
||||
@@ -285,9 +285,9 @@ export const useActiveObjectChange = ({
|
||||
if (selected) {
|
||||
selected.set(selectedBorderProps);
|
||||
/**
|
||||
* 为什么禁用选中多元素的控制点?
|
||||
* 因为直线不期望有旋转,旋转会影响控制点的计算逻辑。
|
||||
* 想要放开这个限制,需要在直线的控制点内考虑旋转 & 缩放因素
|
||||
* Why disable control points with multiple elements selected?
|
||||
* Since a straight line does not expect rotation, rotation affects the computational logic of the control points.
|
||||
* To remove this restriction, you need to consider the rotation & scaling factor within the control points of the line
|
||||
*/
|
||||
if (selected.isType('activeselection')) {
|
||||
selected.setControlsVisibility({
|
||||
@@ -318,7 +318,7 @@ export const useActiveObjectChange = ({
|
||||
};
|
||||
}, [canvas]);
|
||||
|
||||
// 窗口大小变化时,修正下位置
|
||||
// When the window size changes, correct the position
|
||||
useEffect(() => {
|
||||
_setActiveObjectsPopPosition();
|
||||
}, [scale]);
|
||||
@@ -361,7 +361,7 @@ export const useActiveObjectChange = ({
|
||||
canvas?.fire('object:modified');
|
||||
};
|
||||
|
||||
// 实现 shift 水平/垂直移动
|
||||
// To shift horizontally/vertically
|
||||
useEffect(() => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
@@ -369,39 +369,39 @@ export const useActiveObjectChange = ({
|
||||
let originalPos = { left: 0, top: 0 };
|
||||
|
||||
const disposers = [
|
||||
// 监听对象移动开始事件
|
||||
// Listening object movement start event
|
||||
canvas.on('object:moving', function (e) {
|
||||
const obj = e.target;
|
||||
// 手动 canvas.fire('object:moving') 获取不到 obj
|
||||
// Manual canvas.fire ('object: moving') cannot get obj
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是第一次移动,记录对象的原始位置
|
||||
// If it is the first move, record the original position of the object
|
||||
if (originalPos.left === 0 && originalPos.top === 0) {
|
||||
originalPos = { left: obj.left, top: obj.top };
|
||||
}
|
||||
|
||||
// 检查是否按下了Shift键
|
||||
// Check if the Shift key is pressed
|
||||
if (e?.e?.shiftKey) {
|
||||
// 计算从开始移动以来的水平和垂直距离
|
||||
// Calculate the horizontal and vertical distance since the start of the movement
|
||||
const distanceX = obj.left - originalPos.left;
|
||||
const distanceY = obj.top - originalPos.top;
|
||||
|
||||
// 根据移动距离的绝对值判断是水平移动还是垂直移动
|
||||
// Determine whether to move horizontally or vertically according to the absolute value of the moving distance
|
||||
if (Math.abs(distanceX) > Math.abs(distanceY)) {
|
||||
// 水平移动:保持垂直位置不变
|
||||
// Horizontal movement: maintain the same vertical position
|
||||
obj.set('top', originalPos.top);
|
||||
} else {
|
||||
// 垂直移动:保持水平位置不变
|
||||
// Vertical movement: maintain the same horizontal position
|
||||
obj.set('left', originalPos.left);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
// 监听对象移动结束事件
|
||||
// Listening Object Move End Event
|
||||
canvas.on('object:modified', function (e) {
|
||||
// 移动结束后重置原始位置
|
||||
// Reset original position after move
|
||||
originalPos = { left: 0, top: 0 };
|
||||
}),
|
||||
];
|
||||
@@ -418,7 +418,7 @@ export const useActiveObjectChange = ({
|
||||
| undefined
|
||||
>();
|
||||
|
||||
// 元素移动过程中,隐藏控制点
|
||||
// Hide control points during element movement
|
||||
useEffect(() => {
|
||||
const disposers: (() => void)[] = [];
|
||||
if (activeObjects?.length === 1) {
|
||||
@@ -427,29 +427,29 @@ export const useActiveObjectChange = ({
|
||||
element.on('moving', () => {
|
||||
if (!controlsVisibility.current) {
|
||||
controlsVisibility.current = Object.assign(
|
||||
// fabric 规则: undefined 认为是 true
|
||||
// Fabric rule: undefined is considered true
|
||||
{
|
||||
ml: true, // 中点左
|
||||
mr: true, // 中点右
|
||||
mt: true, // 中点上
|
||||
mb: true, // 中点下
|
||||
bl: true, // 底部左
|
||||
br: true, // 底部右
|
||||
tl: true, // 顶部左
|
||||
tr: true, // 顶部右
|
||||
ml: true, // Midpoint left
|
||||
mr: true, // Midpoint right
|
||||
mt: true, // midpoint
|
||||
mb: true, // midpoint
|
||||
bl: true, // Bottom left
|
||||
br: true, // Bottom right
|
||||
tl: true, // Top Left
|
||||
tr: true, // Top right
|
||||
},
|
||||
element._controlsVisibility,
|
||||
);
|
||||
}
|
||||
element.setControlsVisibility({
|
||||
ml: false, // 中点左
|
||||
mr: false, // 中点右
|
||||
mt: false, // 中点上
|
||||
mb: false, // 中点下
|
||||
bl: false, // 底部左
|
||||
br: false, // 底部右
|
||||
tl: false, // 顶部左
|
||||
tr: false, // 顶部右
|
||||
ml: false, // Midpoint left
|
||||
mr: false, // Midpoint right
|
||||
mt: false, // midpoint
|
||||
mb: false, // midpoint
|
||||
bl: false, // Bottom left
|
||||
br: false, // Bottom right
|
||||
tl: false, // Top Left
|
||||
tr: false, // Top right
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useAlign = ({
|
||||
canvas?: Canvas;
|
||||
selectObjects?: FabricObject[];
|
||||
}) => {
|
||||
// 水平居左
|
||||
// Horizontal left
|
||||
const alignLeft = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -48,7 +48,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 水平居右
|
||||
// Horizontal right
|
||||
const alignRight = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -66,7 +66,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 水平居中
|
||||
// centered text
|
||||
const alignCenter = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -84,7 +84,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 垂直居上
|
||||
// vertical top
|
||||
const alignTop = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -102,7 +102,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 垂直居中
|
||||
// Vertically centered
|
||||
const alignMiddle = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -120,7 +120,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 垂直居下
|
||||
// vertical
|
||||
const alignBottom = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -138,7 +138,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 水平均分
|
||||
// horizontal average fraction
|
||||
const verticalAverage = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -155,7 +155,7 @@ export const useAlign = ({
|
||||
const spacing =
|
||||
(activeObject.width - totalWidth) / (selectObjects.length - 1);
|
||||
|
||||
let currentLeft = -activeObject.width / 2; // 初始位置
|
||||
let currentLeft = -activeObject.width / 2; // initial position
|
||||
|
||||
selectObjects
|
||||
.sort((a, b) => a.getBoundingRect().left - b.getBoundingRect().left)
|
||||
@@ -169,7 +169,7 @@ export const useAlign = ({
|
||||
canvas.requestRenderAll();
|
||||
}, [canvas, selectObjects]);
|
||||
|
||||
// 垂直均分
|
||||
// vertical equipartition
|
||||
const horizontalAverage = useCallback(() => {
|
||||
if (!canvas || selectObjects.length < 2) {
|
||||
return;
|
||||
@@ -186,7 +186,7 @@ export const useAlign = ({
|
||||
const spacing =
|
||||
(activeObject.height - totalHeight) / (selectObjects.length - 1);
|
||||
|
||||
let currentTop = -activeObject.height / 2; // 初始位置
|
||||
let currentTop = -activeObject.height / 2; // initial position
|
||||
|
||||
selectObjects
|
||||
.sort((a, b) => a.getBoundingRect().top - b.getBoundingRect().top)
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useBackground = ({
|
||||
);
|
||||
}, [canvas]);
|
||||
|
||||
// 防抖的作用在于,form.schema.backgroundColor 的变化是异步的,setBackgroundColor 是同步的,两者可能会打架
|
||||
// The effect of stabilization is that the change of form.schema.backgroundColor is asynchronous, and the change of setBackgroundColor is synchronous, and the two may fight
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
setBackgroundColor(schema.backgroundColor as string);
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
|
||||
const ImagePlaceholder = `${getUploadCDNAsset('')}/workflow/fabric-canvas/img-placeholder.png`;
|
||||
|
||||
// 需要额外保存的属性
|
||||
// Properties that require additional saving
|
||||
export const saveProps = [
|
||||
'width',
|
||||
'height',
|
||||
@@ -46,17 +46,17 @@ export const saveProps = [
|
||||
'text',
|
||||
'backgroundColor',
|
||||
'padding',
|
||||
// 自定义参数
|
||||
// textBox 的真实高度
|
||||
// custom parameters
|
||||
// The true height of the textBox
|
||||
'customFixedHeight',
|
||||
// 元素 id
|
||||
// Element ID
|
||||
'customId',
|
||||
// 元素类型
|
||||
// element type
|
||||
'customType',
|
||||
// image 的适应模式
|
||||
// Adaptive mode of image
|
||||
'customFixedType',
|
||||
// // 由变量生成元素的 title
|
||||
// 引用关系
|
||||
// The title of the element generated by the variable
|
||||
// reference relationship
|
||||
'customVariableRefs',
|
||||
];
|
||||
|
||||
@@ -94,7 +94,7 @@ export const useCanvasChange = ({
|
||||
schema?.customVariableRefs ?? [],
|
||||
);
|
||||
|
||||
// 删除画布中不存在的引用关系
|
||||
// Delete reference relationships that do not exist in the canvas
|
||||
const resetCustomVariableRefs = useCallback(
|
||||
({ schema: _schema }: { schema: FabricSchema }) => {
|
||||
let newCustomVariableRefs = cacheCustomVariableRefs.current;
|
||||
@@ -110,12 +110,12 @@ export const useCanvasChange = ({
|
||||
[],
|
||||
);
|
||||
|
||||
// 监听画布变化
|
||||
// Monitor canvas changes
|
||||
useEffect(() => {
|
||||
if (canvas && onChangeLatest.current && isListen) {
|
||||
const _onChange = ({ isRemove }: { isRemove: boolean }) => {
|
||||
const json = canvas.toObject(saveProps) as FabricSchema;
|
||||
// 删除时,顺便删掉无效 ref
|
||||
// When deleting, delete the invalid ref by the way.
|
||||
if (isRemove) {
|
||||
json.customVariableRefs = resetCustomVariableRefs({
|
||||
schema: json,
|
||||
@@ -146,7 +146,7 @@ export const useCanvasChange = ({
|
||||
}, [canvas, isListen]);
|
||||
|
||||
/**
|
||||
* 生成带引用的新元素
|
||||
* Generate new elements with references
|
||||
*/
|
||||
const addRefObjectByVariable = useCallback(
|
||||
async (variable: InputVariable, element?: FabricObject) => {
|
||||
@@ -167,7 +167,7 @@ export const useCanvasChange = ({
|
||||
|
||||
let _element: FabricObject | undefined = element;
|
||||
|
||||
// 如果没有传入现有元素,则创建新元素
|
||||
// If no existing element is passed in, a new element is created
|
||||
if (!_element) {
|
||||
if (type === ViewVariableType.Image) {
|
||||
_element = await createElement({
|
||||
@@ -205,7 +205,7 @@ export const useCanvasChange = ({
|
||||
}
|
||||
|
||||
if (_element) {
|
||||
// 更新引用关系
|
||||
// Update reference relationship
|
||||
cacheCustomVariableRefs.current.push({
|
||||
variableId: id as string,
|
||||
objectId: (_element as FabricObjectWithCustomProps)
|
||||
@@ -213,7 +213,7 @@ export const useCanvasChange = ({
|
||||
variableName: name,
|
||||
});
|
||||
|
||||
// 添加到画布并激活
|
||||
// Add to canvas and activate
|
||||
canvas.add(_element);
|
||||
canvas.setActiveObject(_element);
|
||||
}
|
||||
@@ -222,10 +222,10 @@ export const useCanvasChange = ({
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新指定 objectId 的元素的引用关系
|
||||
* 如果 variable 为空,则删除引用
|
||||
* 如果 variable 不为空 && customVariableRefs 已存在对应关系,则更新引用
|
||||
* 如果 variable 不为空 && customVariableRefs 不存在对应关系,则新增引用
|
||||
* Update the reference relationship of the element specifying objectId
|
||||
* If the variable is empty, remove the reference
|
||||
* If variable is not empty & & customVariableRefs already has a correspondence, update the reference
|
||||
* If variable is not empty & & customVariableRefs does not have a correspondence, add a reference
|
||||
*
|
||||
*/
|
||||
const updateRefByObjectId = useCallback(
|
||||
@@ -239,12 +239,12 @@ export const useCanvasChange = ({
|
||||
const customVariableRefs = cacheCustomVariableRefs.current;
|
||||
const targetRef = customVariableRefs.find(d => d.objectId === objectId);
|
||||
let newCustomVariableRefs = [];
|
||||
// 如果 variable 为空,则删除引用
|
||||
// If the variable is empty, remove the reference
|
||||
if (!variable) {
|
||||
newCustomVariableRefs = customVariableRefs.filter(
|
||||
d => d.objectId !== objectId,
|
||||
);
|
||||
// 如果 variable 不为空 && customVariableRefs 不存在对应关系,则新增引用
|
||||
// If variable is not empty & & customVariableRefs does not have a correspondence, add a reference
|
||||
} else if (!targetRef) {
|
||||
newCustomVariableRefs = [
|
||||
...customVariableRefs,
|
||||
@@ -254,7 +254,7 @@ export const useCanvasChange = ({
|
||||
variableName: variable.name,
|
||||
},
|
||||
];
|
||||
// 如果 variable 不为空 && customVariableRefs 已存在对应关系,则更新引用
|
||||
// If variable is not empty & & customVariableRefs already has a correspondence, update the reference
|
||||
} else {
|
||||
newCustomVariableRefs = customVariableRefs.map(d => {
|
||||
if (d.objectId === objectId) {
|
||||
@@ -278,7 +278,7 @@ export const useCanvasChange = ({
|
||||
);
|
||||
|
||||
/**
|
||||
* variables 变化时,更新引用关系中的变量名
|
||||
* When variables change, update the variable names in the reference relationship
|
||||
*/
|
||||
useEffect(() => {
|
||||
const { customVariableRefs = [] } = schemaLatest.current ?? {};
|
||||
@@ -310,7 +310,7 @@ export const useCanvasChange = ({
|
||||
|
||||
const startListen = useCallback(() => {
|
||||
setIsListener(true);
|
||||
// redo undo 完成后,更新引用关系
|
||||
// After redo undo, update the reference relationship
|
||||
cacheCustomVariableRefs.current =
|
||||
schemaLatest.current?.customVariableRefs ?? [];
|
||||
}, []);
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useCommonOperation = ({ canvas }: { canvas?: Canvas }) => {
|
||||
direct: 'left' | 'right' | 'up' | 'down',
|
||||
offsetValue = 1,
|
||||
) => {
|
||||
// 这里不用额外考虑框选 case ,框选时会形成一个临时的组,对组做位移,会影响到组内的每一个元素
|
||||
// There is no need to consider the box selection case here. The box selection will form a temporary group, and the displacement of the group will affect every element in the group
|
||||
const activeSelection = canvas?.getActiveObject();
|
||||
|
||||
switch (direct) {
|
||||
@@ -59,12 +59,12 @@ export const useCommonOperation = ({ canvas }: { canvas?: Canvas }) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘上下左右触发的图形位移,需要主动触发
|
||||
* The graphic displacement triggered by the keyboard up, down, left and right needs to be triggered actively.
|
||||
* 1. moving
|
||||
* if (activeSelection) canvas.fire('object:moving')
|
||||
* else activeSelection.fire('moving')
|
||||
*
|
||||
* 2. object:modified ,用来触发保存
|
||||
* 2. object: modified, used to trigger save
|
||||
*/
|
||||
const isActiveSelection = activeSelection?.isType('activeselection');
|
||||
const fabricObject = (
|
||||
@@ -116,7 +116,7 @@ export const useCommonOperation = ({ canvas }: { canvas?: Canvas }) => {
|
||||
canvas.sendObjectBackwards(obj);
|
||||
});
|
||||
}
|
||||
// 主动触发一次自定义事件:zIndex 变化
|
||||
// Actively trigger a custom event: zIndex change
|
||||
canvas.fire('object:modified-zIndex' as keyof CanvasEvents);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
import { saveProps } from './use-canvas-change';
|
||||
|
||||
/**
|
||||
* 粘贴后的默认偏移
|
||||
* Default offset after pasting
|
||||
*/
|
||||
const staff = 16;
|
||||
export const useCopyPaste = ({
|
||||
@@ -59,11 +59,11 @@ export const useCopyPaste = ({
|
||||
element?: FabricObject,
|
||||
) => void;
|
||||
}) => {
|
||||
// ctrlCV 复制的元素
|
||||
// CtrlCV copied elements
|
||||
const copiedObject1 = useRef<FabricObject>();
|
||||
// ctrlD 复制的元素
|
||||
// CtrlD copied elements
|
||||
const copiedObject2 = useRef<FabricObject>();
|
||||
// dragCopy 拖拽复制的元素
|
||||
// dragCopy Drag and copy elements
|
||||
const copiedObject3 = useRef<FabricObject>();
|
||||
|
||||
const latestCustomVariableRefs = useLatest(customVariableRefs);
|
||||
@@ -88,9 +88,9 @@ export const useCopyPaste = ({
|
||||
});
|
||||
const latestIgnoreMousePosition = useLatest(ignoreMousePosition);
|
||||
|
||||
// 如果鼠标动了,就以鼠标位置为准。仅影响 CopyMode.CtrlD 的粘贴
|
||||
// If the mouse is moved, the mouse position shall prevail. Only the paste of CopyMode. CtrlD is affected.
|
||||
useEffect(() => {
|
||||
// 默认 left top 对应元素的左上角。需要实现元素中点对齐鼠标位置,因此做偏移
|
||||
// Default left top corresponds to the upper left corner of the element. You need to align the mouse position in the middle of the element, so offset
|
||||
setPosition({
|
||||
left: mousePosition.left - (copiedObject1.current?.width ?? 0) / 2,
|
||||
top: mousePosition.top - (copiedObject1.current?.height ?? 0) / 2,
|
||||
@@ -100,12 +100,12 @@ export const useCopyPaste = ({
|
||||
const handleElement = async (element: FabricObject): Promise<void> => {
|
||||
const oldObjectId = (element as FabricObjectWithCustomProps).customId;
|
||||
const newObjectId = nanoid();
|
||||
// 设置新的 id
|
||||
// Set a new ID.
|
||||
element.set({
|
||||
customId: newObjectId,
|
||||
});
|
||||
|
||||
// 走统一的创建元素逻辑
|
||||
// Take a unified approach to creating element logic
|
||||
const rs = await createElement({
|
||||
element: element as FabricObjectWithCustomProps,
|
||||
canvas,
|
||||
@@ -125,8 +125,8 @@ export const useCopyPaste = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* mode 分为三种:'ctrlCV' | 'ctrlD' | 'dragCopy'
|
||||
* 行为一致,区别就是三种行为的复制源隔离,互不影响
|
||||
* There are three modes: 'ctrlCV' | 'ctrlD' | 'dragCopy'
|
||||
* The behavior is consistent, the difference is that the replication sources of the three behaviors are isolated and do not affect each other
|
||||
*/
|
||||
const copy = useCallback(
|
||||
async (mode: CopyMode = CopyMode.CtrlCV) => {
|
||||
@@ -195,14 +195,14 @@ export const useCopyPaste = ({
|
||||
}
|
||||
const cloneObj = await copiedObject.clone(saveProps);
|
||||
|
||||
// ctrlCV 需要考虑鼠标位置,其他的不用
|
||||
// CtrlCV needs to consider the mouse position, others do not need to be
|
||||
const isIgnoreMousePosition = mode !== CopyMode.CtrlCV;
|
||||
|
||||
const { left, top } = isIgnoreMousePosition
|
||||
? latestIgnoreMousePosition.current
|
||||
: latestPosition.current;
|
||||
|
||||
// 计算下次粘贴位置,向 left top 各偏移 staff
|
||||
// Calculate the next paste position and offset the staff to the left top
|
||||
if (isIgnoreMousePosition) {
|
||||
setIgnoreMousePosition({
|
||||
left: left + staff,
|
||||
@@ -228,7 +228,7 @@ export const useCopyPaste = ({
|
||||
}),
|
||||
});
|
||||
|
||||
// 把需要复制的元素都拿出来,多选
|
||||
// Take out all the elements that need to be copied and select more
|
||||
const allPasteObjects: FabricObject[] = [];
|
||||
const originXY = {
|
||||
left: cloneObj.left + cloneObj.width / 2,
|
||||
@@ -242,19 +242,19 @@ export const useCopyPaste = ({
|
||||
});
|
||||
allPasteObjects.push(o);
|
||||
});
|
||||
// 把需要复制的元素都拿出来,单选
|
||||
// Take out all the elements that need to be copied, radio select
|
||||
} else {
|
||||
allPasteObjects.push(cloneObj);
|
||||
}
|
||||
|
||||
// 挨着调用 handleElement 处理元素
|
||||
// Calling handleElement next to handle elements
|
||||
await Promise.all(allPasteObjects.map(async o => handleElement(o)));
|
||||
|
||||
// 如果是多选,需要创新新的多选框,并激活
|
||||
// If it is multiple selection, you need to innovate a new multi-checkbox and activate it.
|
||||
let allPasteObjectsActiveSelection: ActiveSelection | undefined;
|
||||
if (cloneObj.isType('activeselection')) {
|
||||
allPasteObjectsActiveSelection = new ActiveSelection(
|
||||
// 很恶心,这里激活选框,并不会自动转换坐标,需要手动转一下
|
||||
// It's disgusting. Activating the check box here will not automatically convert the coordinates. You need to turn it manually.
|
||||
allPasteObjects.map(o => {
|
||||
o.set({
|
||||
left: o.left - originXY.left,
|
||||
@@ -279,23 +279,23 @@ export const useCopyPaste = ({
|
||||
const keyCodes = ['Alt'];
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (keyCodes.includes(e.key)) {
|
||||
e.preventDefault(); // 阻止默认行为
|
||||
isAltPressing = true; // 标记 alt 已按下
|
||||
e.preventDefault(); // Block default behavior
|
||||
isAltPressing = true; // Mark alt pressed
|
||||
}
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (keyCodes.includes(e.key)) {
|
||||
e.preventDefault(); // 阻止默认行为
|
||||
isAltPressing = false; // 标记 alt 已松开
|
||||
e.preventDefault(); // Block default behavior
|
||||
isAltPressing = false; // The alt tag has been released
|
||||
}
|
||||
};
|
||||
|
||||
const onWindowBlur = () => {
|
||||
isAltPressing = false; // 标记 alt 已松开
|
||||
isAltPressing = false; // The alt tag has been released
|
||||
};
|
||||
|
||||
const onContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault(); // 阻止默认行为
|
||||
e.preventDefault(); // Block default behavior
|
||||
};
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
document.addEventListener('keyup', onKeyUp);
|
||||
@@ -307,7 +307,7 @@ export const useCopyPaste = ({
|
||||
let originalPos = { left: 0, top: 0 };
|
||||
|
||||
const disposers = [
|
||||
// 复制时机:按下 alt 键 & 鼠标按下激活元素
|
||||
// Copy timing: Alt key & mouse down to activate element
|
||||
canvas?.on('mouse:down', async e => {
|
||||
if (isAltPressing) {
|
||||
if (!latestCouldAddNewObject.current) {
|
||||
@@ -320,7 +320,7 @@ export const useCopyPaste = ({
|
||||
|
||||
isDragCopying = true;
|
||||
const activeObject = canvas.getActiveObject();
|
||||
// 创建元素副本期间,锁定 xy 方向的移动
|
||||
// Lock movement in the xy direction during element copy creation
|
||||
activeObject?.set({
|
||||
lockMovementX: true,
|
||||
lockMovementY: true,
|
||||
@@ -331,7 +331,7 @@ export const useCopyPaste = ({
|
||||
mode: CopyMode.DragCV,
|
||||
});
|
||||
|
||||
// 记录对象的原始位置,实现 shift 垂直、水平移动
|
||||
// Record the original position of the object and realize vertical and horizontal shift
|
||||
originalPos = {
|
||||
left: pasteObj?.left ?? 0,
|
||||
top: pasteObj?.top ?? 0,
|
||||
@@ -345,26 +345,26 @@ export const useCopyPaste = ({
|
||||
}
|
||||
}),
|
||||
|
||||
// 因为 copy 是异步的,所以这里会有一些延迟(大图片比较明显),没啥好办法
|
||||
// Because the copy is asynchronous, there will be some delay here (the big picture is more obvious), there is no good way
|
||||
canvas?.on('mouse:move', event => {
|
||||
if (isAltPressing && isDragCopying && pasteObj) {
|
||||
const pointer = canvas.getScenePoint(event.e);
|
||||
|
||||
// 检查是否按下了Shift键
|
||||
// Check if the Shift key is pressed
|
||||
if (event.e.shiftKey) {
|
||||
// 计算从开始移动以来的水平和垂直距离
|
||||
// Calculate the horizontal and vertical distance since the start of the movement
|
||||
const distanceX = pointer.x - originalPos.left;
|
||||
const distanceY = pointer.y - originalPos.top;
|
||||
|
||||
// 根据移动距离的绝对值判断是水平移动还是垂直移动
|
||||
// Determine whether to move horizontally or vertically according to the absolute value of the moving distance
|
||||
if (Math.abs(distanceX) > Math.abs(distanceY)) {
|
||||
// 水平移动:保持垂直位置不变
|
||||
// Horizontal movement: maintain the same vertical position
|
||||
pasteObj?.set({
|
||||
left: pointer.x - (pasteObj?.width ?? 0) / 2,
|
||||
top: originalPos.top,
|
||||
});
|
||||
} else {
|
||||
// 垂直移动:保持水平位置不变
|
||||
// Vertical movement: maintain the same horizontal position
|
||||
pasteObj?.set({
|
||||
left: originalPos.left,
|
||||
top: pointer.y - (pasteObj?.height ?? 0) / 2,
|
||||
@@ -386,7 +386,7 @@ export const useCopyPaste = ({
|
||||
canvas?.on('mouse:up', () => {
|
||||
isDragCopying = false;
|
||||
pasteObj = undefined;
|
||||
// 释放拖拽复制对象,避免对下次拖拽(按着 alt 不松手)造成干扰
|
||||
// Release the drag and drop to copy the object to avoid disturbing the next drag (press alt without letting go)
|
||||
copiedObject3.current = undefined;
|
||||
}),
|
||||
];
|
||||
@@ -399,7 +399,7 @@ export const useCopyPaste = ({
|
||||
};
|
||||
}, [canvas, copy, paste]);
|
||||
|
||||
// 拖拽复制
|
||||
// Drag and drop to copy
|
||||
return {
|
||||
copy,
|
||||
paste,
|
||||
|
||||
@@ -118,7 +118,7 @@ const modeElementMap: Partial<
|
||||
y2: dy + (element as Line).y1,
|
||||
});
|
||||
|
||||
// 创建直线时的终点位置修改,需要主动 fire 影响控制点的显示
|
||||
// The end position modification when creating a straight line requires active fire to affect the display of the control point
|
||||
element.fire('start-end:modified' as keyof ObjectEvents);
|
||||
},
|
||||
},
|
||||
@@ -193,7 +193,7 @@ export const useDragAdd = ({
|
||||
moved: false,
|
||||
};
|
||||
|
||||
// 隐藏控制点,否则 onmouseup 可能被控制点截胡
|
||||
// Hide the control point, otherwise onmouseup may be truncated by the control point
|
||||
element.set('hasControls', false);
|
||||
}
|
||||
});
|
||||
@@ -212,7 +212,7 @@ export const useDragAdd = ({
|
||||
dy,
|
||||
});
|
||||
|
||||
// 修正元素坐标信息
|
||||
// Correct element coordinate information
|
||||
element.setCoords();
|
||||
|
||||
newElement.current.moved = true;
|
||||
@@ -233,7 +233,7 @@ export const useDragAdd = ({
|
||||
|
||||
onShapeAdded?.({ element: element as FabricObjectWithCustomProps });
|
||||
|
||||
// 恢复控制点
|
||||
// Restore Control Point
|
||||
element.set('hasControls', true);
|
||||
newElement.current = undefined;
|
||||
canvas.requestRenderAll();
|
||||
|
||||
@@ -78,8 +78,8 @@ export const useFabricEditor = ({
|
||||
}) => {
|
||||
const schema: FabricSchema = useMemo(() => {
|
||||
/**
|
||||
* 兼容历史数据
|
||||
* 删除时机,见 apps/fabric-canvas-node-render/utils/replace-ref-value.ts 注释
|
||||
* Compatible with historical data
|
||||
* Delete timing, see apps/fabric-canvas-node-render/utils/replace-ref-value.ts comment
|
||||
*/
|
||||
if (
|
||||
!_schema?.customVariableRefs &&
|
||||
@@ -106,7 +106,7 @@ export const useFabricEditor = ({
|
||||
const objectLength = useMemo(() => schema.objects.length, [schema]);
|
||||
|
||||
/**
|
||||
* 最大可添加元素数量限制
|
||||
* Maximum number of elements that can be added
|
||||
*/
|
||||
const MAX_OBJECT_LENGTH = 50;
|
||||
const couldAddNewObject = useMemo(
|
||||
@@ -121,7 +121,7 @@ export const useFabricEditor = ({
|
||||
height: schema.height,
|
||||
});
|
||||
|
||||
// 初始化 fabric canvas
|
||||
// Initialize fabric canvas
|
||||
const { canvas, loadFromJSON } = useInitCanvas({
|
||||
startInit,
|
||||
ref: ref.current,
|
||||
|
||||
@@ -27,18 +27,18 @@ export const useFreePencil = ({ canvas }: { canvas?: Canvas }) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
// 启用自由绘图模式
|
||||
// Enable free drawing mode
|
||||
canvas.isDrawingMode = true;
|
||||
|
||||
// 设置 PencilBrush 为当前的画笔
|
||||
// Set PencilBrush to the current brush
|
||||
canvas.freeDrawingBrush = new PencilBrush(canvas);
|
||||
|
||||
// 设置画笔的一些属性
|
||||
canvas.freeDrawingBrush.color = defaultProps[Mode.PENCIL].stroke as string; // 画笔颜色
|
||||
// Set some properties of the brush
|
||||
canvas.freeDrawingBrush.color = defaultProps[Mode.PENCIL].stroke as string; // Brush color
|
||||
canvas.freeDrawingBrush.width = defaultProps[Mode.PENCIL]
|
||||
.strokeWidth as number; // 画笔宽度
|
||||
.strokeWidth as number; // Brush Width
|
||||
|
||||
// 你也可以设置其他属性,比如 opacity (不透明度)
|
||||
// You can also set other properties, such as opacity.
|
||||
// canvas.freeDrawingBrush.opacity = 0.6;
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useFreePencil = ({ canvas }: { canvas?: Canvas }) => {
|
||||
element: path,
|
||||
});
|
||||
|
||||
// 得触发一次 object:added ,以触发 onSave,否则 schema 里并不会包含 commonOptions
|
||||
// You must fire object: added once to trigger onSave, otherwise the schema will not contain commonOptions.
|
||||
canvas.fire('object:modified');
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ export const useFreePencil = ({ canvas }: { canvas?: Canvas }) => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
// 禁用自由绘图模式
|
||||
// Disable free drawing mode
|
||||
canvas.isDrawingMode = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,20 +15,20 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 这是个半成品,暂时不做了,后面再考虑
|
||||
* 1. 成组后要支持下钻继续选择
|
||||
* 实现思路:
|
||||
* a.双击解组,并记录组关系;
|
||||
* b.下钻选择子组,继续解组,并记录组关系;
|
||||
* c.点击画布(没有任何选中元素时),恢复组(要注意 z-index)。
|
||||
* This is a work in progress, I won't do it for the time being, I'll think about it later.
|
||||
* 1. After forming a group, support drilling down and continue to select.
|
||||
* Realization idea:
|
||||
* A. Double-click to ungroup and record the group relationship;
|
||||
* B. Drill down to select subgroups, continue to ungroup, and record group relationships;
|
||||
* Click on the canvas (when no elements are selected) and restore the group (note the z-index).
|
||||
*
|
||||
* 2. 复制粘贴组时,需要排除掉引用元素
|
||||
* 3. 删除组是,也需要排除引用元素
|
||||
* 4. 因为组的引入,打破了所有元素都是拍平的原则,要注意这个改动的破坏性。
|
||||
* 2. When copying and pasting groups, you need to exclude reference elements
|
||||
* 3. Delete group Yes, also need to exclude reference elements
|
||||
* 4. Due to the introduction of the group, the principle that all elements are flattened is broken. Be aware of the destructive nature of this change.
|
||||
* eg:
|
||||
* a. 获取所有元素
|
||||
* b. 元素的位置计算是由每层父元素叠加来的
|
||||
* c. 服务端渲染:遍历找所有的图片元素。完成图片下载后恢复组
|
||||
* A. Get all elements
|
||||
* B. The position calculation of the element is superimposed by each layer of parent elements
|
||||
* C.server-side rendering: Iterate to find all image elements. Restore group after finishing image download
|
||||
*/
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -47,7 +47,7 @@ export const useGroup = ({ canvas }: { canvas?: Canvas }) => {
|
||||
const group = useCallback(async () => {
|
||||
const activeObject = canvas?.getActiveObject();
|
||||
const objects = (activeObject as ActiveSelection)?.getObjects();
|
||||
// 选中了多个元素时,才可以 group
|
||||
// You can only group when multiple elements are selected.
|
||||
if ((objects?.length ?? 0) > 1) {
|
||||
const _group = await createElement({
|
||||
mode: Mode.GROUP,
|
||||
@@ -69,7 +69,7 @@ export const useGroup = ({ canvas }: { canvas?: Canvas }) => {
|
||||
const unGroup = useCallback(async () => {
|
||||
const activeObject = canvas?.getActiveObject();
|
||||
|
||||
// 仅选中了一个 group 元素时,才可以 ungroup
|
||||
// Ungroup can only be done if a group element is selected
|
||||
if (isGroupElement(activeObject)) {
|
||||
const _group = activeObject as Group;
|
||||
const objects = _group.getObjects();
|
||||
|
||||
@@ -46,7 +46,7 @@ export const useInitCanvas = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 按比例给个初始化高度,随后会通过 resize 修正为真正的宽高
|
||||
// Give an initial height proportionally, and then correct it to the true width and height by resizing.
|
||||
const _canvas = new Canvas(ref, {
|
||||
width: schema.width * scale,
|
||||
height: schema.height * scale,
|
||||
@@ -86,7 +86,7 @@ export const useInitCanvas = ({
|
||||
await fabricCanvas?.loadFromJSON(
|
||||
JSON.stringify(_schema),
|
||||
async (elementSchema, element) => {
|
||||
// 每个元素被加载后的回调
|
||||
// Callback after each element is loaded
|
||||
await setElementAfterLoad({
|
||||
element: element as FabricObject,
|
||||
options: { readonly },
|
||||
|
||||
@@ -49,7 +49,7 @@ const getElementTitlePosition = ({
|
||||
let left = targetElementTopLeft.x * scale;
|
||||
let top = targetElementTopLeft.y * scale;
|
||||
|
||||
// 图片特化,需要考虑比例拉伸,位置限定在 group 范围内
|
||||
// Image specialization, proportional stretching needs to be considered, and the position is limited to the group range
|
||||
if (isImg) {
|
||||
const strokeWidth =
|
||||
(element as unknown as Group).getObjects()?.[1]?.strokeWidth ?? 0;
|
||||
@@ -84,7 +84,7 @@ export const usePosition = ({
|
||||
const [screenPositions, setScreenPositions] = useState<IRefPosition[]>([]);
|
||||
|
||||
const _setPositions = useCallback(() => {
|
||||
// 为什么要 setTimeout?批量时,需要延迟才能拿到正确的坐标
|
||||
// Why setTimeout? When batching, you need to delay to get the correct coordinates.
|
||||
setTimeout(() => {
|
||||
if (!canvas) {
|
||||
return;
|
||||
|
||||
@@ -60,7 +60,7 @@ export const useRedoUndo = ({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// 保存多少步
|
||||
// How many steps to save
|
||||
const max = 20;
|
||||
const end = stepLatest.current + 1;
|
||||
const start = Math.max(0, end - max);
|
||||
@@ -81,25 +81,25 @@ export const useRedoUndo = ({
|
||||
const newStep = stepLatest.current - 1;
|
||||
const _schema = historyLatest.current[newStep];
|
||||
|
||||
// 开始执行 undo
|
||||
// Start executing undo
|
||||
setRedoUndoing(true);
|
||||
|
||||
// 停止监听画布变化
|
||||
// Stop listening for canvas changes
|
||||
stopListen();
|
||||
|
||||
// 保存 schema
|
||||
// Save schema
|
||||
onChange?.(_schema);
|
||||
|
||||
// 画布重新加载
|
||||
// Canvas reload
|
||||
await loadFromJSON?.(_schema);
|
||||
|
||||
// 同步 step
|
||||
// Synchronization steps
|
||||
setStep(newStep);
|
||||
|
||||
// 恢复画布监听
|
||||
// Restore canvas monitor
|
||||
startListen();
|
||||
|
||||
// undo 执行完成
|
||||
// Undo execution complete
|
||||
setRedoUndoing(false);
|
||||
}, [loadFromJSON]);
|
||||
|
||||
@@ -115,25 +115,25 @@ export const useRedoUndo = ({
|
||||
const newStep = stepLatest.current + 1;
|
||||
const _schema = historyLatest.current[newStep];
|
||||
|
||||
// 开始执行 redo
|
||||
// Start redo
|
||||
setRedoUndoing(true);
|
||||
|
||||
// 停止监听画布变化
|
||||
// Stop listening for canvas changes
|
||||
stopListen();
|
||||
|
||||
// 保存 schema
|
||||
// Save schema
|
||||
onChange?.(_schema);
|
||||
|
||||
// 画布重新加载
|
||||
// Canvas reload
|
||||
await loadFromJSON?.(_schema);
|
||||
|
||||
// 同步 step
|
||||
// Synchronization steps
|
||||
setStep(newStep);
|
||||
|
||||
// 恢复画布监听
|
||||
// Restore canvas monitor
|
||||
startListen();
|
||||
|
||||
// redo 执行完成
|
||||
// Redo execution completed
|
||||
setRedoUndoing(false);
|
||||
}, [loadFromJSON]);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import { setElementAfterLoad } from '../utils';
|
||||
import { type FabricSchema } from '../typings';
|
||||
|
||||
/**
|
||||
* 监听 schema 变化,reload canvas
|
||||
* 仅只读态需要
|
||||
* Listen for schema changes, reload canvas
|
||||
* Read-only state required
|
||||
*/
|
||||
export const useSchemaChange = ({
|
||||
canvas,
|
||||
@@ -39,7 +39,7 @@ export const useSchemaChange = ({
|
||||
setLoading(true);
|
||||
canvas
|
||||
?.loadFromJSON(JSON.stringify(schema), (elementSchema, element) => {
|
||||
// 这里是 schema 中每个元素被加载后的回调
|
||||
// Here is the callback for each element in the schema after it has been loaded
|
||||
setElementAfterLoad({
|
||||
element: element as FabricObject,
|
||||
options: { readonly },
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface Options<T> {
|
||||
const storage: Record<string, unknown> = {};
|
||||
|
||||
/**
|
||||
* 持久化保存到内存
|
||||
* Persistent save to memory
|
||||
*/
|
||||
export function useStorageState<T>(key: string, options: Options<T> = {}) {
|
||||
function getStoredValue() {
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useViewport = ({
|
||||
return;
|
||||
}
|
||||
const _vpt: TMat2D = [...vpt];
|
||||
// 限制 viewport 移动区域:不能移出画布
|
||||
// Limit viewport movement area: Cannot move out of canvas
|
||||
if (_vpt[4] > 0) {
|
||||
_vpt[4] = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user