thêm phần paths
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-23 17:51:54 +07:00
parent 50a2587cef
commit 2ce8a23ce9
25 changed files with 1411 additions and 31 deletions

View File

@@ -61,6 +61,10 @@
yamlMeta: null,
/** Pending ROS metadata from upload dialog (set before PNG picker). */
uploadMeta: null,
/** Dotted path overlay when opened from Setup → Paths (view). */
pathPreview: null,
/** Fingerprint of preferred zones when map was loaded (for save warning). */
initialPreferredFp: "",
};
const titleEl = el("mapEditorTitle");
@@ -500,6 +504,46 @@
return mapMetaForOriginDisplay() || state.map || {};
}
function preferredZonesFingerprint(zones) {
return zones
.filter((z) => z && z.type === "preferred")
.map((z) =>
JSON.stringify({
id: z.id,
points: z.points,
geometry: z.geometry,
line_width_cm: z.line_width_cm,
}),
)
.sort()
.join("|");
}
function renderPathPreview() {
if (!objectsSvgEl || !state.pathPreview?.points?.length) return;
const geo = Geo();
const meta = mapMetaForEditor();
const { width, height } = floorPlanSize();
if (!geo || !width) return;
const pts = state.pathPreview.points
.map((p) => {
const px = geo.worldToPixel(meta, width, height, p.x, p.y);
return `${px.x},${px.y}`;
})
.join(" ");
const poly = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
poly.setAttribute("points", pts);
poly.setAttribute("fill", "none");
poly.setAttribute("stroke", "#2563eb");
poly.setAttribute("stroke-width", "3");
poly.setAttribute("stroke-dasharray", "8 6");
poly.setAttribute("stroke-linecap", "round");
poly.setAttribute("stroke-linejoin", "round");
poly.classList.add("mapEditorPathPreview");
poly.setAttribute("pointer-events", "none");
objectsSvgEl.appendChild(poly);
}
function renderObjects() {
const obj = Objects();
if (!objectsSvgEl || !obj) return;
@@ -530,6 +574,7 @@
draft: state.draft,
selectionRect: state.selectionRect,
});
renderPathPreview();
}
function imagePointFromEvent(evt) {
@@ -1769,6 +1814,7 @@
state.map = await api(`/api/maps/${encodeURIComponent(state.mapId)}`);
await loadYamlMeta();
syncZonesFromMap();
state.initialPreferredFp = preferredZonesFingerprint(state.zones);
updateHeader();
renderMapImage();
fillSettingsForm();
@@ -1796,6 +1842,8 @@
state.selectionRect = null;
state.positionDrag = null;
state.pendingPosition = null;
state.pathPreview = callbacks.pathPreview || null;
state.initialPreferredFp = "";
updateObjectTypePickerUi();
state.view = Geo()?.createView(1, 0, 0) || { scale: 1, panX: 0, panY: 0 };
if (tipEl) {
@@ -1829,6 +1877,8 @@
state.selectionRect = null;
state.positionDrag = null;
state.pendingPosition = null;
state.pathPreview = null;
state.initialPreferredFp = "";
drawLineDialogEl?.close();
menuDialogEl?.close();
settingsDialogEl?.close();
@@ -2062,6 +2112,19 @@
if (imageUpdated) state.map = { ...state.map, ...imageUpdated };
state.rasterDirty = false;
}
const preferredChanged =
preferredZonesFingerprint(state.zones) !== state.initialPreferredFp;
if (preferredChanged && state.map?.id) {
try {
const pathsData = await api(
`/api/paths?site_id=${encodeURIComponent(state.map.site_id || "")}`,
);
const onMap = (pathsData.paths || []).filter((p) => p.map_id === state.map.id);
if (onMap.length && !confirm(t("maps.editor.preferredPathWarning"))) return;
} catch {
/* continue save */
}
}
const updated = await api(`/api/maps/${encodeURIComponent(state.map.id)}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
@@ -2069,6 +2132,7 @@
});
state.map = updated;
syncZonesFromMap();
state.initialPreferredFp = preferredZonesFingerprint(state.zones);
state.callbacks.onMapUpdated?.(updated);
setDirty(false);
updateHeader();