grid editor

This commit is contained in:
hamid zarghami
2025-11-29 16:11:24 +03:30
parent 2f60ef806e
commit 2c28218488
8 changed files with 720 additions and 28 deletions
+70
View File
@@ -0,0 +1,70 @@
import { useEffect, useRef } from "react";
type CellEditorProps = {
x: number;
y: number;
width: number;
height: number;
value: string;
onSave: (text: string) => void;
onCancel: () => void;
};
const CellEditor = ({ x, y, width, height, value, onSave, onCancel }: CellEditorProps) => {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, []);
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault();
onSave(inputRef.current?.value || "");
} else if (e.key === "Escape") {
e.preventDefault();
onCancel();
}
};
const handleBlur = () => {
onSave(inputRef.current?.value || "");
};
return (
<div
style={{
position: "fixed",
left: `${x}px`,
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
zIndex: 1000,
pointerEvents: "auto",
}}
>
<input
ref={inputRef}
type="text"
defaultValue={value}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
style={{
width: "100%",
height: "100%",
padding: "4px 8px",
border: "2px solid #3b82f6",
borderRadius: "4px",
fontSize: "14px",
fontFamily: "Arial",
textAlign: "center",
outline: "none",
}}
/>
</div>
);
};
export default CellEditor;
+131
View File
@@ -0,0 +1,131 @@
import React from "react";
import { Group, Rect, Text } from "react-konva";
import Konva from "konva";
import type { EditorObject } from "@/pages/editor/store/editorStore";
type TableProps = {
obj: EditorObject;
selectedCellId: string | null;
onCellClick: (tableId: string, cellId: string, node: Konva.Node) => void;
onCellDblClick: (tableId: string, cellId: string, x: number, y: number, text: string) => void;
onDragEnd: (tableId: string, x: number, y: number) => void;
draggable: boolean;
};
const Table = ({
obj,
selectedCellId,
onCellClick,
onCellDblClick,
onDragEnd,
draggable,
}: TableProps) => {
const groupRef = React.useRef<Konva.Group>(null);
const tableData = obj.tableData;
React.useEffect(() => {
if (groupRef.current) {
groupRef.current.setAttr("id", obj.id);
}
}, [obj.id]);
if (!tableData) return null;
const { rows, cols, cells, cellWidth, cellHeight, stroke, strokeWidth } = tableData;
const handleCellClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
e.cancelBubble = true;
if (groupRef.current) {
onCellClick(obj.id, cellId, groupRef.current);
}
};
const handleCellDblClick = (cellId: string, e: Konva.KonvaEventObject<MouseEvent>) => {
e.cancelBubble = true;
const stage = e.target.getStage();
if (!stage || !groupRef.current) return;
const cell = cells[cellId];
if (!cell) return;
const row = parseInt(cellId.split("-")[1]);
const col = parseInt(cellId.split("-")[2]);
const cellX = col * cellWidth;
const cellY = row * cellHeight;
const groupPos = groupRef.current.getAbsolutePosition();
const stageBox = stage.container().getBoundingClientRect();
const x = stageBox.left + groupPos.x + cellX;
const y = stageBox.top + groupPos.y + cellY;
onCellDblClick(obj.id, cellId, x, y, cell.text);
};
const handleDragEnd = () => {
if (groupRef.current) {
const pos = groupRef.current.position();
onDragEnd(obj.id, pos.x, pos.y);
}
};
return (
<Group
ref={groupRef}
x={obj.x}
y={obj.y}
draggable={draggable}
onDragEnd={handleDragEnd}
>
{Array.from({ length: rows }).map((_, row) =>
Array.from({ length: cols }).map((_, col) => {
const cellId = `cell-${row}-${col}`;
const cell = cells[cellId];
if (!cell) return null;
const x = col * cellWidth;
const y = row * cellHeight;
const isCellSelected = selectedCellId === cellId;
const isTopLeft = row === 0 && col === 0;
const isTopRight = row === 0 && col === cols - 1;
return (
<Group key={cellId}>
<Rect
x={x}
y={y}
width={cellWidth}
height={cellHeight}
fill={cell.background}
stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"}
strokeWidth={isCellSelected ? 3 : strokeWidth || 1}
cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0}
onClick={(e) => handleCellClick(cellId, e)}
onDblClick={(e) => handleCellDblClick(cellId, e)}
/>
{cell.text && (
<Text
x={x}
y={y}
width={cellWidth}
height={cellHeight}
text={cell.text}
fontSize={14}
fontFamily="Arial"
fill="#000000"
align="center"
verticalAlign="middle"
onClick={(e) => handleCellClick(cellId, e)}
onDblClick={(e) => handleCellDblClick(cellId, e)}
/>
)}
</Group>
);
})
)}
</Group>
);
};
export default Table;
@@ -6,6 +6,7 @@ import { clx } from "@/helpers/utils";
import { useDrawingHandlers } from "./canvas/useDrawingHandlers"; import { useDrawingHandlers } from "./canvas/useDrawingHandlers";
import { useStageSize } from "./canvas/useStageSize"; import { useStageSize } from "./canvas/useStageSize";
import ObjectRenderer from "./canvas/ObjectRenderer"; import ObjectRenderer from "./canvas/ObjectRenderer";
import CellEditor from "@/components/CellEditor";
const EditorCanvas = () => { const EditorCanvas = () => {
const stageRef = useRef<Konva.Stage>(null); const stageRef = useRef<Konva.Stage>(null);
@@ -17,6 +18,13 @@ const EditorCanvas = () => {
updateObject, updateObject,
selectedObjectId, selectedObjectId,
setSelectedObjectId, setSelectedObjectId,
setSelectedTableId,
selectedCellId,
setSelectedCellId,
editingCell,
setEditingCell,
updateCellValue,
applyTableResize,
setStageRef, setStageRef,
setLayerRef, setLayerRef,
} = useEditorStore(); } = useEditorStore();
@@ -34,13 +42,83 @@ const EditorCanvas = () => {
}, [setStageRef, setLayerRef]); }, [setStageRef, setLayerRef]);
const handleSelect = (id: string, node: Konva.Node) => { const handleSelect = (id: string, node: Konva.Node) => {
const obj = objects.find((o) => o.id === id);
setSelectedObjectId(id); setSelectedObjectId(id);
if (obj?.type === "grid") {
setSelectedTableId(id);
} else {
setSelectedTableId(null);
}
setSelectedCellId(null);
if (transformerRef.current) { if (transformerRef.current) {
transformerRef.current.nodes([node]); transformerRef.current.nodes([node]);
transformerRef.current.getLayer()?.batchDraw(); transformerRef.current.getLayer()?.batchDraw();
} }
}; };
const handleCellClick = (tableId: string, cellId: string) => {
setSelectedTableId(tableId);
setSelectedObjectId(tableId);
setSelectedCellId(cellId);
const obj = objects.find((o) => o.id === tableId);
if (obj && transformerRef.current && layerRef.current) {
const tableNode = layerRef.current.findOne(`#${tableId}`);
if (tableNode) {
transformerRef.current.nodes([tableNode]);
transformerRef.current.getLayer()?.batchDraw();
}
}
};
const handleCellDblClick = (tableId: string, cellId: string, x: number, y: number, text: string) => {
setEditingCell({ tableId, cellId, x, y, value: text });
};
const handleCellEditorSave = (text: string) => {
if (editingCell) {
updateCellValue(editingCell.tableId, editingCell.cellId, text);
setEditingCell(null);
}
};
const handleCellEditorCancel = () => {
setEditingCell(null);
};
useEffect(() => {
if (!transformerRef.current || !selectedObjectId) return;
const selectedObject = objects.find((obj) => obj.id === selectedObjectId);
if (selectedObject?.type !== "grid") return;
const transformer = transformerRef.current;
const handleTransformEnd = () => {
const node = transformer.nodes()[0];
if (!node || !selectedObject) return;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
const newWidth = (selectedObject.width || 300) * scaleX;
const newHeight = (selectedObject.height || 200) * scaleY;
applyTableResize(selectedObject.id, newWidth, newHeight);
updateObject(selectedObject.id, {
x: node.x(),
y: node.y(),
});
};
transformer.on("transformend", handleTransformEnd);
return () => {
transformer.off("transformend", handleTransformEnd);
};
}, [selectedObjectId, objects, transformerRef, applyTableResize, updateObject]);
return ( return (
<div className="flex-1 flex items-center justify-center bg-gray-100 overflow-hidden"> <div className="flex-1 flex items-center justify-center bg-gray-100 overflow-hidden">
<Stage <Stage
@@ -63,6 +141,9 @@ const EditorCanvas = () => {
onSelect={handleSelect} onSelect={handleSelect}
onUpdate={updateObject} onUpdate={updateObject}
draggable={tool === "select"} draggable={tool === "select"}
onCellClick={handleCellClick}
onCellDblClick={handleCellDblClick}
selectedCellId={selectedCellId}
/> />
))} ))}
{selectedObjectId && (() => { {selectedObjectId && (() => {
@@ -101,6 +182,23 @@ const EditorCanvas = () => {
})()} })()}
</Layer> </Layer>
</Stage> </Stage>
{editingCell && (() => {
const obj = objects.find((o) => o.id === editingCell.tableId);
if (!obj || !obj.tableData) return null;
const cell = obj.tableData.cells[editingCell.cellId];
if (!cell) return null;
return (
<CellEditor
x={editingCell.x}
y={editingCell.y}
width={cell.width}
height={cell.height}
value={editingCell.value}
onSave={handleCellEditorSave}
onCancel={handleCellEditorCancel}
/>
);
})()}
</div> </div>
); );
}; };
@@ -12,6 +12,7 @@ import {
LinkShape, LinkShape,
VideoShape, VideoShape,
} from "../tools"; } from "../tools";
import Table from "@/components/Table";
type ObjectRendererProps = { type ObjectRendererProps = {
obj: EditorObject; obj: EditorObject;
@@ -21,6 +22,9 @@ type ObjectRendererProps = {
onSelect: (id: string, node: Konva.Node) => void; onSelect: (id: string, node: Konva.Node) => void;
onUpdate: (id: string, updates: Partial<EditorObject>) => void; onUpdate: (id: string, updates: Partial<EditorObject>) => void;
draggable: boolean; draggable: boolean;
onCellClick?: (tableId: string, cellId: string) => void;
onCellDblClick?: (tableId: string, cellId: string, x: number, y: number, text: string) => void;
selectedCellId?: string | null;
}; };
const ObjectRenderer = ({ const ObjectRenderer = ({
@@ -31,6 +35,9 @@ const ObjectRenderer = ({
onSelect, onSelect,
onUpdate, onUpdate,
draggable, draggable,
onCellClick,
onCellDblClick,
selectedCellId,
}: ObjectRendererProps) => { }: ObjectRendererProps) => {
const commonProps = { const commonProps = {
obj, obj,
@@ -67,6 +74,25 @@ const ObjectRenderer = ({
return <VideoShape key={obj.id} {...commonProps} />; return <VideoShape key={obj.id} {...commonProps} />;
case "document": case "document":
return <ImageObject key={obj.id} {...commonProps} />; return <ImageObject key={obj.id} {...commonProps} />;
case "grid":
return (
<Table
key={obj.id}
obj={obj}
selectedCellId={selectedCellId || null}
onCellClick={(tableId, cellId, node) => {
onSelect(tableId, node);
onCellClick?.(tableId, cellId);
}}
onCellDblClick={(tableId, cellId, x, y, text) => {
onCellDblClick?.(tableId, cellId, x, y, text);
}}
onDragEnd={(tableId, x, y) => {
onUpdate(tableId, { x, y });
}}
draggable={draggable}
/>
);
default: default:
return null; return null;
} }
@@ -10,7 +10,7 @@ export const useDrawingHandlers = () => {
const [tempObject, setTempObject] = useState<EditorObject | null>(null); const [tempObject, setTempObject] = useState<EditorObject | null>(null);
const transformerRef = useRef<Konva.Transformer>(null); const transformerRef = useRef<Konva.Transformer>(null);
const { tool, addObject, updateObject, setSelectedObjectId, selectedObjectId, setTool } = useEditorStore(); const { tool, addObject, updateObject, setSelectedObjectId, selectedObjectId, setTool, addTable, setSelectedTableId } = useEditorStore();
const { defaults } = useTextDefaultsStore(); const { defaults } = useTextDefaultsStore();
useEffect(() => { useEffect(() => {
@@ -73,6 +73,12 @@ export const useDrawingHandlers = () => {
setSelectedObjectId(newText.id); setSelectedObjectId(newText.id);
setTool("select"); setTool("select");
} }
if (tool === "grid") {
addTable(pointerPos.x, pointerPos.y, 4, 5);
setSelectedTableId(null);
setTool("select");
}
}; };
const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => { const handleStageMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
@@ -1,6 +1,9 @@
const GridInstruction = () => ( const GridInstruction = () => (
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600 space-y-2">
<p>برای افزودن گرید، روی کانوس کلیک کنید</p> <p>برای افزودن جدول، روی کانوس کلیک کنید</p>
<p className="text-xs text-gray-500">
جدول به صورت پیشفرض با 3 ردیف و 3 ستون ایجاد میشود
</p>
</div> </div>
); );
@@ -1,35 +1,127 @@
import { Add, Minus } from "iconsax-react";
import type { EditorObject } from "../../../store/editorStore"; import type { EditorObject } from "../../../store/editorStore";
import Input from "@/components/Input"; import { useEditorStore } from "../../../store/editorStore";
import ColorPicker from "@/components/ColorPicker";
type GridSettingsProps = { type GridSettingsProps = {
selectedObject: EditorObject; selectedObject: EditorObject;
onUpdate: (id: string, updates: Partial<EditorObject>) => void; onUpdate: (id: string, updates: Partial<EditorObject>) => void;
}; };
const GridSettings = ({ selectedObject, onUpdate }: GridSettingsProps) => { const GridSettings = ({ selectedObject }: GridSettingsProps) => {
const {
addRow,
addColumn,
removeRow,
removeColumn,
changeCellBackground,
selectedCellId,
} = useEditorStore();
const tableDataObj = selectedObject.tableData;
const handleAddRow = () => {
if (tableDataObj) {
addRow(selectedObject.id);
}
};
const handleAddColumn = () => {
if (tableDataObj) {
addColumn(selectedObject.id);
}
};
const handleRemoveRow = () => {
if (tableDataObj) {
removeRow(selectedObject.id);
}
};
const handleRemoveColumn = () => {
if (tableDataObj) {
removeColumn(selectedObject.id);
}
};
const handleCellBackgroundChange = (color: string) => {
if (selectedCellId && tableDataObj) {
changeCellBackground(selectedObject.id, selectedCellId, color);
}
};
const selectedCell = selectedCellId && tableDataObj ? tableDataObj.cells[selectedCellId] : null;
return ( return (
<> <div className="space-y-4">
<Input <div className="space-y-2">
label="عرض" <div className="flex items-center justify-between">
type="number" <span className="text-sm font-medium">ردیفها</span>
value={selectedObject.width || 200} <div className="flex gap-2">
onChange={(e) => <button
onUpdate(selectedObject.id, { onClick={handleAddRow}
width: parseInt(e.target.value) || 200, className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
}) title="افزودن ردیف"
} >
/> <Add size={20} color="black" />
<Input </button>
label="ارتفاع" <button
type="number" onClick={handleRemoveRow}
value={selectedObject.height || 200} disabled={!tableDataObj || tableDataObj.rows <= 1}
onChange={(e) => className="p-2 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
onUpdate(selectedObject.id, { title="حذف ردیف"
height: parseInt(e.target.value) || 200, >
}) <Minus size={20} color="black" />
} </button>
/> </div>
</> </div>
<div className="text-xs text-gray-500">
تعداد ردیفها: {tableDataObj?.rows || 0}
</div>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">ستونها</span>
<div className="flex gap-2">
<button
onClick={handleAddColumn}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
title="افزودن ستون"
>
<Add size={20} color="black" />
</button>
<button
onClick={handleRemoveColumn}
disabled={!tableDataObj || tableDataObj.cols <= 1}
className="p-2 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="حذف ستون"
>
<Minus size={20} color="black" />
</button>
</div>
</div>
<div className="text-xs text-gray-500">
تعداد ستونها: {tableDataObj?.cols || 0}
</div>
</div>
{selectedCell && (
<div className="space-y-2 pt-4 border-t border-border">
<ColorPicker
label="رنگ پس‌زمینه سلول"
value={selectedCell.background}
onChange={handleCellBackgroundChange}
/>
</div>
)}
{!selectedCell && (
<div className="text-xs text-gray-500 pt-4 border-t border-border">
برای تغییر رنگ سلول، ابتدا روی یک سلول کلیک کنید
</div>
)}
</div>
); );
}; };
+267 -1
View File
@@ -15,6 +15,25 @@ export type ToolType =
| "link" | "link"
| "grid"; | "grid";
export type TableCell = {
id: string;
text: string;
background: string;
width: number;
height: number;
};
export type TableData = {
id: string;
rows: number;
cols: number;
cellWidth: number;
cellHeight: number;
cells: Record<string, TableCell>;
stroke?: string;
strokeWidth?: number;
};
export type EditorObject = { export type EditorObject = {
id: string; id: string;
type: ToolType; type: ToolType;
@@ -39,6 +58,7 @@ export type EditorObject = {
scaleX?: number; scaleX?: number;
scaleY?: number; scaleY?: number;
shapeType?: ShapeType; shapeType?: ShapeType;
tableData?: TableData;
}; };
type EditorStoreType = { type EditorStoreType = {
@@ -50,13 +70,46 @@ type EditorStoreType = {
deleteObject: (id: string) => void; deleteObject: (id: string) => void;
selectedObjectId: string | null; selectedObjectId: string | null;
setSelectedObjectId: (id: string | null) => void; setSelectedObjectId: (id: string | null) => void;
selectedTableId: string | null;
setSelectedTableId: (id: string | null) => void;
selectedCellId: string | null;
setSelectedCellId: (id: string | null) => void;
editingCell: { tableId: string; cellId: string; x: number; y: number; value: string } | null;
setEditingCell: (cell: { tableId: string; cellId: string; x: number; y: number; value: string } | null) => void;
addTable: (x: number, y: number, rows?: number, cols?: number) => void;
addRow: (tableId: string) => void;
addColumn: (tableId: string) => void;
removeRow: (tableId: string) => void;
removeColumn: (tableId: string) => void;
updateCellValue: (tableId: string, cellId: string, text: string) => void;
changeCellBackground: (tableId: string, cellId: string, color: string) => void;
applyTableResize: (tableId: string, newWidth: number, newHeight: number) => void;
stageRef: React.RefObject<Konva.Stage | null> | null; stageRef: React.RefObject<Konva.Stage | null> | null;
setStageRef: (ref: React.RefObject<Konva.Stage | null>) => void; setStageRef: (ref: React.RefObject<Konva.Stage | null>) => void;
layerRef: React.RefObject<Konva.Layer | null> | null; layerRef: React.RefObject<Konva.Layer | null> | null;
setLayerRef: (ref: React.RefObject<Konva.Layer | null>) => void; setLayerRef: (ref: React.RefObject<Konva.Layer | null>) => void;
}; };
export const useEditorStore = create<EditorStoreType>((set) => ({ const createTableCellId = (row: number, col: number) => `cell-${row}-${col}`;
const createInitialCells = (rows: number, cols: number, cellWidth: number, cellHeight: number): Record<string, TableCell> => {
const cells: Record<string, TableCell> = {};
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const cellId = createTableCellId(row, col);
cells[cellId] = {
id: cellId,
text: "",
background: "#f5f5f5",
width: cellWidth,
height: cellHeight,
};
}
}
return cells;
};
export const useEditorStore = create<EditorStoreType>((set, get) => ({
tool: "select", tool: "select",
setTool: (tool) => setTool: (tool) =>
set((state) => ({ set((state) => ({
@@ -77,9 +130,222 @@ export const useEditorStore = create<EditorStoreType>((set) => ({
objects: state.objects.filter((obj) => obj.id !== id), objects: state.objects.filter((obj) => obj.id !== id),
selectedObjectId: selectedObjectId:
state.selectedObjectId === id ? null : state.selectedObjectId, state.selectedObjectId === id ? null : state.selectedObjectId,
selectedTableId: state.selectedTableId === id ? null : state.selectedTableId,
})), })),
selectedObjectId: null, selectedObjectId: null,
setSelectedObjectId: (id) => set({ selectedObjectId: id }), setSelectedObjectId: (id) => set({ selectedObjectId: id }),
selectedTableId: null,
setSelectedTableId: (id) => set({ selectedTableId: id }),
selectedCellId: null,
setSelectedCellId: (id) => set({ selectedCellId: id }),
editingCell: null,
setEditingCell: (cell) => set({ editingCell: cell }),
addTable: (x, y, rows = 4, cols = 5) => {
const tableId = `table-${crypto.randomUUID()}`;
const cellWidth = 100;
const cellHeight = 50;
const tableData: TableData = {
id: tableId,
rows,
cols,
cellWidth,
cellHeight,
cells: createInitialCells(rows, cols, cellWidth, cellHeight),
stroke: "#808080",
strokeWidth: 1,
};
const tableObject: EditorObject = {
id: tableId,
type: "grid",
x,
y,
width: cols * cellWidth,
height: rows * cellHeight,
tableData,
};
get().addObject(tableObject);
get().setSelectedObjectId(tableId);
get().setSelectedTableId(tableId);
},
addRow: (tableId) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
const newRow = tableData.rows;
const newCells = { ...tableData.cells };
for (let col = 0; col < tableData.cols; col++) {
const cellId = createTableCellId(newRow, col);
newCells[cellId] = {
id: cellId,
text: "",
background: "#f5f5f5",
width: tableData.cellWidth,
height: tableData.cellHeight,
};
}
state.updateObject(tableId, {
tableData: {
...tableData,
rows: tableData.rows + 1,
cells: newCells,
},
height: (tableData.rows + 1) * tableData.cellHeight,
});
},
addColumn: (tableId) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
const newCol = tableData.cols;
const newCells = { ...tableData.cells };
for (let row = 0; row < tableData.rows; row++) {
const cellId = createTableCellId(row, newCol);
newCells[cellId] = {
id: cellId,
text: "",
background: "#f5f5f5",
width: tableData.cellWidth,
height: tableData.cellHeight,
};
}
state.updateObject(tableId, {
tableData: {
...tableData,
cols: tableData.cols + 1,
cells: newCells,
},
width: (tableData.cols + 1) * tableData.cellWidth,
});
},
removeRow: (tableId) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
if (tableData.rows <= 1) return;
const newCells = { ...tableData.cells };
for (let col = 0; col < tableData.cols; col++) {
const cellId = createTableCellId(tableData.rows - 1, col);
delete newCells[cellId];
}
state.updateObject(tableId, {
tableData: {
...tableData,
rows: tableData.rows - 1,
cells: newCells,
},
height: (tableData.rows - 1) * tableData.cellHeight,
});
},
removeColumn: (tableId) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
if (tableData.cols <= 1) return;
const newCells = { ...tableData.cells };
for (let row = 0; row < tableData.rows; row++) {
const cellId = createTableCellId(row, tableData.cols - 1);
delete newCells[cellId];
}
state.updateObject(tableId, {
tableData: {
...tableData,
cols: tableData.cols - 1,
cells: newCells,
},
width: (tableData.cols - 1) * tableData.cellWidth,
});
},
updateCellValue: (tableId, cellId, text) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
const updatedCells = {
...tableData.cells,
[cellId]: {
...tableData.cells[cellId],
text,
},
};
state.updateObject(tableId, {
tableData: {
...tableData,
cells: updatedCells,
},
});
},
changeCellBackground: (tableId, cellId, color) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
const updatedCells = {
...tableData.cells,
[cellId]: {
...tableData.cells[cellId],
background: color,
},
};
state.updateObject(tableId, {
tableData: {
...tableData,
cells: updatedCells,
},
});
},
applyTableResize: (tableId, newWidth, newHeight) => {
const state = get();
const obj = state.objects.find((o) => o.id === tableId);
if (!obj || !obj.tableData) return;
const { tableData } = obj;
const newCellWidth = newWidth / tableData.cols;
const newCellHeight = newHeight / tableData.rows;
const updatedCells: Record<string, TableCell> = {};
for (let row = 0; row < tableData.rows; row++) {
for (let col = 0; col < tableData.cols; col++) {
const cellId = createTableCellId(row, col);
const oldCell = tableData.cells[cellId];
updatedCells[cellId] = {
...oldCell,
width: newCellWidth,
height: newCellHeight,
};
}
}
state.updateObject(tableId, {
width: newWidth,
height: newHeight,
tableData: {
...tableData,
cellWidth: newCellWidth,
cellHeight: newCellHeight,
cells: updatedCells,
},
});
},
stageRef: null, stageRef: null,
setStageRef: (ref) => set({ stageRef: ref }), setStageRef: (ref) => set({ stageRef: ref }),
layerRef: null, layerRef: null,