feat(refboard): incremental sync — stop full-scene broadcast on edits
Replace broadcastSceneNow() with incremental element:update for all property-modifying operations (align, arrange, flip, resize, rotate, normalize, frame color). Full scene sync now only fires via debounced fallback (500ms) for structural changes (add/remove/paste/group). Changes: - onCanvasChange() accepts optional changedIds for incremental sync - SelectionToolbar, context menu, shortcuts all pass item IDs - TransformBox.onDragEnd passes item IDs instead of full scene - ShortcutContext, MenuContext, hook interfaces updated This dramatically reduces sync traffic during normal editing — only the changed elements are sent instead of the entire board state.
This commit is contained in:
@@ -58,7 +58,7 @@ export class TransformBox extends Container {
|
|||||||
private _drag: DragState | null = null;
|
private _drag: DragState | null = null;
|
||||||
private _viewport: Viewport | null = null;
|
private _viewport: Viewport | null = null;
|
||||||
private _onItemTransform: ((item: SceneItem) => void) | null = null;
|
private _onItemTransform: ((item: SceneItem) => void) | null = null;
|
||||||
private _onDragEnd: (() => void) | null = null;
|
private _onDragEnd: ((itemIds: string[]) => void) | null = null;
|
||||||
private _snapGuides: SnapGuides | null = null;
|
private _snapGuides: SnapGuides | null = null;
|
||||||
private _dimLabel!: Text;
|
private _dimLabel!: Text;
|
||||||
private _dimLabelBg!: Graphics;
|
private _dimLabelBg!: Graphics;
|
||||||
@@ -68,7 +68,7 @@ export class TransformBox extends Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Called when resize/rotate drag ends — use to persist/sync final state. */
|
/** Called when resize/rotate drag ends — use to persist/sync final state. */
|
||||||
set onDragEnd(fn: () => void) {
|
set onDragEnd(fn: (itemIds: string[]) => void) {
|
||||||
this._onDragEnd = fn;
|
this._onDragEnd = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ export class TransformBox extends Container {
|
|||||||
this._dimLabelBg.visible = false;
|
this._dimLabelBg.visible = false;
|
||||||
this._snapGuides?.endSession();
|
this._snapGuides?.endSession();
|
||||||
// Notify that drag ended — persist/sync the final state
|
// Notify that drag ended — persist/sync the final state
|
||||||
this._onDragEnd?.();
|
this._onDragEnd?.(this._items.map(i => i.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ interface MenuContext {
|
|||||||
viewport: Viewport | null;
|
viewport: Viewport | null;
|
||||||
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
||||||
writeCanvasToClipboard: (items?: SceneItem[]) => Promise<void>;
|
writeCanvasToClipboard: (items?: SceneItem[]) => Promise<void>;
|
||||||
onChange: () => void;
|
onChange: (changedIds?: string[]) => void;
|
||||||
refreshLayers: () => void;
|
refreshLayers: () => void;
|
||||||
handleGroup: () => void;
|
handleGroup: () => void;
|
||||||
handleUngroup: () => void;
|
handleUngroup: () => void;
|
||||||
@@ -37,6 +37,7 @@ export function buildContextMenuItems(ctx: MenuContext): MenuItem[] {
|
|||||||
const selected = selection ? selection.getSelectedItems() : [];
|
const selected = selection ? selection.getSelectedItems() : [];
|
||||||
const hasSel = selected.length > 0;
|
const hasSel = selected.length > 0;
|
||||||
const multiSel = selected.length >= 2;
|
const multiSel = selected.length >= 2;
|
||||||
|
const ids = selected.map((s) => s.id);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// -- Clipboard --
|
// -- Clipboard --
|
||||||
@@ -78,12 +79,12 @@ export function buildContextMenuItems(ctx: MenuContext): MenuItem[] {
|
|||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- Alignment --
|
// -- Alignment --
|
||||||
{ label: 'Align Left', shortcut: 'Ctrl+\u2190', onClick: () => { ops.alignLeft(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Align Left', shortcut: 'Ctrl+\u2190', onClick: () => { ops.alignLeft(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Align Right', shortcut: 'Ctrl+\u2192', onClick: () => { ops.alignRight(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Align Right', shortcut: 'Ctrl+\u2192', onClick: () => { ops.alignRight(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Align Top', shortcut: 'Ctrl+\u2191', onClick: () => { ops.alignTop(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Align Top', shortcut: 'Ctrl+\u2191', onClick: () => { ops.alignTop(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Align Bottom', shortcut: 'Ctrl+\u2193', onClick: () => { ops.alignBottom(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Align Bottom', shortcut: 'Ctrl+\u2193', onClick: () => { ops.alignBottom(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Distribute H', shortcut: '', onClick: () => { ops.distributeHorizontal(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Distribute H', shortcut: '', onClick: () => { ops.distributeHorizontal(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Distribute V', shortcut: '', onClick: () => { ops.distributeVertical(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Distribute V', shortcut: '', onClick: () => { ops.distributeVertical(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- Layer ordering --
|
// -- Layer ordering --
|
||||||
@@ -134,23 +135,23 @@ export function buildContextMenuItems(ctx: MenuContext): MenuItem[] {
|
|||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- Arrangement --
|
// -- Arrangement --
|
||||||
{ label: 'Arrange Pack', shortcut: 'Ctrl+Shift+P', onClick: () => { ops.arrangeOptimal(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Arrange Pack', shortcut: 'Ctrl+Shift+P', onClick: () => { ops.arrangeOptimal(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Arrange Grid', shortcut: '', onClick: () => { ops.arrangeGrid(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Arrange Grid', shortcut: '', onClick: () => { ops.arrangeGrid(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Arrange Row', shortcut: '', onClick: () => { ops.arrangeRow(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Arrange Row', shortcut: '', onClick: () => { ops.arrangeRow(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Arrange Column', shortcut: '', onClick: () => { ops.arrangeColumn(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Arrange Column', shortcut: '', onClick: () => { ops.arrangeColumn(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Stack', shortcut: 'Ctrl+Alt+S', onClick: () => { ops.stackObjects(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Stack', shortcut: 'Ctrl+Alt+S', onClick: () => { ops.stackObjects(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- Normalize --
|
// -- Normalize --
|
||||||
{ label: 'Normalize Size', shortcut: '', onClick: () => { ops.normalizeSize(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Normalize Size', shortcut: '', onClick: () => { ops.normalizeSize(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Normalize Width', shortcut: '', onClick: () => { ops.normalizeWidth(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Normalize Width', shortcut: '', onClick: () => { ops.normalizeWidth(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: 'Normalize Height', shortcut: '', onClick: () => { ops.normalizeHeight(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !multiSel },
|
{ label: 'Normalize Height', shortcut: '', onClick: () => { ops.normalizeHeight(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !multiSel },
|
||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- Image --
|
// -- Image --
|
||||||
{ label: 'Flip Horizontal', shortcut: 'Alt+Shift+H', onClick: () => { ops.flipHorizontal(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !hasSel },
|
{ label: 'Flip Horizontal', shortcut: 'Alt+Shift+H', onClick: () => { ops.flipHorizontal(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !hasSel },
|
||||||
{ label: 'Flip Vertical', shortcut: 'Alt+Shift+V', onClick: () => { ops.flipVertical(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !hasSel },
|
{ label: 'Flip Vertical', shortcut: 'Alt+Shift+V', onClick: () => { ops.flipVertical(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !hasSel },
|
||||||
{ label: 'Reset Transform', shortcut: 'Ctrl+Shift+T', onClick: () => { ops.resetTransform(selected); selection?.transformBox.update(selected); ctx.onChange(); }, disabled: !hasSel },
|
{ label: 'Reset Transform', shortcut: 'Ctrl+Shift+T', onClick: () => { ops.resetTransform(selected); selection?.transformBox.update(selected); ctx.onChange(ids); }, disabled: !hasSel },
|
||||||
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
{ label: '', shortcut: '', onClick: () => {}, divider: true },
|
||||||
|
|
||||||
// -- View --
|
// -- View --
|
||||||
@@ -244,5 +245,5 @@ function _setFrameColor(item: SceneItem, color: string, ctx: MenuContext): void
|
|||||||
if (item.displayObject instanceof FrameSprite) {
|
if (item.displayObject instanceof FrameSprite) {
|
||||||
item.displayObject.setBgColor(color);
|
item.displayObject.setBgColor(color);
|
||||||
}
|
}
|
||||||
ctx.onChange();
|
ctx.onChange([item.id]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ async function _pasteInternal(ctx: ShortcutContext): Promise<void> {
|
|||||||
ctx.onChange();
|
ctx.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run op on selected items → update transform box → fire onChange. */
|
/** Run op on selected items → update transform box → fire onChange with IDs. */
|
||||||
function _opUpdate(ctx: ShortcutContext, op: (items: SceneItem[]) => void): void {
|
function _opUpdate(ctx: ShortcutContext, op: (items: SceneItem[]) => void): void {
|
||||||
const items = ctx.selection.getSelectedItems();
|
const items = ctx.selection.getSelectedItems();
|
||||||
op(items);
|
op(items);
|
||||||
ctx.selection.transformBox.update(items);
|
ctx.selection.transformBox.update(items);
|
||||||
ctx.onChange();
|
ctx.onChange(items.map(i => i.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shortcuts: ShortcutDef[] = [
|
export const shortcuts: ShortcutDef[] = [
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export interface ShortcutContext {
|
|||||||
selection: SelectionManager;
|
selection: SelectionManager;
|
||||||
history: UndoManager;
|
history: UndoManager;
|
||||||
viewport: Viewport;
|
viewport: Viewport;
|
||||||
onChange: () => void;
|
onChange: (changedIds?: string[]) => void;
|
||||||
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
||||||
showToast: (msg: string) => void;
|
showToast: (msg: string) => void;
|
||||||
fitAll: () => void;
|
fitAll: () => void;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ interface CanvasSetupDeps {
|
|||||||
undoRef: React.MutableRefObject<UndoManager | null>;
|
undoRef: React.MutableRefObject<UndoManager | null>;
|
||||||
syncRef: React.MutableRefObject<SyncHandle | null>;
|
syncRef: React.MutableRefObject<SyncHandle | null>;
|
||||||
inboxZoneRef: React.MutableRefObject<InboxZone | null>;
|
inboxZoneRef: React.MutableRefObject<InboxZone | null>;
|
||||||
onCanvasChange: () => void;
|
onCanvasChange: (changedIds?: string[]) => void;
|
||||||
showToast: (msg: string) => void;
|
showToast: (msg: string) => void;
|
||||||
setOnlineUsers: React.Dispatch<React.SetStateAction<OnlineUser[]>>;
|
setOnlineUsers: React.Dispatch<React.SetStateAction<OnlineUser[]>>;
|
||||||
setSelectedLayerIds: React.Dispatch<React.SetStateAction<string[]>>;
|
setSelectedLayerIds: React.Dispatch<React.SetStateAction<string[]>>;
|
||||||
@@ -136,9 +136,9 @@ export function useCanvasSetup(deps: CanvasSetupDeps) {
|
|||||||
selection.transformBox.onItemTransform = (item) => {
|
selection.transformBox.onItemTransform = (item) => {
|
||||||
syncRef.current?.broadcastTransform(item);
|
syncRef.current?.broadcastTransform(item);
|
||||||
};
|
};
|
||||||
selection.transformBox.onDragEnd = () => {
|
selection.transformBox.onDragEnd = (itemIds) => {
|
||||||
syncRef.current?.broadcastSceneNow();
|
syncRef.current?.broadcastElements(itemIds);
|
||||||
onCanvasChange();
|
onCanvasChange(itemIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.on('user:joined', (data: any) => {
|
socket.on('user:joined', (data: any) => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface LayerItem {
|
|||||||
interface LayerPanelDeps {
|
interface LayerPanelDeps {
|
||||||
canvasRef: React.RefObject<PixiCanvasHandle | null>;
|
canvasRef: React.RefObject<PixiCanvasHandle | null>;
|
||||||
selectionRef: React.RefObject<SelectionManager | null>;
|
selectionRef: React.RefObject<SelectionManager | null>;
|
||||||
onCanvasChange: () => void;
|
onCanvasChange: (changedIds?: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoName(item: SceneItem, i: number, counters: Record<string, number>): string {
|
function autoName(item: SceneItem, i: number, counters: Record<string, number>): string {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface ShortcutHandlerDeps {
|
|||||||
undoRef: React.RefObject<UndoManager | null>;
|
undoRef: React.RefObject<UndoManager | null>;
|
||||||
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
clipboardRef: React.MutableRefObject<SceneItem[]>;
|
||||||
resolvedBoardId: string | undefined;
|
resolvedBoardId: string | undefined;
|
||||||
onCanvasChange: () => void;
|
onCanvasChange: (changedIds?: string[]) => void;
|
||||||
showToast: (msg: string) => void;
|
showToast: (msg: string) => void;
|
||||||
refreshLayers: () => void;
|
refreshLayers: () => void;
|
||||||
handleGroup: () => void;
|
handleGroup: () => void;
|
||||||
|
|||||||
@@ -102,11 +102,16 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
// Save manager
|
// Save manager
|
||||||
const { scheduleSave } = useSaveManager({ resolvedBoardId, isPublicView, canvasRef, setSaveStatus });
|
const { scheduleSave } = useSaveManager({ resolvedBoardId, isPublicView, canvasRef, setSaveStatus });
|
||||||
|
|
||||||
// Canvas change handler
|
// Canvas change handler.
|
||||||
const onCanvasChange = useCallback(() => {
|
// Pass changedIds for incremental sync (fast, lightweight).
|
||||||
|
// Omit for structural changes — falls through to debounced full scene sync.
|
||||||
|
const onCanvasChange = useCallback((changedIds?: string[]) => {
|
||||||
scheduleSave();
|
scheduleSave();
|
||||||
// Broadcast changes to other connected clients immediately
|
if (changedIds && changedIds.length > 0) {
|
||||||
syncRef.current?.broadcastSceneNow();
|
// Incremental: broadcast only changed elements
|
||||||
|
syncRef.current?.broadcastElements(changedIds);
|
||||||
|
}
|
||||||
|
// Full scene sync happens via debounced SceneManager.onChange chain in sync.ts
|
||||||
if (undoRef.current && !undoRef.current.isLocked()) {
|
if (undoRef.current && !undoRef.current.isLocked()) {
|
||||||
undoRef.current.saveState();
|
undoRef.current.saveState();
|
||||||
setCanUndo(undoRef.current.canUndo());
|
setCanUndo(undoRef.current.canUndo());
|
||||||
@@ -528,23 +533,23 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
x={selToolbar.x}
|
x={selToolbar.x}
|
||||||
y={selToolbar.y}
|
y={selToolbar.y}
|
||||||
count={selToolbar.count}
|
count={selToolbar.count}
|
||||||
onAlignLeft={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignLeft(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignLeft={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignLeft(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onAlignCenterH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignCenterH(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignCenterH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignCenterH(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onAlignRight={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignRight(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignRight={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignRight(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onAlignTop={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignTop(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignTop={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignTop(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onAlignCenterV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignCenterV(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignCenterV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignCenterV(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onAlignBottom={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignBottom(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onAlignBottom={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.alignBottom(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onDistributeH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.distributeHorizontal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onDistributeH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.distributeHorizontal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onDistributeV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.distributeVertical(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onDistributeV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.distributeVertical(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onPack={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeOptimal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onPack={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeOptimal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onGrid={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeGrid(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onGrid={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeGrid(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onRow={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeRow(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onRow={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeRow(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onColumn={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeColumn(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onColumn={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.arrangeColumn(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onStack={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.stackObjects(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onStack={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.stackObjects(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onFlipH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.flipHorizontal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onFlipH={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.flipHorizontal(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onFlipV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.flipVertical(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onFlipV={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.flipVertical(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
onGroup={handleGroup}
|
onGroup={handleGroup}
|
||||||
onNormSize={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.normalizeSize(s); selectionRef.current?.transformBox.update(s); onCanvasChange(); } }}
|
onNormSize={() => { const s = selectionRef.current?.getSelectedItems(); if (s) { ops.normalizeSize(s); selectionRef.current?.transformBox.update(s); onCanvasChange(s.map(i => i.id)); } }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user