Files
History-client/components/Editor.tsx
2026-04-13 21:47:28 +07:00

229 lines
7.4 KiB
TypeScript

"use client";
import { UndoAction } from "@/lib/useEditorState";
type Mode = "draw" | "select" | "idle" | "add-point" | "add-line" | "add-path" | "add-circle";
type Props = {
mode: Mode;
setMode: (mode: Mode) => void;
entityStatus?: string | null;
onUndo: () => void;
onSave: () => void;
isSaving: boolean;
changesCount: number;
undoStack: UndoAction[];
};
export default function Editor({
mode,
setMode,
entityStatus,
onUndo,
onSave,
isSaving,
changesCount,
undoStack,
}: Props) {
const toggleMode = (newMode: Mode) => {
if (mode === newMode) {
setMode("idle"); // bấm lại → tắt
} else {
setMode(newMode); // chuyển mode
}
};
// Lấy tối đa 8 tác vụ mới nhất, bỏ trùng nhãn (cùng loại/cùng id)
const recentUndoLabels = (() => {
const seen = new Set<string>();
const labels: string[] = [];
for (let i = undoStack.length - 1; i >= 0 && labels.length < 8; i -= 1) {
const label = formatUndoLabel(undoStack[i]);
if (seen.has(label)) continue;
seen.add(label);
labels.push(label);
}
return labels.reverse();
})();
const getButtonStyle = (btnMode: Mode) => ({
width: "100%",
padding: "8px",
marginBottom: "6px",
border: "none",
cursor: "pointer",
background: mode === btnMode ? "#4caf50" : "#222",
color: "white",
borderRadius: "4px",
});
return (
<div
style={{
width: "220px",
background: "#111",
color: "white",
padding: "12px",
borderRight: "1px solid #333",
}}
>
<h3 style={{ marginBottom: "10px" }}>Editor</h3>
<button
style={getButtonStyle("draw")}
onClick={() => toggleMode("draw")}
>
Draw
</button>
<button
style={getButtonStyle("select")}
onClick={() => toggleMode("select")}
>
Select
</button>
<button
style={getButtonStyle("idle")}
onClick={() => setMode("idle")}
>
Idle
</button>
<button
style={getButtonStyle("add-point")}
onClick={() => setMode("add-point")}
>
Add point
</button>
<button
style={getButtonStyle("add-line")}
onClick={() => setMode("add-line")}
>
Add line
</button>
<button
style={getButtonStyle("add-path")}
onClick={() => setMode("add-path")}
>
Add path
</button>
<button
style={getButtonStyle("add-circle")}
onClick={() => setMode("add-circle")}
>
Add circle
</button>
<div style={{ marginTop: "12px", fontSize: "14px" }}>
Mode: <b>{mode}</b>
</div>
{mode === "add-line" ? (
<div style={{ marginTop: "6px", fontSize: "12px", color: "#93c5fd" }}>
Click đ thêm điểm, Enter đ hoàn tất, Esc đ hủy.
</div>
) : null}
{mode === "add-path" ? (
<div style={{ marginTop: "6px", fontSize: "12px", color: "#93c5fd" }}>
Click đ thêm điểm, Enter đ hoàn tất, Esc đ hủy.
</div>
) : null}
{mode === "add-circle" ? (
<div style={{ marginTop: "6px", fontSize: "12px", color: "#93c5fd" }}>
Giữ chuột trái kéo đ mở bán kính, thả chuột đ hoàn tất.
</div>
) : null}
{entityStatus ? (
<div
style={{
marginTop: "12px",
padding: "10px",
background: "#0b1220",
borderRadius: "6px",
border: "1px solid #1f2937",
color: "#fca5a5",
fontSize: "12px",
}}
>
{entityStatus}
</div>
) : null}
<div style={{ marginTop: "12px", display: "flex", gap: "8px" }}>
<button
style={{
flex: 1,
padding: "8px",
borderRadius: "4px",
border: "none",
cursor: "pointer",
background: "#334155",
color: "white",
}}
onClick={onUndo}
>
Undo
</button>
<button
style={{
flex: 1,
padding: "8px",
borderRadius: "4px",
border: "none",
cursor: isSaving ? "not-allowed" : "pointer",
background: isSaving ? "#555" : "#3b82f6",
color: "white",
opacity: changesCount === 0 ? 0.6 : 1,
}}
onClick={onSave}
disabled={isSaving || changesCount === 0}
>
Save ({changesCount})
</button>
</div>
<div
style={{
marginTop: "16px",
padding: "10px",
background: "#0b1220",
borderRadius: "6px",
border: "1px solid #1f2937",
}}
>
<div style={{ marginBottom: "6px", fontWeight: 600, fontSize: "14px" }}>
Tác vụ thể undo ({recentUndoLabels.length})
</div>
{recentUndoLabels.length === 0 ? (
<div style={{ color: "#94a3b8", fontSize: "13px" }}>Chưa thao tác</div>
) : (
<ul style={{ listStyle: "none", margin: 0, padding: 0, fontSize: "13px", color: "#e2e8f0" }}>
{recentUndoLabels.map((label, idx) => (
<li key={`${label}-${idx}`} style={{ padding: "4px 0", borderBottom: "1px solid #1f2937" }}>
{label}
</li>
))}
</ul>
)}
</div>
</div>
);
}
function formatUndoLabel(action: UndoAction) {
switch (action.type) {
case "create":
return `Thêm mới #${action.id}`;
case "delete":
return `Xóa #${action.feature.properties.id}`;
case "update":
return `Chỉnh sửa #${action.id}`;
default:
return "Tác vụ";
}
}