This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
const el = (id) => document.getElementById(id);
|
||||
const t = (key, vars) => window.I18n?.t(key, vars) ?? key;
|
||||
const Geo = () => window.MapGeo;
|
||||
const Occ = () => window.MapOccupancyCanvas;
|
||||
|
||||
const state = {
|
||||
mapId: null,
|
||||
@@ -14,6 +15,9 @@
|
||||
view: Geo()?.createView(1, 0, 0) || { scale: 1, panX: 0, panY: 0 },
|
||||
panning: null,
|
||||
tipVisible: true,
|
||||
showOrigin: true,
|
||||
/** ROS yaml thresholds (occupied_thresh, free_thresh, negate) from map.yaml. */
|
||||
yamlMeta: null,
|
||||
/** Pending ROS metadata from upload dialog (set before PNG picker). */
|
||||
uploadMeta: null,
|
||||
};
|
||||
@@ -27,7 +31,10 @@
|
||||
const sheetEl = el("mapEditorSheet");
|
||||
const gridEl = el("mapEditorSheetGrid");
|
||||
const imageEl = el("mapEditorImage");
|
||||
const occupancyCanvasEl = el("mapEditorOccupancyCanvas");
|
||||
const originEl = el("mapEditorOrigin");
|
||||
const originHitEl = el("mapEditorOriginHit");
|
||||
const originLabelEl = el("mapEditorOriginLabel");
|
||||
const emptyEl = el("mapEditorEmpty");
|
||||
const tipEl = el("mapEditorCanvasTip");
|
||||
const statusViewEl = el("mapEditorStatusView");
|
||||
@@ -94,7 +101,58 @@
|
||||
}
|
||||
|
||||
function hasFloorPlan() {
|
||||
return !!(state.map?.image_file && imageEl && !imageEl.hidden && imageEl.naturalWidth);
|
||||
return !!(
|
||||
state.map?.image_file &&
|
||||
imageEl &&
|
||||
!imageEl.hidden &&
|
||||
imageEl.naturalWidth &&
|
||||
occupancyCanvasEl &&
|
||||
!occupancyCanvasEl.hidden &&
|
||||
occupancyCanvasEl.width
|
||||
);
|
||||
}
|
||||
|
||||
/** ROS yaml thresholds for occupancy coloring. */
|
||||
function mapRenderMeta() {
|
||||
const base = mapMetaForOriginDisplay() || state.map || {};
|
||||
const yaml = state.yamlMeta || {};
|
||||
return {
|
||||
occupied_thresh:
|
||||
base.occupied_thresh != null ? base.occupied_thresh : yaml.occupied_thresh,
|
||||
free_thresh: base.free_thresh != null ? base.free_thresh : yaml.free_thresh,
|
||||
negate: base.negate != null ? base.negate : yaml.negate,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadYamlMeta() {
|
||||
state.yamlMeta = await fetchExistingYamlMeta();
|
||||
}
|
||||
|
||||
function setOccupancyCanvasVisible(visible) {
|
||||
if (!occupancyCanvasEl) return;
|
||||
occupancyCanvasEl.hidden = !visible;
|
||||
occupancyCanvasEl.setAttribute("aria-hidden", visible ? "false" : "true");
|
||||
}
|
||||
|
||||
/** Paint RViz-style occupancy colors from loaded PNG (hidden loader img). */
|
||||
function paintOccupancyFromImage() {
|
||||
const occ = Occ();
|
||||
if (!occ || !occupancyCanvasEl || !imageEl?.naturalWidth) return false;
|
||||
const ok = occ.renderFromImage(occupancyCanvasEl, imageEl, mapRenderMeta());
|
||||
setOccupancyCanvasVisible(ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint live occupancy grid (record/stream — roadmap step 2+).
|
||||
* @param {{ width: number, height: number, data: number[]|Int8Array|string }} grid
|
||||
*/
|
||||
function paintOccupancyGrid(grid) {
|
||||
const occ = Occ();
|
||||
if (!occ || !occupancyCanvasEl || !grid) return false;
|
||||
const ok = occ.renderGrid(occupancyCanvasEl, grid);
|
||||
if (ok) setOccupancyCanvasVisible(true);
|
||||
return ok;
|
||||
}
|
||||
|
||||
function setDirty(flag) {
|
||||
@@ -204,7 +262,6 @@
|
||||
gridEl.hidden = true;
|
||||
}
|
||||
|
||||
if (originEl) originEl.hidden = !has;
|
||||
updateOriginMarker();
|
||||
updateStatusBar();
|
||||
}
|
||||
@@ -224,11 +281,25 @@
|
||||
return base;
|
||||
}
|
||||
|
||||
function setShowOrigin(show) {
|
||||
state.showOrigin = !!show;
|
||||
const btn = el("mapEditorOriginBtn");
|
||||
btn?.classList.toggle("is-active", state.showOrigin);
|
||||
btn?.setAttribute("aria-pressed", state.showOrigin ? "true" : "false");
|
||||
updateOriginMarker();
|
||||
}
|
||||
|
||||
function setOriginLabelVisible(visible) {
|
||||
if (!originEl) return;
|
||||
originEl.classList.toggle("mapEditorOrigin--showLabel", !!visible);
|
||||
if (originLabelEl) originLabelEl.setAttribute("aria-hidden", visible ? "false" : "true");
|
||||
}
|
||||
|
||||
function updateOriginMarker() {
|
||||
if (!originEl) return;
|
||||
const geo = Geo();
|
||||
const { width, height } = floorPlanSize();
|
||||
if (!geo || !hasFloorPlan() || !width || !height) {
|
||||
if (!state.showOrigin || !geo || !hasFloorPlan() || !width || !height) {
|
||||
originEl.hidden = true;
|
||||
originEl.setAttribute("aria-hidden", "true");
|
||||
return;
|
||||
@@ -249,18 +320,23 @@
|
||||
originEl.style.top = `${pt.y}px`;
|
||||
originEl.style.transform = `rotate(${yawDeg}deg)`;
|
||||
|
||||
const labelEl = el("mapEditorOriginLabel");
|
||||
if (labelEl) {
|
||||
labelEl.textContent = t("maps.editor.originLabelShort", {
|
||||
if (originLabelEl) {
|
||||
originLabelEl.textContent = t("maps.editor.originLabelShort", {
|
||||
x: ox.toFixed(2),
|
||||
y: oy.toFixed(2),
|
||||
});
|
||||
originLabelEl.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
originEl.title = t("maps.editor.originTooltip", {
|
||||
const tooltip = t("maps.editor.originTooltip", {
|
||||
x: ox.toFixed(3),
|
||||
y: oy.toFixed(3),
|
||||
yaw: ((oyaw * 180) / Math.PI).toFixed(1),
|
||||
});
|
||||
if (originHitEl) {
|
||||
originHitEl.title = tooltip;
|
||||
originHitEl.setAttribute("aria-label", tooltip);
|
||||
}
|
||||
setOriginLabelVisible(false);
|
||||
}
|
||||
|
||||
function updateStatusBar(pointerClient) {
|
||||
@@ -311,11 +387,13 @@
|
||||
imageEl.src = url;
|
||||
imageEl.hidden = false;
|
||||
if (emptyEl) emptyEl.hidden = true;
|
||||
setOccupancyCanvasVisible(false);
|
||||
} else {
|
||||
if (imageEl) {
|
||||
imageEl.hidden = true;
|
||||
imageEl.removeAttribute("src");
|
||||
}
|
||||
setOccupancyCanvasVisible(false);
|
||||
if (emptyEl) emptyEl.hidden = false;
|
||||
}
|
||||
updateMenuActionsUi();
|
||||
@@ -323,6 +401,7 @@
|
||||
imageEl?.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
paintOccupancyFromImage();
|
||||
updateImageLayer();
|
||||
fitToView();
|
||||
},
|
||||
@@ -381,6 +460,7 @@
|
||||
async function reloadMap() {
|
||||
if (!state.mapId) return;
|
||||
state.map = await api(`/api/maps/${encodeURIComponent(state.mapId)}`);
|
||||
await loadYamlMeta();
|
||||
updateHeader();
|
||||
renderMapImage();
|
||||
fillSettingsForm();
|
||||
@@ -392,6 +472,7 @@
|
||||
state.readOnly = !!callbacks.readOnly || !callbacks.canWrite;
|
||||
state.dirty = false;
|
||||
state.tipVisible = true;
|
||||
state.showOrigin = true;
|
||||
state.activeTool = "pan";
|
||||
state.view = Geo()?.createView(1, 0, 0) || { scale: 1, panX: 0, panY: 0 };
|
||||
if (tipEl) {
|
||||
@@ -399,6 +480,7 @@
|
||||
tipEl.textContent = t("maps.editor.canvasTip");
|
||||
}
|
||||
setActiveTool("pan");
|
||||
setShowOrigin(true);
|
||||
setDirty(false);
|
||||
applyReadOnlyUi();
|
||||
reloadMap().catch((e) => alert(e.message));
|
||||
@@ -409,6 +491,7 @@
|
||||
state.map = null;
|
||||
state.callbacks = {};
|
||||
state.uploadMeta = null;
|
||||
state.yamlMeta = null;
|
||||
menuDialogEl?.close();
|
||||
settingsDialogEl?.close();
|
||||
activateDialogEl?.close();
|
||||
@@ -584,6 +667,7 @@
|
||||
state.callbacks.onMapUpdated?.(updated);
|
||||
setDirty(false);
|
||||
fillSettingsForm();
|
||||
await loadYamlMeta();
|
||||
renderMapImage();
|
||||
menuDialogEl?.close();
|
||||
uploadMetaDialogEl?.close();
|
||||
@@ -689,6 +773,11 @@
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
originHitEl?.addEventListener("mouseenter", () => setOriginLabelVisible(true));
|
||||
originHitEl?.addEventListener("mouseleave", () => setOriginLabelVisible(false));
|
||||
originHitEl?.addEventListener("focus", () => setOriginLabelVisible(true));
|
||||
originHitEl?.addEventListener("blur", () => setOriginLabelVisible(false));
|
||||
|
||||
el("mapEditorBackBtn")?.addEventListener("click", () => {
|
||||
if (state.dirty && !confirm(t("maps.editor.unsavedLeave"))) return;
|
||||
state.callbacks.onClose?.();
|
||||
@@ -714,6 +803,7 @@
|
||||
saveMap().catch((e) => alert(e.message));
|
||||
});
|
||||
el("mapEditorPanBtn")?.addEventListener("click", () => setActiveTool("pan"));
|
||||
el("mapEditorOriginBtn")?.addEventListener("click", () => setShowOrigin(!state.showOrigin));
|
||||
el("mapEditorFitBtn")?.addEventListener("click", fitToView);
|
||||
el("mapEditorCenterBtn")?.addEventListener("click", () => {
|
||||
dismissCanvasTip();
|
||||
@@ -818,6 +908,9 @@
|
||||
Object.values(uploadMetaFields).forEach((node) => {
|
||||
node?.addEventListener("input", () => {
|
||||
updateOriginMarker();
|
||||
if (uploadMetaDialogEl?.open && imageEl?.naturalWidth) {
|
||||
paintOccupancyFromImage();
|
||||
}
|
||||
if (node === uploadMetaFields.resolution && uploadMetaDialogEl?.open) {
|
||||
updateImageLayer();
|
||||
}
|
||||
@@ -835,5 +928,11 @@
|
||||
bindCanvasPanZoom();
|
||||
bindEvents();
|
||||
|
||||
window.MapEditorApp = { open, close, reloadMap };
|
||||
window.MapEditorApp = {
|
||||
open,
|
||||
close,
|
||||
reloadMap,
|
||||
paintOccupancyGrid,
|
||||
paintOccupancyFromImage,
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user