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;