update draw line and shape
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
2026-06-21 06:31:18 +02:00
parent 365a15c32a
commit 064c9b5758
9 changed files with 551 additions and 175 deletions

View File

@@ -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();