Files
History-client/lib/engine/lineEngine.ts
2026-04-20 23:27:38 +07:00

142 lines
3.8 KiB
TypeScript

import maplibregl from "maplibre-gl";
import { Geometry } from "@/lib/useEditorState";
type ModeGetter = () => "idle" | "draw" | "select" | "add-point" | "add-line" | "add-path" | "add-circle";
const EMPTY_PREVIEW: GeoJSON.FeatureCollection = {
type: "FeatureCollection",
features: [],
};
// Khởi tạo engine vẽ line (gấp khúc, không mũi tên).
export function initLine(
map: maplibregl.Map,
getMode: ModeGetter,
onComplete: (geometry: Geometry) => void
) {
let coords: [number, number][] = [];
// Xóa dữ liệu preview line.
const clearPreview = () => {
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
EMPTY_PREVIEW
);
};
// Hủy phiên vẽ line hiện tại.
const cancelLine = () => {
coords = [];
clearPreview();
};
// Cập nhật line preview theo danh sách tọa độ tạm.
const updatePreview = (lineCoords: [number, number][]) => {
if (lineCoords.length < 2) {
clearPreview();
return;
}
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "LineString",
coordinates: lineCoords,
},
},
],
});
};
// Chốt line khi đủ số đỉnh tối thiểu.
const finishLine = () => {
if (getMode() !== "add-line" || coords.length < 2) return;
const geometry: Geometry = {
type: "LineString",
coordinates: [...coords],
};
onComplete(geometry);
cancelLine();
};
// Xóa đỉnh cuối cùng trong line đang vẽ.
const removeLastVertex = () => {
if (!coords.length) return;
coords = coords.slice(0, -1);
updatePreview(coords);
};
// Thêm một đỉnh line khi click map.
const onClick = (e: maplibregl.MapLayerMouseEvent) => {
if (getMode() !== "add-line") return;
coords.push([e.lngLat.lng, e.lngLat.lat]);
updatePreview(coords);
};
// Cập nhật preview động theo vị trí chuột.
const onMove = (e: maplibregl.MapLayerMouseEvent) => {
const canvas = map.getCanvas();
if (getMode() !== "add-line") {
if (coords.length) {
cancelLine();
}
if (canvas.style.cursor === "crosshair") {
canvas.style.cursor = "";
}
return;
}
canvas.style.cursor = "crosshair";
if (coords.length === 0) return;
updatePreview([...coords, [e.lngLat.lng, e.lngLat.lat]]);
};
// Xử lý phím nóng Enter/Escape/Backspace cho chế độ vẽ line.
const onKeyDown = (e: KeyboardEvent) => {
if (getMode() !== "add-line") return;
if (e.key === "Enter") {
e.preventDefault();
finishLine();
return;
}
if (e.key === "Escape") {
e.preventDefault();
cancelLine();
return;
}
if (e.key === "Backspace") {
e.preventDefault();
removeLastVertex();
}
};
map.on("click", onClick);
map.on("mousemove", onMove);
document.addEventListener("keydown", onKeyDown);
const cleanup = () => {
map.off("click", onClick);
map.off("mousemove", onMove);
document.removeEventListener("keydown", onKeyDown);
cancelLine();
if (map.getCanvas().style.cursor === "crosshair") {
map.getCanvas().style.cursor = "";
}
};
return {
cleanup,
cancel: cancelLine,
};
}