chore: replace all cn comments of fe to en version by volc api (#320)

This commit is contained in:
tecvan
2025-07-31 10:32:15 +08:00
committed by GitHub
parent 716ec0cba8
commit 71f6245a01
2960 changed files with 15545 additions and 15545 deletions

View File

@@ -58,7 +58,7 @@ describe('useAlign', () => {
act(() => {
result.current.alignLeft();
});
// 由于没有 canvas不应该有任何操作发生
// Since there is no canvas, no operation should occur
});
it('应该在选中对象少于 2 个时不执行任何操作', () => {

View File

@@ -88,11 +88,11 @@ describe('useBackground', () => {
},
);
// 更新 schema
// Update schema
const newSchema = createMockSchema('#000000');
rerender({ currentSchema: newSchema });
// 等待 debounce
// Waiting to debounce
await vi.runAllTimers();
expect(result.current.backgroundColor).toBe('#000000');

View File

@@ -59,7 +59,7 @@ describe('useCanvasChange', () => {
const createMockCanvas = () => {
const mockCanvas = {
on: vi.fn((event: string, callback: (event: any) => void) =>
// 返回一个清理函数
// Returns a cleaning function
() => {
mockCanvas.off(event, callback);
},
@@ -121,7 +121,7 @@ describe('useCanvasChange', () => {
}),
);
// 验证是否监听了所有默认事件
// Verify that all default events are being listened for
expect(mockCanvas.on).toHaveBeenCalledWith(
'object:modified',
expect.any(Function),
@@ -157,12 +157,12 @@ describe('useCanvasChange', () => {
}),
);
// 获取 object:modified 事件的回调函数
// Get the callback function for the object: modified event
const modifiedCallback = (mockCanvas.on as any).mock.calls.find(
(call: [string, Function]) => call[0] === 'object:modified',
)?.[1];
// 模拟事件触发
// simulated event firing
modifiedCallback?.();
expect(mockCanvas.toObject).toHaveBeenCalledWith(saveProps);
@@ -194,15 +194,15 @@ describe('useCanvasChange', () => {
}),
);
// 获取 object:removed 事件的回调函数
// Get the callback function for the object: removed event
const removedCallback = (mockCanvas.on as any).mock.calls.find(
(call: [string, Function]) => call[0] === 'object:removed',
)?.[1];
// 模拟删除事件
// mock delete event
removedCallback?.();
// 验证只保留了存在对象的引用
// Validation only keeps references to existing objects
expect(mockOnChange).toHaveBeenCalledWith({
...mockSchema,
customVariableRefs: [
@@ -296,7 +296,7 @@ describe('useCanvasChange', () => {
}),
);
// 更新已存在的引用
// Update existing references
act(() => {
result.current.updateRefByObjectId({
objectId: 'obj1',
@@ -309,7 +309,7 @@ describe('useCanvasChange', () => {
});
});
// 添加新的引用
// Add a new reference
act(() => {
result.current.updateRefByObjectId({
objectId: 'obj2',
@@ -322,14 +322,14 @@ describe('useCanvasChange', () => {
});
});
// 删除引用
// delete reference
act(() => {
result.current.updateRefByObjectId({
objectId: 'obj1',
});
});
// 验证最终的引用关系
// Verify the final reference relationship
expect(mockCanvas.toObject().customVariableRefs).toEqual([
{ objectId: 'obj2', variableId: 'var3', variableName: 'var3' },
]);

View File

@@ -110,12 +110,12 @@ describe('useCanvasClip', () => {
useCanvasClip({ canvas: mockCanvas, schema }),
);
// 先添加裁剪区域
// Add the clipping area first
act(() => {
result.current.addClip();
});
// 移除裁剪区域
// Remove clipping area
act(() => {
result.current.removeClip();
});

View File

@@ -57,7 +57,7 @@ describe('useGroup', () => {
add: vi.fn(),
remove: vi.fn(),
getObjects: vi.fn(),
// 添加必要的 fabric.Object 属性
// Add the necessary fabric. Object properties
noScaleCache: false,
lockMovementX: false,
lockMovementY: false,

View File

@@ -50,7 +50,7 @@ describe('useInitCanvas', () => {
const mockRequestRenderAll = vi.fn();
const mockDispose = vi.fn();
const mockOn = vi.fn(() => () => {
// 清理函数
// cleanup function
});
let mockCanvas: any;

View File

@@ -24,7 +24,7 @@ describe('useMousePosition', () => {
const createMockCanvas = () => {
const mockCanvas = {
on: vi.fn((event: string, callback: (event: any) => void) =>
// 返回一个清理函数
// Returns a cleaning function
() => {
mockCanvas.off(event, callback);
},
@@ -85,10 +85,10 @@ describe('useMousePosition', () => {
useMousePosition({ canvas: mockCanvas }),
);
// 初始位置
// initial position
expect(result.current.mousePosition).toEqual({ left: 0, top: 0 });
// 模拟鼠标移动
// Simulate mouse movement
act(() => {
moveCallback({
e: { clientX: 100, clientY: 200 },
@@ -135,12 +135,12 @@ describe('useMousePosition', () => {
expect.any(Function),
);
// 更新 canvas
// Update canvas
rerender({ canvas: mockCanvas2 });
// 应该清理旧的事件监听
// Old event listeners should be cleaned up
expect(cleanupSpy).toHaveBeenCalled();
// 应该设置新的事件监听
// New event listeners should be set up
expect(mockCanvas2.on).toHaveBeenCalledWith(
'mouse:move',
expect.any(Function),

View File

@@ -54,7 +54,7 @@ describe('useSnapMove', () => {
const createMockCanvas = () => {
const mockCanvas = {
on: vi.fn((event: string, callback: (event: any) => void) =>
// 返回一个清理函数
// Returns a cleaning function
() => {
mockCanvas.off(event, callback);
},
@@ -114,12 +114,12 @@ describe('useSnapMove', () => {
}),
);
// 获取 mouse:down 事件的回调函数
// Get the callback function for the mouse: down event
const mouseDownCallback = (mockCanvas.on as any).mock.calls.find(
(call: [string, Function]) => call[0] === 'mouse:down',
)?.[1];
// 模拟事件触发
// simulated event firing
mouseDownCallback?.({ target: { id: 'test-object' } });
expect(snap.resetAllObjectsPosition).toHaveBeenCalledWith({
@@ -157,12 +157,12 @@ describe('useSnapMove', () => {
}),
);
// 获取 mouse:up 事件的回调函数
// Get the callback function for the mouse: up event
const mouseUpCallback = (mockCanvas.on as any).mock.calls.find(
(call: [string, Function]) => call[0] === 'mouse:up',
)?.[1];
// 模拟事件触发
// simulated event firing
mouseUpCallback?.({ target: { id: 'test-object' } });
expect(mockSnap.reset).toHaveBeenCalled();
@@ -198,12 +198,12 @@ describe('useSnapMove', () => {
}),
);
// 获取 object:moving 事件的回调函数
// Get the callback function for the object: moving event
const movingCallback = (mockCanvas.on as any).mock.calls.find(
(call: [string, Function]) => call[0] === 'object:moving',
)?.[1];
// 模拟事件触发
// simulated event firing
movingCallback?.({ target: { id: 'test-object' } });
expect(mockSnap.move).toHaveBeenCalledWith({ id: 'test-object' });
@@ -259,7 +259,7 @@ describe('useSnapMove', () => {
},
);
// 更新 scale
// Update scale
rerender({ scale: 2 });
expect(snap.helpline.resetScale).toHaveBeenCalledWith(2);

View File

@@ -37,14 +37,14 @@ describe('typings', () => {
describe('AlignMode', () => {
it('应该定义正确的对齐模式枚举值', () => {
// 注意:这里的具体值需要根据实际的 AlignMode 枚举定义来填写
// Note: The specific values here need to be filled in according to the actual AlignMode enumeration definition
expect(AlignMode).toBeDefined();
expect(typeof AlignMode).toBe('object');
});
});
// 由于其他导出主要是类型定义,在运行时无法直接测试
// 但我们可以通过 TypeScript 的类型检查来验证它们的正确性
// Since other exports are mainly type definitions, they cannot be tested directly at runtime
// But we can verify their correctness through TypeScript's type checking
it('应该正确定义 FormMetaItem 接口', () => {
const formMetaItem = {
name: 'test',
@@ -59,7 +59,7 @@ describe('typings', () => {
},
};
// 这个测试主要是确保类型定义正确,实际运行时不会失败
// The main purpose of this test is to ensure that the type definition is correct and the actual execution does not fail
expect(formMetaItem).toBeDefined();
});

View File

@@ -48,7 +48,7 @@ const fontsFormat: {
const group = dArr[2];
return {
// 原本的名称
// Original name
value: dArr[1],
label: (
<img
@@ -57,11 +57,11 @@ const fontsFormat: {
src={`${cdnPrefix}/image-canvas-fonts-preview-svg/${d}`}
/>
),
// 顺序
// order
order: Number(dArr[0]),
// 一级分组名称
// first-level grouping name
name,
// 属于哪个分组
// Which group does it belong to?
groupName: group,
};
});

View File

@@ -15,31 +15,31 @@
*/
/**
* 画布最大缩放
* Canvas Max Zoom
*/
export const MAX_ZOOM = 3;
/**
* 画布最小缩放
* Canvas minimum zoom
*/
export const MIN_ZOOM = 1;
/**
* 画布最大宽度
* Canvas maximum width
*/
export const MAX_WIDTH = 10000;
/**
* 画布最小宽度
* Canvas minimum width
*/
export const MIN_WIDTH = 1;
/**
* 画布最大高度
* Canvas maximum height
*/
export const MAX_HEIGHT = 10000;
/**
* 画布最小高度
* minimum height of canvas
*/
export const MIN_HEIGHT = 1;
/**
* 画布最大面积
* Canvas maximum area
*/
export const MAX_AREA = 3840 * 2160;

View File

@@ -72,8 +72,8 @@ interface IProps {
onChange: (schema: FabricSchema) => void;
className?: string;
/**
* 不强制,用来当做 redo/undo 操作栈保存到内存的 key
* 不传的话,不会保存操作栈到内存,表现:关闭侧拉窗,丢失操作栈
* Unforced, used as the key to save the redo/undo operation stack to memory
* If it is not passed, the operation stack will not be saved to memory. Performance: close the side pull window and lose the operation stack.
*/
id?: string;
}
@@ -98,10 +98,10 @@ export const FabricEditor: FC<IProps> = props => {
}, [_variables]);
/**
* props.onChange 是异步,这个异步导致 schema 的状态很难管理。
* 因此此处用 state 来管理 schema后续消费 onChange 的地方可以当同步处理
* Props.onChange is asynchronous, which makes the state of the schema difficult to manage.
* Therefore, state is used to manage the schema here, and subsequent consumption of onChange can be handled synchronously
*
* 副作用:外界引发的 schema 变化,不会同步到画布(暂时没这个场景)
* Side effect: Schema changes caused by the outside world will not be synchronized to the canvas (this scene is not available for now)
*/
const [schema, setSchema] = useState<FabricSchema>(_schema);
const onChange = useCallback(
@@ -115,26 +115,26 @@ export const FabricEditor: FC<IProps> = props => {
const [id] = useState<string>(props.id ?? nanoid());
const helpLineLayerId = `help-line-${id}`;
// 快捷点监听区域
// Shortcut the listening area
const shortcutRef = useRef<HTMLDivElement>(null);
// Popover 渲染至 dom
// Popover rendering to dom
const popRef = useRef(null);
// Popover 渲染至 dom作用于 select dropdown 右对齐
// Popover render to dom, act on select, dropdown right align
const popRefAlignRight = useRef<HTMLDivElement>(null);
// canvas 可渲染区域 dom
// Canvas renderable domain
const sizeRef = useRef<HTMLDivElement>(null);
const size = useSize(sizeRef);
// popover 渲染至 dom
// Popover render to dom
const popoverRef = useRef<HTMLDivElement>(null);
const popoverSize = useSize(popoverRef);
// fabric canvas 渲染 dom
// Fabric canvas rendering dom
const canvasRef = useRef<HTMLCanvasElement>(null);
// 模式
// pattern
const [drawMode, setDrawMode] = useState<Mode | undefined>();
const latestDrawMode = useLatest(drawMode);
@@ -146,7 +146,7 @@ export const FabricEditor: FC<IProps> = props => {
| undefined
>();
// 监听鼠标是否处于按下状态,松手时才显示属性设置面板
// Monitor whether the mouse is pressed, and display the property settings panel when you let go
const [isMousePressing, setIsMousePressing] = useState(false);
const cancelContentMenu = useCallback(() => {
@@ -298,7 +298,7 @@ export const FabricEditor: FC<IProps> = props => {
},
};
// 针对画笔模式,达到上限后,主动退出绘画模式
// For brush mode, after reaching the upper limit, actively exit the painting mode
useEffect(() => {
if (drawMode && !couldAddNewObject) {
modeSetting[drawMode]?.exitFn();
@@ -307,7 +307,7 @@ export const FabricEditor: FC<IProps> = props => {
}, [couldAddNewObject, drawMode]);
const zoomStartPointer = useRef<Point>();
// 鼠标滚轮缩放
// mouse wheel zoom
const onWheelZoom = (e: WheelEvent, isFirst: boolean) => {
if (!canvas) {
return;
@@ -321,7 +321,7 @@ export const FabricEditor: FC<IProps> = props => {
zoomStartPointer.current = pointer;
}
// 根据滚轮方向确定是放大还是缩小
// Determine whether to zoom in or out according to the direction of the roller
if (delta < 0) {
zoomLevel += zoomStep;
} else {
@@ -333,7 +333,7 @@ export const FabricEditor: FC<IProps> = props => {
);
};
// 鼠标位移
// mouse displacement
const onWheelTransform = (deltaX: number, deltaY: number) => {
const vpt: TMat2D = [...viewport];
vpt[4] -= deltaX;
@@ -341,7 +341,7 @@ export const FabricEditor: FC<IProps> = props => {
setViewport(vpt);
};
// 触摸板手势缩放、位移
// Touchpad gesture zoom, shift
const gestureBind = useGesture(
{
onPinch: state => {
@@ -380,7 +380,7 @@ export const FabricEditor: FC<IProps> = props => {
},
);
// 当用户编辑文本时,按删除键不应该执行删除元素操作
// When a user edits text, pressing the delete key should not perform a delete element operation
const [isTextEditing, setIsTextEditing] = useState(false);
useEffect(() => {
let disposers: (() => void)[] = [];
@@ -426,7 +426,7 @@ export const FabricEditor: FC<IProps> = props => {
};
}, [sizeRef]);
// 点击画布外侧,取消选中
// Click on the outside of the canvas and uncheck it.
useEffect(() => {
const clickOutside = (e: MouseEvent) => {
setContentMenuPosition(undefined);
@@ -438,7 +438,7 @@ export const FabricEditor: FC<IProps> = props => {
};
}, [discardActiveObject]);
// 注册快捷键
// Registration shortcut
useShortcut({
ref: shortcutRef,
state: {
@@ -471,7 +471,7 @@ export const FabricEditor: FC<IProps> = props => {
const isContentMenuShow = !readonly && contentMenuPosition;
// 选中元素是否为同一类型(包含框选)
// Whether the selected elements are of the same type (including box selection)
const isSameActiveObjects =
Array.from(
new Set(
@@ -482,14 +482,14 @@ export const FabricEditor: FC<IProps> = props => {
).length === 1;
/**
* 属性菜单没有展示 &&
* 鼠标右键没有按下(拖拽 ing&&
* Properties menu not displayed & &
* The right mouse button is not pressed (drag and drop ing) & &
* isSameActiveObjects &&
*/
const isFormShow =
!isContentMenuShow && !isMousePressing && isSameActiveObjects;
// 最大宽高有两层限制 1. 面积 2. 固定最大值
// There are two restrictions on the maximum width and height: 1. Area 2. Fixed maximum
const { canvasMaxWidth, canvasMaxHeight } = useMemo(
() => ({
canvasMaxWidth: Math.min(MAX_AREA / schema.height, MAX_WIDTH),
@@ -708,7 +708,7 @@ export const FabricEditor: FC<IProps> = props => {
e.stopPropagation();
}}
>
{/* 引用 tag */}
{/* Reference tag */}
<RefTitle visible={!isMousePressing} />
<div className="w-fit h-fit overflow-hidden">
<div
@@ -719,7 +719,7 @@ export const FabricEditor: FC<IProps> = props => {
</div>
</div>
</div>
{/* 右键菜单 */}
{/* right-click menu */}
{isContentMenuShow ? (
<ContentMenu
limitRect={popoverSize}
@@ -745,10 +745,10 @@ export const FabricEditor: FC<IProps> = props => {
<></>
)}
{/* 属性面板 */}
{/* properties panel */}
{isFormShow ? (
<Form
// 文本切换时,涉及字号变化,需要 rerender form 同步状态
// Text switching, involving font size changes, need to rerender form synchronization state
key={
(activeObjects as FabricObjectWithCustomProps[])?.[0]
?.customType

View File

@@ -74,7 +74,7 @@ export const useShortcut = ({
horizontalAverage: () => void;
};
}) => {
// 上下左右微调元素位置
// Fine-tune element positions up, down, left, right
useKeyPress(
['uparrow', 'downarrow', 'leftarrow', 'rightarrow'],
e => {
@@ -101,7 +101,7 @@ export const useShortcut = ({
},
);
// 删除元素
// Delete element
useKeyPress(
['backspace', 'delete'],
e => {
@@ -118,7 +118,7 @@ export const useShortcut = ({
useKeyPress(
['ctrl.z', 'meta.z'],
e => {
// 一定要加,否则会命中浏览器乱七八糟的默认行为
// Be sure to add it, otherwise it will hit the browser's messy default behavior.
e.preventDefault();
if (e.shiftKey) {
redo();
@@ -133,7 +133,7 @@ export const useShortcut = ({
);
/**
* 功能开发暂停了,原因详见 packages/workflow/fabric-canvas/src/hooks/use-group.tsx
* Functional development has been suspended. For the reasons, see packages/workflow/fabricate-canvas/src/hooks/use-group.tsx
*/
// useKeyPress(
// ['ctrl.g', 'meta.g'],
@@ -181,11 +181,11 @@ export const useShortcut = ({
},
);
// 生成副本
// make a copy
useKeyPress(
['ctrl.d', 'meta.d'],
async e => {
// 必须阻止默认行为,否则会触发添加标签
// The default behavior must be blocked or the add label will be triggered
e.preventDefault();
await copy(CopyMode.CtrlD);
paste({
@@ -199,7 +199,7 @@ export const useShortcut = ({
},
);
// [ 下移一层
// [Move down one floor
useKeyPress(
['openbracket'],
e => {
@@ -214,7 +214,7 @@ export const useShortcut = ({
},
);
// ] 上移一层
// Move up one layer
useKeyPress(
['closebracket'],
e => {
@@ -228,7 +228,7 @@ export const useShortcut = ({
target: ref,
},
);
// ⌘ + [、⌘ + ] 禁止浏览器默认行为 前进、后退
// ⌘ + [、⌘ + ] disable browser default behavior, forward and backward
useKeyPress(
['meta.openbracket', 'meta.closebracket'],
e => {
@@ -243,7 +243,7 @@ export const useShortcut = ({
},
);
// ⌘ + [ 置底
// < unk > +
useKeyPress(
['meta.openbracket'],
e => {
@@ -258,7 +258,7 @@ export const useShortcut = ({
},
);
// + ] 置顶
// 🥰 +] top
useKeyPress(
['meta.closebracket'],
e => {
@@ -273,7 +273,7 @@ export const useShortcut = ({
},
);
// 水平居左
// Horizontal left
useKeyPress(
['alt.a'],
e => {
@@ -287,7 +287,7 @@ export const useShortcut = ({
},
);
// 水平居右
// Horizontal right
useKeyPress(
['alt.d'],
e => {
@@ -301,7 +301,7 @@ export const useShortcut = ({
},
);
// 水平居中
// centered text
useKeyPress(
['alt.h'],
e => {
@@ -315,7 +315,7 @@ export const useShortcut = ({
},
);
// 垂直居上
// vertical top
useKeyPress(
['alt.w'],
e => {
@@ -329,7 +329,7 @@ export const useShortcut = ({
},
);
// 垂直居下
// vertical
useKeyPress(
['alt.s'],
e => {
@@ -343,7 +343,7 @@ export const useShortcut = ({
},
);
// 垂直居中
// Vertically centered
useKeyPress(
['alt.v'],
e => {
@@ -357,7 +357,7 @@ export const useShortcut = ({
},
);
// 水平均分
// horizontal average fraction
useKeyPress(
['alt.ctrl.h'],
e => {
@@ -371,7 +371,7 @@ export const useShortcut = ({
},
);
// 垂直均分
// vertical equipartition
useKeyPress(
['alt.ctrl.v'],
e => {

View File

@@ -40,7 +40,7 @@ export const FabricPreview: FC<IFabricPreview> = props => {
oldWidth.current = size?.width || 0;
}
// 防止抖动,当宽度变化 > 20 时才更新宽度
// To prevent jitter, update the width when the width changes > 20
if (size?.width && size.width - oldWidth.current > 20) {
oldWidth.current = size?.width || 0;
}

View File

@@ -84,7 +84,7 @@ const FormItem = memo(
metaItem: FormMetaItem;
isLast: boolean;
isRow: boolean;
// 给图片上传组件特化的,需要根据是否为引用元素,设置不同的 label
// Specialized for the image upload component, you need to set different labels according to whether it is a reference element.
isRefElement: boolean;
formValue: Partial<FabricObjectSchema>;
onChange: (v: Partial<FabricObjectSchema>, cacheSave?: boolean) => void;
@@ -236,7 +236,7 @@ export const Form: FC<IProps> = props => {
[activeObjects],
);
// 临时保存不需要保存到 schema 中的表单值
// Temporary saving of form values that do not need to be saved to the schema
const [cacheFormValue, setCacheFormValue] = useState<
Partial<FabricObjectSchema>
>({});

View File

@@ -26,7 +26,7 @@ import {
import styles from './index.module.less';
/**
* size:small 的基础上,覆盖了 padding 5px -> 4px
* On the basis of size: small, overlay padding, 5px - > 4px
*/
export const MyIconButton = forwardRef<
SemiButton,

View File

@@ -66,8 +66,8 @@ export const PopInScreen: FC<IProps> = props => {
}
/**
* ahooks useSize 初次执行会返回 undefined,导致组件位置计算错误
* 这里监听 childrenSize ,如果为 undefined 则延迟 100ms 再渲染,以修正组件位置
* ahooks useSize returns undefined on first execution, resulting in an error in component location evaluation
* This listens to childrenSize. If it is undefined, delay rendering by 100ms to correct the component position.
*/
const [id, setId] = useState('');
const timer = useRef<NodeJS.Timeout>();
@@ -109,7 +109,7 @@ export const PopInScreen: FC<IProps> = props => {
transform,
}}
>
{/* 为了触发二次渲染 */}
{/* To trigger secondary rendering */}
<div className="hidden" id={id} />
{children}
</div>

View File

@@ -138,13 +138,13 @@ export const ColorPicker: FC<IProps> = props => {
})}
</div>
<Input
// 因为是不受控模式,当点击色块时,需要重置 input.value。所以这里以 color key
// Because it is in uncontrolled mode, when clicking on the color block, you need to reset the input.value. So here color is the key
key={`input-${color}`}
disabled={readonly}
prefix={<ColorRect color={color as string} size={16} />}
type="text"
className="w-[110px]"
// 为什么不使用受控模式?使用受控模式,用户输入过程中触发的格式校验处理起来比较麻烦
// Why not use controlled mode? With controlled mode, format checking triggered during user input is cumbersome to handle
defaultValue={color}
onChange={v => {
if (isHexColor(v)) {

View File

@@ -64,8 +64,8 @@ export const FontSize: FC<IProps> = props => {
<IconCozFontSize className="text-[16px] coz-fg-secondary m-[8px]" />
}
/**
* 因为开启了 allowCreate,所以 optionList 不会再响应动态变化
* 这里给个 key ,重新渲染 select保证 optionList 符合预期
* Since allowCreate is enabled, the optionList will no longer respond to dynamic changes
* Give a key here, re-render select, and ensure that the optionList meets expectations
*/
key={_optionsList.map(d => d.label).join()}
value={value}

View File

@@ -30,8 +30,8 @@ export const InputNumber = forwardRef<InputNumberProps, InputNumberProps>(
min={min}
max={max}
value={value}
// InputNumber 长按 + - 时,会一直触发变化。这里有 bug有时定时器清不掉会鬼畜一直增加/减小)。
// pressInterval 设置成 24h ,变相禁用长按增减
// InputNumber When long pressing + -, it will keep triggering changes. There are bugs here, and sometimes the timer can't be cleared, and it will be ghost (keep increasing/decreasing).
// Set pressInterval to 24h, and disable long press increase or decrease in disguise
pressInterval={1000 * 60 * 60 * 24}
onNumberChange={v => {
if (Number.isFinite(v)) {

View File

@@ -71,8 +71,8 @@ export const LineHeight: FC<IProps> = props => {
}
{...rest}
/**
* 因为开启了 allowCreate,所以 optionList 不会再响应动态变化
* 这里给个 key ,重新渲染 select保证 optionList 符合预期
* Since allowCreate is enabled, the optionList will no longer respond to dynamic changes
* Give a key here, re-render select, and ensure that the optionList meets expectations
*/
key={_optionsList.map(d => d.label).join()}
filter

View File

@@ -61,7 +61,7 @@ export const Align: FC<IProps> = props => {
</Select.Option>
);
return (
// 禁止冒泡防止点击对齐时canvas 的选中状态被清空
// Prohibit bubbling to prevent the selected state of canvas from being cleared when clicking align
<div
onClick={e => {
e.stopPropagation();

View File

@@ -193,7 +193,7 @@ export const TopBar: FC<IProps> = props => {
aligns,
} = props;
// 点击已选中的,则取消选中
// Click on the selected one to unselect it.
const onModeChange = useCallback(
(m: Mode | undefined) => {
if (m === mode) {
@@ -233,7 +233,7 @@ export const TopBar: FC<IProps> = props => {
'flex justify-center items-center gap-[12px]',
])}
>
{/* 引用变量 */}
{/* reference variable */}
<Tooltip
key="ref-variable"
content={I18n.t('workflow_detail_condition_reference')}
@@ -317,7 +317,7 @@ export const TopBar: FC<IProps> = props => {
</Tooltip>
<SplitLine />
{/* 画布基础设置 */}
{/* canvas base settings */}
<Tooltip
key="canvas-setting"
position="bottom"
@@ -407,7 +407,7 @@ export const TopBar: FC<IProps> = props => {
/>
</Tooltip>
{/* 重置视图 */}
{/* Reset view */}
<Tooltip
key="reset-view"
content={I18n.t('imageflow_canvas_restart')}
@@ -489,7 +489,7 @@ export const TopBar: FC<IProps> = props => {
</Tooltip>
<SplitLine />
{/* 置底 置顶 */}
{/* Bottom, top */}
<Tooltip
key="move-to-bottom"
content={I18n.t('card_builder_move_to_bottom')}
@@ -512,12 +512,12 @@ export const TopBar: FC<IProps> = props => {
icon={<IconCozMoveToTopFill className="text-[16px]" />}
/>
</Tooltip>
{/* 对齐 */}
{/* align */}
<div className="flex">
<MyIconButton
disabled={readonly}
onClick={e => {
// 禁止冒泡防止点击对齐时canvas 的选中状态被清空
// Prohibit bubbling to prevent the selected state of canvas from being cleared when clicking align
e.stopPropagation();
aligns[alignType]();
}}
@@ -534,7 +534,7 @@ export const TopBar: FC<IProps> = props => {
/>
</div>
{/* 文本 */}
{/* Text */}
<div className="flex">
<Tooltip
key="text"
@@ -583,7 +583,7 @@ export const TopBar: FC<IProps> = props => {
}}
/>
</div>
{/* 图片 */}
{/* picture */}
<ImageUpload
onChange={onAddImg}
@@ -595,7 +595,7 @@ export const TopBar: FC<IProps> = props => {
/>
</ImageUpload>
{/* 形状 */}
{/* shape */}
<div className="flex">
<Tooltip
key="shape"
@@ -656,7 +656,7 @@ export const TopBar: FC<IProps> = props => {
/>
</div>
{/* 自由画笔 */}
{/* Free brush */}
<Tooltip
key="pencil"
content={I18n.t('imageflow_canvas_draw')}

View File

@@ -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
* 这里主动清除下,否则字体相关的设置(fontSizefontFamily...)不生效
* 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
});
}),
);

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 ?? [];
}, []);

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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();

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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 },

View File

@@ -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;

View File

@@ -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]);

View File

@@ -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 },

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -15,8 +15,8 @@
*/
/**
* 这个文件仅实现 setImageFixed 一个函数就好
* nodejs 图片渲染同样需要计算位置。要在 packages/workflow/nodejs/fabric-render 实现一份功能完全一致的 js 版
* This file only implements one function setImageFixed
* Nodejs image rendering also needs to calculate the position. To implement a fully functional js version in packages/workflow/nodejs/fabricate-render
*/
import {
type FabricImage,
@@ -28,7 +28,7 @@ import {
import { ImageFixedType, type FabricObjectWithCustomProps } from './typings';
/**
* 调整 img 位置
* Adjust img position
*/
export const setImageFixed = ({ element }: { element: FabricObject }) => {
const { width, height } = element;
@@ -38,7 +38,7 @@ export const setImageFixed = ({ element }: { element: FabricObject }) => {
const borderRect = (element as Group).getObjects()[1] as Rect;
const { strokeWidth = 0 } = borderRect;
// 填充/拉伸时,框适配 group 大小即可
// When filling/stretching, the box fits the group size
const borderRectWidth = width - strokeWidth;
const borderRectHeight = height - strokeWidth;
borderRect.set({
@@ -51,11 +51,11 @@ export const setImageFixed = ({ element }: { element: FabricObject }) => {
const { width: originWidth, height: originHeight } = img.getOriginalSize();
/**
* 为什么 +1
* 经过计算后,存储位数有限,不管是 scaleX/Y width/height top/left都会丢失一点点精度
* 这点精度反馈到图片上,就是图片与边框有一点点间隙
* 这里 +1 让图片显示的稍微大一点,弥补精度带来的间隙。
* 弊端:边框会覆盖一点点图片(覆盖多少看缩放比),用户基本无感
* Why + 1?
* After calculation, the number of storage bits is limited, whether it is scaleX/Y width/height top/left, a little precision will be lost
* This accuracy is fed back to the picture, that is, there is a little gap between the picture and the border.
* Here + 1 makes the image appear slightly larger to make up for the gap in accuracy.
* Disadvantages: The border will cover a little bit of the picture (how much to cover depends on the zoom ratio), and the user basically has no feeling
*/
const realScaleX = (width - strokeWidth * 2 + 1) / originWidth;
const realScaleY = (height - strokeWidth * 2 + 1) / originHeight;
@@ -74,7 +74,7 @@ export const setImageFixed = ({ element }: { element: FabricObject }) => {
const imgLeft = -(originWidth * scaleX) / 2;
const imgTop = -(originHeight * scaleY) / 2;
// 自适应时需要对图片描边
// When adapting, you need to stroke the picture
if (customFixedType === ImageFixedType.AUTO) {
borderRect.set({
width: Math.min(borderRectWidth, originWidth * scaleX + strokeWidth),

View File

@@ -212,8 +212,8 @@ export const fontSvg = [
'47-Bolderslant.svg',
'48-PinyonScript.svg',
'49-ZYLAADeepblue.svg',
// 站酷庆科黄油体 加载总是失败 ,找不到原因,暂时屏蔽
// '5-站酷庆科黄油体.svg',
// Station Kuqingke butter body, the loading always fails, the reason cannot be found, and it is temporarily blocked.
// '5-Station Kuqingke Butter Body.svg ',
'50-ZYLAASylph.svg',
'51-ZYENAFetching.svg',
'52-ZYLAACosy.svg',

View File

@@ -30,7 +30,7 @@ export interface FabricObjectSchema extends CustomFabricProps {
lineHeight?: number;
text?: string;
/**
* 图片链接
* image link
*/
src?: string;
objects?: FabricObjectSchema[];
@@ -50,9 +50,9 @@ export interface FabricSchema extends FabricObjectSchema {
}
/**
* 为什么不用 FabricObject.type
* 因为 fabricSchema.type fabricObject.type 对不上
* eg Textbox 在 schema 里是 textbox实例化后是 Textbox
* Why not use FabricObject.type?
* Because fabricSchema.type does not match fabricObject.type
* Eg: Textbox is a textbox in the schema, and a Textbox after instantiation
*/
export enum Mode {
INLINE_TEXT = 'inline_text',
@@ -67,7 +67,7 @@ export enum Mode {
}
/**
* 填充和描边
* Fill and Stroke
*/
export enum ColorMode {
FILL = 'fill',
@@ -75,7 +75,7 @@ export enum ColorMode {
}
/**
* 文本对齐方式
* text alignment
*/
export enum TextAlign {
LEFT = 'left',
@@ -84,7 +84,7 @@ export enum TextAlign {
JUSTIFY = 'justify',
}
/**
* 图片填充方式
* Image filling method
*/
export enum ImageFixedType {
AUTO = 'auto',
@@ -97,7 +97,7 @@ export interface CustomFabricProps {
customId: string;
customFixedHeight?: number;
customFixedType?: ImageFixedType;
/** @deprecated 兼容历史,不可新增消费 */
/** @deprecated compatible history, no new consumption */
customVariableName?: string;
[k: string]: unknown;
}

View File

@@ -25,7 +25,7 @@ import { type FabricObjectSchema } from './share/typings';
export interface FormMetaItem {
name?: string;
title?: string;
// 临时存储,不保存到后端
// Temporary storage, not saved to the backend
cacheSave?: boolean;
visible?: (formValue: Partial<FabricObjectSchema>) => boolean;
setter:

View File

@@ -82,7 +82,7 @@ type GetControls = (props?: {
needResetScaleAndSnap?: boolean;
}) => Control;
/**
* 直线起点控制点
* Straight Start Control Point
*/
export const getLineStartControl: GetControls = (props = {}) => {
const { x, y, callback } = props;
@@ -102,12 +102,12 @@ export const getLineStartControl: GetControls = (props = {}) => {
callback?.({ element: transformData.target });
return true;
},
actionName: 'startControl', // 控制点的名称
actionName: 'startControl', // The name of the control point
});
};
/**
* 直线终点控制点
* straight endpoint control point
*/
export const getLineEndControl: GetControls = (props = {}) => {
const { x, y, callback } = props;
@@ -127,7 +127,7 @@ export const getLineEndControl: GetControls = (props = {}) => {
callback?.({ element: transformData.target });
return true;
},
actionName: 'endControl', // 控制点的名称
actionName: 'endControl', // The name of the control point
});
};
@@ -191,7 +191,7 @@ const scaleToSize = (
transformData.target.set({
width: targetWidth,
height: targetHeight,
// textBox 特化属性
// textBox specialization property
customFixedHeight: targetHeight,
scaleX: 1,
scaleY: 1,
@@ -200,11 +200,11 @@ const scaleToSize = (
});
};
/**
* 直接问 GPT
一个矩形,宽度 w ,高度 h
将矩形顺时针旋转角度 a左上角坐标为 x1 y1
拉伸矩形左上角,使矩形右下角保持不变,宽度增加到 w1高度增加到 h1
求左上角坐标
* Ask the GPT directly:
A rectangle, width w, height h
Rotate the rectangle clockwise by angle a with the upper left coordinate x1 y1.
Stretch the upper left corner of the rectangle so that the lower right corner of the rectangle remains unchanged, increasing the width to w1 and the height to h1.
Find the coordinates of the upper left corner
*/
const calcLeftTopByTopLeft: LeftTopCalcFn = ({
angle,
@@ -233,12 +233,12 @@ const calcLeftTopByTopLeft: LeftTopCalcFn = ({
};
/**
* 直接问 GPT
一个矩形,宽度 w ,高度 h
将矩形顺时针旋转角度 a , 左上角坐标为 x1 y1
拉伸矩形右上角,使矩形左下角保持不变,宽度增加到 w1高度增加到 h1
求左上角坐标
GPT 给的答案不准确,需要稍微理解下,修改加减)
* Ask the GPT directly:
A rectangle, width w, height h
Rotate the rectangle clockwise by angle a, with the upper left coordinate x1 y1.
Stretch the upper right corner of the rectangle so that the lower left corner of the rectangle remains unchanged, increasing the width to w1 and the height to h1.
Find the coordinates of the upper left corner
(The answer given by GPT is inaccurate, you need to understand it a little, modify it, add and subtract)
*/
const calcLeftTopByTopRight: LeftTopCalcFn = ({
angle,
@@ -255,13 +255,13 @@ const calcLeftTopByTopRight: LeftTopCalcFn = ({
};
/**
* 直接问 GPT
一个矩形,宽度 w ,高度 h
将矩形顺时针旋转角度 a , 左上角坐标为 x1 y1
拉伸矩形左下角,使矩形右上角保持不变,宽度增加到 w1高度增加到 h1
求左上角坐标
* Ask the GPT directly:
A rectangle, width w, height h
Rotate the rectangle clockwise by angle a, with the upper left coordinate x1 y1.
Stretch the lower left corner of the rectangle so that the upper right corner of the rectangle remains unchanged, increasing the width to w1 and the height to h1.
Find the coordinates of the upper left corner
GPT 给的答案不准确,这个比较麻烦,所以写出了每一步的推导过程
The answer given by GPT is inaccurate, which is more troublesome, so I wrote out the derivation process of each step
*/
const calcLeftTopByBottomLeft: LeftTopCalcFn = ({
angle,
@@ -271,18 +271,18 @@ const calcLeftTopByBottomLeft: LeftTopCalcFn = ({
newWidth,
newHeight,
}) => {
// 将角度转换为弧度
// Convert angle to radians
const aRad = (angle * Math.PI) / 180;
// 计算旋转后的右上角坐标
// Calculate the coordinates of the upper right corner after rotation
const x2 = originLeft + originWidth * Math.cos(aRad);
const y2 = originTop + originWidth * Math.sin(aRad);
// 计算拉伸后的左下角坐标
// Calculate the lower left corner coordinates after stretching
const x3 = x2 - newHeight * Math.sin(aRad) - newWidth * Math.cos(aRad);
const y3 = y2 + newHeight * Math.cos(aRad) - newWidth * Math.sin(aRad);
// 计算拉伸后的左上角坐标
// Calculate the coordinates of the upper left corner after stretching
const x1New = x3 + newHeight * Math.sin(aRad);
const y1New = y3 - newHeight * Math.cos(aRad);
return {
@@ -344,7 +344,7 @@ const _actionHandler = ({
leftTopCalcFn?: LeftTopCalcFn;
}) => {
const rs = fn(
// 如果使用吸附则禁用默认缩放;否则取反
// Disable default scaling if adsorption is used; otherwise reverse
{ ...e, shiftKey: needResetScaleAndSnap ? true : !e.shiftKey },
transformData,
x,
@@ -363,7 +363,7 @@ const _actionHandler = ({
};
/**
* 上左
* upper left
*/
export const getResizeTLControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap = true } = props;
@@ -386,11 +386,11 @@ export const getResizeTLControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeTLControl', // 控制点的名称
actionName: 'resizeTLControl', // The name of the control point
});
};
/**
* 上中
* upper middle school
*/
export const getResizeMTControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap = true } = props;
@@ -413,12 +413,12 @@ export const getResizeMTControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeMTControl', // 控制点的名称
actionName: 'resizeMTControl', // The name of the control point
});
};
/**
* 上右
* upper right
*/
export const getResizeTRControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -441,12 +441,12 @@ export const getResizeTRControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeTRControl', // 控制点的名称
actionName: 'resizeTRControl', // The name of the control point
});
};
/**
* 中左
* center left
*/
export const getResizeMLControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -470,12 +470,12 @@ export const getResizeMLControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeMLControl', // 控制点的名称
actionName: 'resizeMLControl', // The name of the control point
});
};
/**
* 中右
* center right
*/
export const getResizeMRControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -498,12 +498,12 @@ export const getResizeMRControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeMRControl', // 控制点的名称
actionName: 'resizeMRControl', // The name of the control point
});
};
/**
* 下左
* Lower left
*/
export const getResizeBLControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -527,12 +527,12 @@ export const getResizeBLControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeBLControl', // 控制点的名称
actionName: 'resizeBLControl', // The name of the control point
});
};
/**
* 下中
* lower middle
*/
export const getResizeMBControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -555,12 +555,12 @@ export const getResizeMBControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeMBControl', // 控制点的名称
actionName: 'resizeMBControl', // The name of the control point
});
};
/**
* 下右
* Lower right
*/
export const getResizeBRControl: GetControls = (props = {}) => {
const { callback, needResetScaleAndSnap } = props;
@@ -583,7 +583,7 @@ export const getResizeBRControl: GetControls = (props = {}) => {
return rs;
},
mouseDownHandler: _mouseDownHandler,
actionName: 'resizeBRControl', // 控制点的名称
actionName: 'resizeBRControl', // The name of the control point
});
};
@@ -605,7 +605,7 @@ const _getRotateControl =
}): GetControls =>
(props = {}) => {
const { callback } = props;
// 这个大小,取决于 resize 控制点的大小
// This size depends on the size of the resize control point
const offset = 12;
return new Control({
x,
@@ -614,15 +614,15 @@ const _getRotateControl =
sizeY: 20,
offsetY: offsetY * offset,
offsetX: offsetX * offset,
// 覆盖旋转控制点渲染,预期不显示,所以啥都没写
// Override the rotation control point rendering, it is not expected to be displayed, so nothing is written.
// eslint-disable-next-line @typescript-eslint/no-empty-function
render: () => {},
// 只能做到 hover 上时的 cursor旋转过程中 cursor 无法修改
// You can only do the cursor when hovering, and the cursor cannot be modified during rotation.
cursorStyleHandler: (eventData, control, object) =>
`url(${getCursor(object.angle + rotateStaff)}) 16 16, crosshair`,
actionHandler: (e, transformData, _x, _y) => {
// 旋转吸附,单位:角度 一圈 = 360度
// Rotational adsorption, unit: angle, one turn = 360 degrees
if (e.shiftKey) {
transformData.target.set({
snapAngle: 15,
@@ -647,11 +647,11 @@ const _getRotateControl =
return rs;
},
actionName, // 控制点的名称
actionName, // The name of the control point
});
};
// 上左旋转点
// Top Left Rotation Point
export const getRotateTLControl: GetControls = (props = {}) =>
_getRotateControl({
x: -0.5,
@@ -662,7 +662,7 @@ export const getRotateTLControl: GetControls = (props = {}) =>
actionName: 'rotateTLControl',
})(props);
// 上右旋转点
// Top right rotation point
export const getRotateTRControl: GetControls = (props = {}) =>
_getRotateControl({
x: 0.5,
@@ -673,7 +673,7 @@ export const getRotateTRControl: GetControls = (props = {}) =>
actionName: 'rotateTRControl',
})(props);
// 下右旋转点
// Lower right rotation point
export const getRotateBRControl: GetControls = (props = {}) =>
_getRotateControl({
x: 0.5,
@@ -684,7 +684,7 @@ export const getRotateBRControl: GetControls = (props = {}) =>
actionName: 'rotateBRControl',
})(props);
// 下左旋转点
// Lower left rotation point
export const getRotateBLControl: GetControls = (props = {}) =>
_getRotateControl({
x: -0.5,

View File

@@ -44,27 +44,27 @@ export const setLineControlVisible = ({
const { x1, x2, y1, y2 } = element as Line;
if ((x1 < x2 && y1 < y2) || (x1 > x2 && y1 > y2)) {
element.setControlsVisibility({
ml: false, // 中点左
mr: false, // 中点右
mt: false, // 中点上
mb: false, // 中点下
bl: false, // 底部左
br: true, // 底部右
tl: true, // 顶部左
tr: false, // 顶部右
mtr: false, // 旋转控制点
ml: false, // Midpoint left
mr: false, // Midpoint right
mt: false, // midpoint
mb: false, // midpoint
bl: false, // Bottom left
br: true, // Bottom right
tl: true, // Top Left
tr: false, // Top right
mtr: false, // Rotation Control Point
});
} else {
element.setControlsVisibility({
ml: false, // 中点左
mr: false, // 中点右
mt: false, // 中点上
mb: false, // 中点下
bl: true, // 底部左
br: false, // 底部右
tl: false, // 顶部左
tr: true, // 顶部右
mtr: false, // 旋转控制点
ml: false, // Midpoint left
mr: false, // Midpoint right
mt: false, // midpoint
mb: false, // midpoint
bl: true, // Bottom left
br: false, // Bottom right
tl: false, // Top Left
tr: true, // Top right
mtr: false, // Rotation Control Point
});
}
};
@@ -82,50 +82,50 @@ const getCommonControl = ({
needResetScaleAndSnap?: boolean;
}) => {
element.setControlsVisibility({
mtr: false, // 旋转控制点
mtr: false, // Rotation Control Point
});
// resize
// 上左
// upper left
element.controls.tl = getResizeTLControl({
needResetScaleAndSnap,
});
// 上中
// upper middle school
element.controls.mt = getResizeMTControl({
needResetScaleAndSnap,
});
// 上右
// upper right
element.controls.tr = getResizeTRControl({
needResetScaleAndSnap,
});
// 中左
// center left
element.controls.ml = getResizeMLControl({
needResetScaleAndSnap,
});
// 中右
// center right
element.controls.mr = getResizeMRControl({
needResetScaleAndSnap,
});
// 下左
// Lower left
element.controls.bl = getResizeBLControl({
needResetScaleAndSnap,
});
// 下中
// lower middle
element.controls.mb = getResizeMBControl({
needResetScaleAndSnap,
});
// 下右
// Lower right
element.controls.br = getResizeBRControl({
needResetScaleAndSnap,
});
// rotate
// 上左
// upper left
element.controls.tlr = getRotateTLControl();
// 上右
// upper right
element.controls.trr = getRotateTRControl();
// 下左
// Lower left
element.controls.blr = getRotateBLControl();
// 下右
// Lower right
element.controls.brr = getRotateBRControl();
};
@@ -135,27 +135,27 @@ export const createControls: Partial<
[Mode.STRAIGHT_LINE]: ({ element }) => {
setLineControlVisible({ element });
// 左上
// top left
element.controls.tl = getLineStartControl({
x: -0.5,
y: -0.5,
callback: setLineControlVisible,
});
// 右上
// top right
element.controls.tr = getLineStartControl({
x: 0.5,
y: -0.5,
callback: setLineControlVisible,
});
// 左下
// lower left
element.controls.bl = getLineEndControl({
x: -0.5,
y: 0.5,
callback: setLineControlVisible,
});
// 右下
// lower right
element.controls.br = getLineEndControl({
x: 0.5,
y: 0.5,
@@ -173,7 +173,7 @@ export const createControls: Partial<
},
[Mode.CIRCLE]: ({ element }) => {
element.setControlsVisibility({
mtr: false, // 旋转控制点
mtr: false, // Rotation Control Point
});
const controlProps = {
@@ -195,7 +195,7 @@ export const createControls: Partial<
},
[Mode.BLOCK_TEXT]: ({ element }) => {
element.setControlsVisibility({
mtr: false, // 旋转控制点
mtr: false, // Rotation Control Point
});
const controlProps = {
@@ -220,7 +220,7 @@ export const createControls: Partial<
},
[Mode.INLINE_TEXT]: ({ element }) => {
element.setControlsVisibility({
mtr: false, // 旋转控制点
mtr: false, // Rotation Control Point
});
element.controls.tlr = getRotateTLControl();
@@ -230,7 +230,7 @@ export const createControls: Partial<
},
[Mode.IMAGE]: ({ element }) => {
element.setControlsVisibility({
mtr: false, // 旋转控制点
mtr: false, // Rotation Control Point
});
const controlProps = {

View File

@@ -22,7 +22,7 @@ import {
} from '../typings';
/**
* 选中态边框及控制点样式
* Selected border and control point styles
*/
export const selectedBorderProps = {
borderColor: '#4D53E8',
@@ -60,7 +60,7 @@ export const defaultProps: Record<Mode, Partial<FabricObjectSchema>> = {
width: 200,
height: 200,
padding: defaultFontSize / 2,
// 必须拆分true否则中文不会换行。splitByGrapheme:true 约等于 wordBreak: break-all
// It must be split (true), otherwise Chinese will not wrap lines. splitByGrapheme: true is approximately equal to wordBreak: break-all
splitByGrapheme: true,
},
[Mode.RECT]: shapeProps,

View File

@@ -17,9 +17,9 @@
/* eslint-disable @coze-arch/max-line-per-function */
/* eslint-disable complexity */
/**
* 托管所有的图形创建,赋予业务改造
* 调用时机
* 1. 初次创建
* Hosting all graphics creation, empowering business transformation
* call timing
* 1. Initial creation
* 2. loadFromSchema
*/
import { nanoid } from 'nanoid';
@@ -49,9 +49,9 @@ import { defaultProps } from './default-props';
import { createControls, setLineControlVisible } from './create-controls';
/**
* 覆盖默认的 Textbox height 计算逻辑
* 默认:根据内容,撑起 Textbox
* 预期:严格按照给定高度渲染,溢出隐藏
* Override default Textbox height calculation logic
* Default: Hold up Textbox according to content
* Expected: Render strictly according to the given height, overflow hidden
*/
const _calcTextHeight = Textbox.prototype.calcTextHeight;
Textbox.prototype.calcTextHeight = function () {
@@ -60,8 +60,8 @@ Textbox.prototype.calcTextHeight = function () {
};
/**
* 修复 fabric bug使用某些自定义字体后Text 宽度计算异常
* 修复方案 from https://github.com/fabricjs/fabric.js/issues/9852
* Fix fabric bug: Text width calculation is abnormal after using some custom fonts
* Repair plan from https://github.com/fabricjs/fabric.js/issues/9852
*/
IText.getDefaults = () => ({});
// for each class in the chain that has a ownDefaults object:
@@ -80,7 +80,7 @@ export const createCommonObjectOptions = (
});
/**
* 元素创建入口,所有的元素创建逻辑都走这里
* Element creation portal, where all element creation logic goes
*/
export const createElement = async ({
mode,
@@ -170,7 +170,7 @@ export const createElement = async ({
...elementProps,
});
// 创建直线时的起始点不是通过控制点触发的,需要额外监听
// The starting point when creating a line is not triggered by a control point and requires additional listening
_element.on('start-end:modified' as keyof ObjectEvents, () => {
setLineControlVisible({
element: _element as Line,
@@ -242,14 +242,14 @@ export const createElement = async ({
let _element: FabricObject | undefined = element;
/**
* FabricImage 不支持拉伸/自适应
* 需要用 Group 包一下,根据 Group 大小,计算 image 的位置
* 1. customId customType 要给到 Group。根其他元素保持一致从 element 上能直接取到)
* 2. 边框的相关设置要给到图片
* FabricImage does not support stretching/adaptive
* You need to wrap it in Group, and calculate the position of the image according to the size of the Group.
* 1. customId customType should be given to the Group. (The root of other elements is consistent and can be directly retrieved from element)
* 2. The relevant settings of the border should be given to the picture.
*
* 因此而产生的 Hack
* 1. 属性表单根据 schema 解析成 formValue ,需要取 groupSchema.objects[0]
* 2. 属性表单设置元素属性(边框),需要调用 group.getObjects()[0].set
* The resulting hack:
* 1. The attribute form is parsed into formValue according to the schema, and you need to take groupSchema.objects [0]
* 2. The property form sets the element properties (borders), you need to call group.getObjects () [0] .set
*/
if (!_element) {
if (elementProps?.src) {
@@ -264,7 +264,7 @@ export const createElement = async ({
top ?? ((elementProps?.top ?? defaultProps[_mode].top) as number);
/**
* stroke, strokeWidth 设置给 borderRect objects[1]
* Stroke, strokeWidth set for borderRect objects [1]
*/
const { stroke, strokeWidth, ...rest } = {
...defaultProps[_mode],
@@ -294,7 +294,7 @@ export const createElement = async ({
(_element as Group).add(borderRect);
_element.set(groupProps);
// 比例填充时,图片会溢出,所以加了裁剪
// When filling in proportions, the image will overflow, so cropping is added.
const clipRect = new Rect();
_element.set({
clipPath: clipRect,
@@ -304,7 +304,7 @@ export const createElement = async ({
resetElementClip({ element: _element as FabricObject });
// 计算 image 的渲染位置
// Calculate the render position of the image
setImageFixed({
element: _element as Group,
});
@@ -342,7 +342,7 @@ export const createElement = async ({
}
};
// hook element 加载到画布
// Load hook elements to canvas
export const setElementAfterLoad = async ({
element,
options: { readonly },

View File

@@ -25,7 +25,7 @@ import {
import { Mode, type FabricObjectWithCustomProps } from '../typings';
/**
* 缩放到指定点
* Zoom to the specified point
*/
export const zoomToPoint = ({
canvas,
@@ -40,17 +40,17 @@ export const zoomToPoint = ({
minZoom: number;
maxZoom: number;
}): TMat2D => {
// 设置缩放级别的限制
zoomLevel = Math.max(zoomLevel, minZoom); // 最小缩放级别
zoomLevel = Math.min(zoomLevel, maxZoom); // 最大缩放级别
// Set limits on zoom levels
zoomLevel = Math.max(zoomLevel, minZoom); // minimum zoom level
zoomLevel = Math.min(zoomLevel, maxZoom); // Maximum zoom level
// 以鼠标位置为中心进行缩放
// Zoom centered on mouse position
canvas?.zoomToPoint(point, zoomLevel);
return [...(canvas?.viewportTransform as TMat2D)];
};
/**
* 设置 canvas 视图
* Set canvas view
*/
export const setViewport = ({
canvas,
@@ -65,7 +65,7 @@ export const setViewport = ({
};
/**
* 画布坐标点距离画布左上角距离(单位:px
* Canvas coordinate point distance from the upper left corner of the canvas (unit: px)
*/
export const canvasXYToScreen = ({
canvas,
@@ -76,10 +76,10 @@ export const canvasXYToScreen = ({
scale: number;
point: { x: number; y: number };
}) => {
// 获取画布的变换矩阵
// Get the transformation matrix of the canvas
const transform = canvas.viewportTransform;
// 应用缩放和平移
// Apply scaling and panning
const zoomX = transform[0];
const zoomY = transform[3];
const translateX = transform[4];
@@ -88,11 +88,11 @@ export const canvasXYToScreen = ({
const screenX = (point.x * zoomX + translateX) * scale;
const screenY = (point.y * zoomY + translateY) * scale;
// 获取画布在屏幕上的位置
// Get the position of the canvas on the screen
const x = screenX;
const y = screenY;
// 不做限制
// No restrictions
return {
x,
y,
@@ -100,7 +100,7 @@ export const canvasXYToScreen = ({
};
/**
* 得到选中元素的屏幕坐标(左上 tl、右下 br
* Get the screen coordinates of the selected element (upper left tl, lower right br)
*/
export const getPopPosition = ({
canvas,
@@ -113,13 +113,13 @@ export const getPopPosition = ({
if (canvas && selection) {
const boundingRect = selection.getBoundingRect();
// 左上角坐标
// upper left coordinate
const tl = {
x: boundingRect.left,
y: boundingRect.top,
};
// 右下角坐标
// Lower right corner coordinates
const br = {
x: boundingRect.left + boundingRect.width,
y: boundingRect.top + boundingRect.height,

View File

@@ -22,7 +22,7 @@ import {
} from '../typings';
/**
* fabric schema formValue
* Fabric schema to formValue
*/
export const schemaToFormValue = ({
schema,
@@ -35,12 +35,12 @@ export const schemaToFormValue = ({
}): Partial<FabricObjectSchema> => {
let s = schema.objects.find(o => o.customId === activeObjectId);
// 图片是 Group 复合元素,要把需要的元素取出来
// The picture is a Group composite element, and the required elements should be taken out.
if (s?.customType === Mode.IMAGE) {
s = {
...s,
...s.objects?.[0],
// 描边颜色和粗细从 borderRect 上取
// Stroke color and thickness are taken from borderRect
stroke: s.objects?.[1].stroke,
strokeWidth: s.objects?.[1].strokeWidth,
} as unknown as FabricObjectSchema;

View File

@@ -53,7 +53,7 @@ const getLineHtml = (startXY: Snap.Point, endXY: Snap.Point) => {
"
></div>`;
} else {
// 横线
// horizontal line
innerHTML += `<div
class="absolute bg-[#00B2B2]"
style="

View File

@@ -28,20 +28,20 @@ type Attribute =
interface Config {
/**
* 影响到的属性
* Affected properties
*/
key: Attribute;
/**
* 吸附方向
* adsorption direction
*/
direction: 'x' | 'y';
/**
* 吸附到的值
* Adsorbed value
*/
snapValue: number[];
}
// 计算目标元素的未来位置
// Calculate the future position of the target element
const getNextHelplinePoint = ({
targetPoint,
latestDistance,
@@ -94,7 +94,7 @@ const getNextHelplinePoint = ({
}));
};
// 计算吸附结果
// Calculate adsorption results
const getNextRs = ({
latestDistance,
latestDistanceAbs,
@@ -176,7 +176,7 @@ const getNextRs = ({
return {};
};
// 对齐规则
// alignment rule
export const alignRule: Snap.Rule = ({
otherPoints,
targetPoint,
@@ -275,27 +275,27 @@ export const alignRule: Snap.Rule = ({
const config = configMap[controlType];
config.forEach(item => {
// 需要判断吸附的点位集合
// It is necessary to determine the set of adsorption points
const points = item.snapValue;
// 找到距离最近的吸附点集合
// Find the closest collection of adsorption points
const {
snapPoints,
distance: latestDistance,
distanceAbs: latestDistanceAbs,
} = findLatestObject(otherPoints, points, item.direction);
// 如果距离小于阈值,则进行吸附
// If the distance is less than the threshold, adsorption is performed
if (latestDistanceAbs <= threshold) {
const helplines: Snap.Line[] = [];
const allPoints: (Snap.Point & { isTarget?: boolean })[] = [];
// 将所有其他对象的点添加到 allPoints
// Add the points of all other objects to allPoints
otherPoints.forEach(a => {
allPoints.push(...Object.values(a));
});
// 将目标对象的点添加到 allPoints
// Add the target object's points to allPoints
allPoints.push(
...getNextHelplinePoint({
targetPoint,
@@ -305,10 +305,10 @@ export const alignRule: Snap.Rule = ({
}),
);
// 根据吸附方向对 allPoints 进行排序
// Sort allPoints by adsorption direction
const sortKey = item.direction === 'x' ? 'y' : 'x';
// 根据吸附结果,从所有点中挑选出需要绘制辅助线的点
// According to the adsorption results, select the points where the auxiliary line needs to be drawn from all the points
snapPoints.forEach(sp => {
const _helpline = allPoints
.filter(p => numberEqual(p[item.direction], sp[item.direction]))
@@ -328,7 +328,7 @@ export const alignRule: Snap.Rule = ({
}
});
// x,y 一起吸附,需要对 x 吸附线的 y 坐标进行修正
// X and y are adsorbed together, and the y coordinate of the x adsorption line needs to be corrected.
if (
controlType === Snap.ControlType.Center &&
rs.top?.isSnap &&
@@ -347,7 +347,7 @@ export const alignRule: Snap.Rule = ({
);
}
// x,y 一起吸附,需要对 y 吸附线的 x 坐标进行修正
// X and y are adsorbed together, and the x coordinate of the y adsorption line needs to be corrected.
if (
controlType === Snap.ControlType.Center &&
rs.left?.isSnap &&

View File

@@ -21,7 +21,7 @@
import { numberEqual } from '../util';
import { Snap } from '../../../typings';
// 获取对象的底部点
// Get the bottom point of the object
const getBottomPoint = (p: Snap.ObjectPointsWithMiddle) => {
let point = p.tl;
Object.values(p).forEach(d => {
@@ -31,7 +31,7 @@ const getBottomPoint = (p: Snap.ObjectPointsWithMiddle) => {
});
return point;
};
// 获取对象的顶部点
// Get the top point of the object
const getTopPoint = (p: Snap.ObjectPointsWithMiddle) => {
let point = p.tl;
Object.values(p).forEach(d => {
@@ -42,7 +42,7 @@ const getTopPoint = (p: Snap.ObjectPointsWithMiddle) => {
return point;
};
// 获取对象的左侧点
// Get the left point of the object
const getLeftPoint = (p: Snap.ObjectPointsWithMiddle) => {
let point = p.tl;
Object.values(p).forEach(d => {
@@ -53,7 +53,7 @@ const getLeftPoint = (p: Snap.ObjectPointsWithMiddle) => {
return point;
};
// 获取对象的右侧点
// Get the right point of the object
const getRightPoint = (p: Snap.ObjectPointsWithMiddle) => {
let point = p.tl;
Object.values(p).forEach(d => {
@@ -64,7 +64,7 @@ const getRightPoint = (p: Snap.ObjectPointsWithMiddle) => {
return point;
};
// 判断两段范围是否重叠
// Determine whether the two ranges overlap
const isInDuration = (
target: {
min: number;
@@ -93,7 +93,7 @@ const getMiddle = (
return target.max - (target.max - duration.min) / 2;
};
// 找到指定方向(横/纵)距离最近的左右两个对象的相关信息
// Find the relevant information about the two objects closest to the specified direction (horizontal/vertical)
const findLatestObject = ({
otherPoints: _otherPoints,
targetPoint,
@@ -204,7 +204,7 @@ const findLatestObject = ({
return rs;
};
// 等距吸附规则
// isometric adsorption rule
export const paddingRule: Snap.Rule = ({
otherPoints,
targetPoint,
@@ -224,19 +224,19 @@ export const paddingRule: Snap.Rule = ({
},
};
// 如果没有其他对象,则直接返回结果
// If there are no other objects, the result is returned directly
if (!otherPoints || otherPoints.length === 0) {
return rs;
}
// 横向 padding 判断
// Horizontal padding judgment
const latestXObj = findLatestObject({
otherPoints,
targetPoint,
direction: 'x',
});
// 遍历得到横向的右边对象
// Traverse to get the horizontal right object
let next = [];
let i = latestXObj.durationObjects.findIndex(
d => d === latestXObj.next.point,
@@ -256,7 +256,7 @@ export const paddingRule: Snap.Rule = ({
}
}
// 遍历得到横向的左边对象
// Traverse to get the horizontal left object
let prev = [];
i = latestXObj.durationObjects.findIndex(d => d === latestXObj.prev.point);
if (i !== -1) {
@@ -409,7 +409,7 @@ export const paddingRule: Snap.Rule = ({
let latestDistance = 999;
let latestItem = next[0];
// 从左侧所有 padding 中,找到最接近的吸附距离
// Find the closest adsorption distance from all the padding on the left
next.forEach(n => {
const distance = n.distance - latestXObj.next.distance;
if (Math.abs(distance) < Math.abs(latestDistance)) {
@@ -418,20 +418,20 @@ export const paddingRule: Snap.Rule = ({
}
});
// 如果距离小于阈值,则进行吸附
// If the distance is less than the threshold, adsorption is performed
if (Math.abs(latestDistance) <= threshold) {
// 如果找到的 所有 padding 距离 = 最接近的距离,则将这些 padding 添加到 mins
// If found, all padding distances = closest distance, add those padding to mins
next.forEach(n => {
if (numberEqual(n.distance, latestItem.distance)) {
mins.push(n);
}
});
// 如果 mins 不为空,则进行吸附
// If mins is not empty, perform adsorption
if (mins.length > 0) {
// 计算吸附距离
// Calculate the adsorption distance
let staff = latestXObj.next.distance - mins[0].distance;
// 是拖拽右侧 resize 控制点
// Yes drag right resize control point
if (
[
Snap.ControlType.TopRight,
@@ -442,7 +442,7 @@ export const paddingRule: Snap.Rule = ({
staff = -(latestXObj.next.distance - mins[0].distance);
}
// 计算目标对象的中间点
// Calculate the midpoint of the target object
const nextMiddle = getMiddle(
{
min: getTopPoint(latestXObj.next.point).y,
@@ -454,10 +454,10 @@ export const paddingRule: Snap.Rule = ({
},
);
// 计算辅助线 x 坐标
// Calculate auxiliary line x coordinates
let nextX = staff + targetPoint.tr.x;
// 如果是拖拽右侧 resize 控制点,计算方式不同
// If you drag and drop the right to resize the control point, the calculation method is different.
if (
[
Snap.ControlType.TopRight,
@@ -941,7 +941,7 @@ export const paddingRule: Snap.Rule = ({
}
}
// 根据控制点,处理属性变化值
// According to the control point, process the property change value
if (
[
Snap.ControlType.TopLeft,

View File

@@ -27,20 +27,20 @@ type Attribute =
interface Config {
/**
* 影响到的属性
* Affected properties
*/
key: Attribute;
/**
* 吸附方向
* adsorption direction
*/
direction: 'x' | 'y';
/**
* 吸附到的值
* Adsorbed value
*/
snapValue: number[];
}
// 计算目标元素的未来位置
// Calculate the future position of the target element
const getNextHelplinePoint = ({
targetPoint,
latestDistance,

View File

@@ -47,10 +47,10 @@ class SnapService {
testPoints: Snap.Point[] = [];
snapOpen = true;
// 开发模式,开启后,按下 shift 会显示激活元素的 5 个点位
// Development mode, when turned on, pressing shift will display 5 points of the active element
devMode = false;
onKeyDown = (event: KeyboardEvent) => {
// 按下 cmd 键,关闭吸附(与截图冲突,暂时隐藏)
// Press the cmd key to turn off the adsorption (conflicts with screenshots, temporarily hidden)
if (event.key.toLowerCase() === 'meta') {
// this.snapOpen = false;
} else if (event.key.toLowerCase() === 'shift' && this.devMode) {
@@ -64,7 +64,7 @@ class SnapService {
};
onKeyUp = (event: KeyboardEvent) => {
// 松手 cmd 键,打开吸附(与截图冲突,暂时隐藏)
// Let go of the cmd button and turn on the adsorption (conflicts with screenshots, temporarily hidden)
if (event.key.toLowerCase() === 'meta') {
// this.snapOpen = true;
// this.helpline.hide();
@@ -152,14 +152,14 @@ class SnapService {
return newAttrs;
};
// move resize 影响到的属性不同所以分开。move 仅影响 left top
// Move and resize affect different properties, so they are separated. move only affects left top
move = (target: FabricObject) =>
this._move({
target,
controlType: Snap.ControlType.Center,
});
// resize 根据控制点的不同,可能影响到多个属性
// Resize may affect multiple properties depending on the control point
resize = (target: FabricObject, controlType: Snap.ControlType) => {
if (target.angle !== 0) {
return;

View File

@@ -103,7 +103,7 @@ export const fixedMiddlePoint = (
};
};
// 寻找距离指定点,最近元素及点位
// Find the specified point, the nearest element, and the point
export const findLatestObject = (
otherPoints: Snap.ObjectPointsWithMiddle[],
targets: number[],
@@ -146,10 +146,10 @@ export const getLatestSnapRs = (
const sortedSnapRs = snapRsFilterEmpty.sort(
(a, b) => a.snapDistance - b.snapDistance,
);
// 找到最近的距离
// Find the nearest distance
const latestSnapRs = sortedSnapRs[0];
// 找到最近的距离的 helplines,最近的距离可能有多个,要把 helplines 合并
// Find the helplines with the closest distance, there may be multiple closest distances, and merge the helplines.
const helplinesRs = snapRsFilterEmpty
.filter(rs => numberEqual(rs.snapDistance, latestSnapRs.snapDistance))
.map(rs => rs.helplines)

View File

@@ -5,7 +5,7 @@ module.exports = {
important: '',
content: ['./src/**/*.{ts,tsx}'],
corePlugins: {
preflight: false, // 关闭@tailwind base默认样式,避免对现有样式影响
preflight: false, // Turn off @tailwind base default styles to avoid affecting existing styles
},
plugins: [require('@coze-arch/tailwind-config/coze')],
};

View File

@@ -25,7 +25,7 @@ export default {
'./node_modules/@coze-arch/coze-design/dist/**/*.{js,jsx,css}',
],
corePlugins: {
preflight: false, // 关闭@tailwind base默认样式,避免对现有样式影响
preflight: false, // Turn off @tailwind base default styles to avoid affecting existing styles
},
// eslint-disable-next-line @typescript-eslint/no-require-imports
plugins: [require('@coze-arch/tailwind-config/coze')],