This commit is contained in:
@@ -46,6 +46,8 @@
|
||||
pendingBehavior: null,
|
||||
/** Pending directional / planner / I/O zone dialog payload. */
|
||||
pendingAdvanced: null,
|
||||
/** Line width chosen in Draw line dialog (cm). */
|
||||
lineDrawWidthCm: null,
|
||||
/** Cached sounds for sound-zone picker. */
|
||||
soundsCache: [],
|
||||
/** Suppress click after pointer drag. */
|
||||
@@ -132,6 +134,11 @@
|
||||
};
|
||||
const ioDialogEl = el("mapEditorIoDialog");
|
||||
const ioFormEl = el("mapEditorIoForm");
|
||||
const drawLineDialogEl = el("mapEditorDrawLineDialog");
|
||||
const drawLineFormEl = el("mapEditorDrawLineForm");
|
||||
const drawLineFields = {
|
||||
width: el("mapDrawLineWidth"),
|
||||
};
|
||||
const ioFields = {
|
||||
module: el("mapIoModule"),
|
||||
plcRegister: el("mapIoPlcRegister"),
|
||||
@@ -284,7 +291,7 @@
|
||||
|
||||
function rebakeComposite() {
|
||||
const edit = OccEdit();
|
||||
if (!edit?.rebakeComposite(getBaseCanvas(), getSourceCanvas(), state.zones)) return false;
|
||||
if (!edit?.rebakeComposite(getBaseCanvas(), getSourceCanvas(), state.zones, mapMetaForEditor())) return false;
|
||||
state.bakedZoneIds = floorPlanZoneIds();
|
||||
refreshDisplayFromSource();
|
||||
return true;
|
||||
@@ -326,7 +333,7 @@
|
||||
canvasWrapEl.classList.toggle("is-panning", !!state.panning);
|
||||
canvasWrapEl.classList.toggle(
|
||||
"is-draw-tool",
|
||||
state.activeTool === "draw" ||
|
||||
isDrawTool(state.activeTool) ||
|
||||
state.activeTool === "select" ||
|
||||
state.activeTool === "eraseShape" ||
|
||||
state.activeTool === "eraseSelection",
|
||||
@@ -335,6 +342,19 @@
|
||||
canvasWrapEl.classList.toggle("is-erase-selection-tool", state.activeTool === "eraseSelection");
|
||||
}
|
||||
|
||||
function isDrawTool(tool) {
|
||||
return tool === "drawLine" || tool === "drawShape";
|
||||
}
|
||||
|
||||
function drawToolSupported(tool) {
|
||||
const obj = Objects();
|
||||
const type = state.objectType;
|
||||
if (!obj || !type) return false;
|
||||
if (tool === "drawLine") return obj.supportsDrawLine(type);
|
||||
if (tool === "drawShape") return obj.supportsDrawShape(type);
|
||||
return false;
|
||||
}
|
||||
|
||||
function isInteractionBlocked() {
|
||||
return !!(
|
||||
state.draft ||
|
||||
@@ -354,10 +374,12 @@
|
||||
}
|
||||
|
||||
function setActiveTool(tool) {
|
||||
const allowed = ["pan", "draw", "select", "eraser", "eraseShape", "eraseSelection"];
|
||||
const allowed = ["pan", "drawLine", "drawShape", "select", "eraser", "eraseShape", "eraseSelection"];
|
||||
if (!allowed.includes(tool)) return;
|
||||
if (tool !== "pan" && !state.objectType) return;
|
||||
if (tool !== "draw") cancelDraft();
|
||||
if (isDrawTool(tool) && !drawToolSupported(tool)) return;
|
||||
if (isDrawTool(tool) && isDrawTool(state.activeTool) && tool !== state.activeTool) cancelDraft();
|
||||
if (!isDrawTool(tool)) cancelDraft();
|
||||
state.eraserStroke = null;
|
||||
clearTransientDrag();
|
||||
state.activeTool = tool;
|
||||
@@ -368,8 +390,42 @@
|
||||
renderObjects();
|
||||
}
|
||||
|
||||
function beginDrawLine() {
|
||||
if (state.readOnly || !drawToolSupported("drawLine")) return;
|
||||
const obj = Objects();
|
||||
const type = state.objectType;
|
||||
const minWidth = obj?.minLineWidthCm(type) ?? 10;
|
||||
const defaultW = state.lineDrawWidthCm ?? obj?.defaultLineWidthCm(type) ?? 40;
|
||||
if (drawLineFields.width) {
|
||||
drawLineFields.width.min = String(minWidth);
|
||||
drawLineFields.width.step = "1";
|
||||
drawLineFields.width.value = String(Math.round(defaultW));
|
||||
}
|
||||
drawLineDialogEl?.showModal();
|
||||
}
|
||||
|
||||
function cancelDrawLineDialog() {
|
||||
drawLineDialogEl?.close();
|
||||
}
|
||||
|
||||
function commitDrawLineDialog() {
|
||||
const obj = Objects();
|
||||
const type = state.objectType;
|
||||
const minWidth = obj?.minLineWidthCm(type) ?? 10;
|
||||
const widthCm = Number(drawLineFields.width?.value);
|
||||
if (!Number.isFinite(widthCm) || widthCm < minWidth) {
|
||||
alert(t("maps.editor.drawLine.lineWidthInvalid"));
|
||||
return false;
|
||||
}
|
||||
state.lineDrawWidthCm = widthCm;
|
||||
if (state.draft) cancelDraft();
|
||||
setActiveTool("drawLine");
|
||||
drawLineDialogEl?.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
function syncZonesFromMap() {
|
||||
state.zones = Objects()?.parseZones(state.map?.zones) || [];
|
||||
state.zones = Objects()?.parseZones(state.map?.zones, mapMetaForEditor()) || [];
|
||||
state.undoStack = [];
|
||||
state.draft = null;
|
||||
state.selectedZoneId = null;
|
||||
@@ -478,15 +534,25 @@
|
||||
function commitDraft() {
|
||||
const obj = Objects();
|
||||
if (!state.draft || state.draft.kind !== "shape" || !obj) return false;
|
||||
const drawMode = state.draft.drawMode || "shape";
|
||||
const zoneType = obj.resolveZoneTypeForDraw(state.draft.type, drawMode);
|
||||
const lineWidthCm =
|
||||
state.draft.line_width_cm ??
|
||||
state.lineDrawWidthCm ??
|
||||
obj.defaultLineWidthCm(state.draft.type);
|
||||
const extra = {};
|
||||
if (drawMode === "line" && obj.isPlannerZoneType(state.draft.type)) {
|
||||
extra.geometry = obj.GEOMETRY_LINE;
|
||||
extra.line_width_cm = lineWidthCm;
|
||||
}
|
||||
if (state.draft.type === obj.TYPES.speed) {
|
||||
extra.speed_mps = Behavior()?.DEFAULT_SPEED_MPS ?? 0.8;
|
||||
}
|
||||
if (state.draft.type === obj.TYPES.directional) {
|
||||
if (zoneType === obj.TYPES.directional) {
|
||||
extra.direction_deg = 0;
|
||||
}
|
||||
if (state.draft.type === obj.TYPES.directional_line) {
|
||||
extra.line_width = 8;
|
||||
if (zoneType === obj.TYPES.directional_line || zoneType === obj.TYPES.wall) {
|
||||
extra.line_width_cm = lineWidthCm;
|
||||
}
|
||||
if (state.draft.type === obj.TYPES.planner) {
|
||||
Object.assign(extra, Advanced()?.DEFAULT_PLANNER || {});
|
||||
@@ -494,7 +560,7 @@
|
||||
if (state.draft.type === obj.TYPES.io) {
|
||||
extra.io_module = "";
|
||||
}
|
||||
const zone = obj.createZone(state.draft.type, state.draft.points, extra);
|
||||
const zone = obj.createZone(zoneType, state.draft.points, extra);
|
||||
if (!zone) return false;
|
||||
pushUndo();
|
||||
state.zones = [...state.zones, zone];
|
||||
@@ -532,7 +598,9 @@
|
||||
const obj = Objects();
|
||||
const shapeDraft = state.draft?.kind === "shape" ? state.draft : null;
|
||||
const canConfirm =
|
||||
shapeDraft && obj && shapeDraft.points.length >= obj.minPoints(shapeDraft.type);
|
||||
shapeDraft &&
|
||||
obj &&
|
||||
shapeDraft.points.length >= obj.minPointsForDraw(shapeDraft.type, shapeDraft.drawMode || "shape");
|
||||
const confirmBtn = el("mapEditorConfirmDrawBtn");
|
||||
if (confirmBtn) {
|
||||
confirmBtn.hidden = !shapeDraft;
|
||||
@@ -660,9 +728,13 @@
|
||||
const hasType = !!state.objectType && canEdit;
|
||||
const obj = Objects();
|
||||
const isOverlay = obj?.isOverlayObjectType(state.objectType);
|
||||
const type = state.objectType || "";
|
||||
const canLine = hasType && obj?.supportsDrawLine(type);
|
||||
const canShape = hasType && obj?.supportsDrawShape(type);
|
||||
objectTypeBtnEl?.toggleAttribute("disabled", !canEdit);
|
||||
if (!canEdit) closeObjectTypeMenu();
|
||||
el("mapEditorDrawBtn")?.toggleAttribute("disabled", !hasType);
|
||||
el("mapEditorDrawLineBtn")?.toggleAttribute("disabled", !canLine);
|
||||
el("mapEditorDrawShapeBtn")?.toggleAttribute("disabled", !canShape);
|
||||
el("mapEditorSelectBtn")?.toggleAttribute("disabled", !hasType);
|
||||
el("mapEditorEraserBtn")?.toggleAttribute("disabled", !hasType || isOverlay);
|
||||
el("mapEditorEraseShapeBtn")?.toggleAttribute("disabled", !hasType);
|
||||
@@ -691,8 +763,13 @@
|
||||
cancelDraft();
|
||||
closeObjectTypeMenu();
|
||||
updateObjectTypePickerUi();
|
||||
if (state.objectType) setActiveTool("draw");
|
||||
else setActiveTool("pan");
|
||||
const obj = Objects();
|
||||
if (state.objectType === obj?.TYPES.position) setActiveTool("select");
|
||||
else if (state.objectType) {
|
||||
const defaultTool = obj?.defaultDrawTool(state.objectType) || "drawShape";
|
||||
if (defaultTool === "drawLine") beginDrawLine();
|
||||
else setActiveTool(defaultTool);
|
||||
} else setActiveTool("pan");
|
||||
updateObjectToolsUi();
|
||||
}
|
||||
|
||||
@@ -829,7 +906,7 @@
|
||||
function beginPositionDrag(evt) {
|
||||
const obj = Objects();
|
||||
if (
|
||||
state.activeTool !== "draw" ||
|
||||
state.activeTool !== "select" ||
|
||||
state.objectType !== obj?.TYPES.position ||
|
||||
state.readOnly
|
||||
) {
|
||||
@@ -1081,7 +1158,9 @@
|
||||
if (directionalFields.linePanel) directionalFields.linePanel.hidden = false;
|
||||
if (directionalFields.reversed) directionalFields.reversed.checked = !!zone.reversed;
|
||||
if (directionalFields.lineWidth) {
|
||||
directionalFields.lineWidth.value = String(zone.line_width ?? 8);
|
||||
directionalFields.lineWidth.value = String(
|
||||
Math.round(obj.zoneLineWidthCm(zone, mapMetaForEditor())),
|
||||
);
|
||||
}
|
||||
directionalDialogEl?.showModal();
|
||||
return;
|
||||
@@ -1137,14 +1216,15 @@
|
||||
z.id === zoneId ? { ...z, direction_deg } : z,
|
||||
);
|
||||
} else if (zone.type === obj.TYPES.directional_line) {
|
||||
const line_width = Number(directionalFields.lineWidth?.value);
|
||||
if (!Number.isFinite(line_width) || line_width < 2) {
|
||||
const line_width_cm = Number(directionalFields.lineWidth?.value);
|
||||
const minCm = obj.minLineWidthCm(obj.TYPES.directional_line);
|
||||
if (!Number.isFinite(line_width_cm) || line_width_cm < minCm) {
|
||||
alert(t("maps.editor.directional.lineWidthInvalid"));
|
||||
return false;
|
||||
}
|
||||
state.zones = state.zones.map((z) =>
|
||||
z.id === zoneId
|
||||
? { ...z, reversed: !!directionalFields.reversed?.checked, line_width }
|
||||
? { ...z, reversed: !!directionalFields.reversed?.checked, line_width_cm }
|
||||
: z,
|
||||
);
|
||||
}
|
||||
@@ -1224,14 +1304,23 @@
|
||||
if (!obj) return;
|
||||
|
||||
if (
|
||||
state.activeTool === "draw" &&
|
||||
isDrawTool(state.activeTool) &&
|
||||
(obj.isFloorPlanType(state.objectType) ||
|
||||
obj.isPlannerZoneType(state.objectType) ||
|
||||
obj.isBehaviorZoneType(state.objectType) ||
|
||||
obj.isAdvancedZoneType(state.objectType))
|
||||
) {
|
||||
if (!state.draft || state.draft.kind !== "shape") {
|
||||
state.draft = { kind: "shape", type: state.objectType, points: [], hover: null };
|
||||
const drawMode = state.activeTool === "drawLine" ? "line" : "shape";
|
||||
if (!state.draft || state.draft.kind !== "shape" || state.draft.drawMode !== drawMode) {
|
||||
state.draft = {
|
||||
kind: "shape",
|
||||
type: state.objectType,
|
||||
drawMode,
|
||||
line_width_cm:
|
||||
state.lineDrawWidthCm ?? obj.defaultLineWidthCm(state.objectType),
|
||||
points: [],
|
||||
hover: null,
|
||||
};
|
||||
updateUndoUi();
|
||||
}
|
||||
let next = { x: pt.x, y: pt.y };
|
||||
@@ -1239,7 +1328,7 @@
|
||||
if (evt.shiftKey && pts.length) {
|
||||
next = obj.constrainAxis(pts[pts.length - 1], next);
|
||||
}
|
||||
if (obj.isDirectionalLineType(state.draft.type)) {
|
||||
if (obj.isDraftPolyline(state.draft)) {
|
||||
if (pts.length && obj.nearPoint(next, pts[pts.length - 1])) return;
|
||||
pts.push(next);
|
||||
renderObjects();
|
||||
@@ -1694,6 +1783,7 @@
|
||||
state.selectionRect = null;
|
||||
state.positionDrag = null;
|
||||
state.pendingPosition = null;
|
||||
drawLineDialogEl?.close();
|
||||
menuDialogEl?.close();
|
||||
settingsDialogEl?.close();
|
||||
positionDialogEl?.close();
|
||||
@@ -1980,7 +2070,7 @@
|
||||
|
||||
viewportEl?.addEventListener("mousemove", (evt) => {
|
||||
updateStatusBar({ x: evt.clientX, y: evt.clientY });
|
||||
if (state.draft?.kind === "shape" && state.activeTool === "draw") {
|
||||
if (state.draft?.kind === "shape" && isDrawTool(state.activeTool)) {
|
||||
const pt = imagePointFromEvent(evt);
|
||||
const obj = Objects();
|
||||
if (pt && obj) {
|
||||
@@ -2098,7 +2188,8 @@
|
||||
saveMap().catch((e) => alert(e.message));
|
||||
});
|
||||
el("mapEditorPanBtn")?.addEventListener("click", () => setActiveTool("pan"));
|
||||
el("mapEditorDrawBtn")?.addEventListener("click", () => setActiveTool("draw"));
|
||||
el("mapEditorDrawLineBtn")?.addEventListener("click", () => beginDrawLine());
|
||||
el("mapEditorDrawShapeBtn")?.addEventListener("click", () => setActiveTool("drawShape"));
|
||||
el("mapEditorSelectBtn")?.addEventListener("click", () => setActiveTool("select"));
|
||||
el("mapEditorEraserBtn")?.addEventListener("click", () => setActiveTool("eraser"));
|
||||
el("mapEditorEraseShapeBtn")?.addEventListener("click", () => setActiveTool("eraseShape"));
|
||||
@@ -2163,6 +2254,15 @@
|
||||
state.pendingBehavior = null;
|
||||
window.LmApp?.setActivePage?.("sounds");
|
||||
});
|
||||
el("mapDrawLineCancelBtn")?.addEventListener("click", () => cancelDrawLineDialog());
|
||||
drawLineDialogEl?.addEventListener("cancel", (evt) => {
|
||||
evt.preventDefault();
|
||||
cancelDrawLineDialog();
|
||||
});
|
||||
drawLineFormEl?.addEventListener("submit", (evt) => {
|
||||
evt.preventDefault();
|
||||
commitDrawLineDialog();
|
||||
});
|
||||
el("mapDirectionalCancelBtn")?.addEventListener("click", () => cancelAdvancedDialog());
|
||||
directionalDialogEl?.addEventListener("cancel", (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user