This commit is contained in:
26
www/i18n.js
26
www/i18n.js
@@ -424,6 +424,13 @@
|
|||||||
"maps.editor.filter.unpreferred": "Unpreferred",
|
"maps.editor.filter.unpreferred": "Unpreferred",
|
||||||
"maps.editor.filter.speed": "Speed",
|
"maps.editor.filter.speed": "Speed",
|
||||||
"maps.editor.filter.sound": "Sound",
|
"maps.editor.filter.sound": "Sound",
|
||||||
|
"maps.editor.tool.drawLine": "Vẽ line — polyline (wall, drive zone line…)",
|
||||||
|
"maps.editor.tool.drawShape": "Vẽ shape — polygon (floor, zones…)",
|
||||||
|
"maps.editor.drawLine.title": "Vẽ line mới",
|
||||||
|
"maps.editor.drawLine.lineWidth": "Độ rộng line (cm)",
|
||||||
|
"maps.editor.drawLine.hint": "Chỉnh độ rộng line (cm), xác nhận, rồi click trên map để đặt các điểm.",
|
||||||
|
"maps.editor.drawLine.confirm": "Xác nhận",
|
||||||
|
"maps.editor.drawLine.lineWidthInvalid": "Nhập độ rộng line hợp lệ (wall ≥ 5 cm, zone line ≥ 10 cm).",
|
||||||
"maps.editor.tool.draw": "Vẽ — line hoặc polygon (walls, zones…)",
|
"maps.editor.tool.draw": "Vẽ — line hoặc polygon (walls, zones…)",
|
||||||
"maps.editor.tool.select": "Chọn object",
|
"maps.editor.tool.select": "Chọn object",
|
||||||
"maps.editor.tool.erase": "Tẩy object",
|
"maps.editor.tool.erase": "Tẩy object",
|
||||||
@@ -431,7 +438,7 @@
|
|||||||
"maps.editor.tool.eraseShape": "Xóa line/shape đã vẽ",
|
"maps.editor.tool.eraseShape": "Xóa line/shape đã vẽ",
|
||||||
"maps.editor.tool.eraseSelection": "Xóa vùng chọn — tẩy pixel trong khung",
|
"maps.editor.tool.eraseSelection": "Xóa vùng chọn — tẩy pixel trong khung",
|
||||||
"maps.editor.tool.confirmDraw": "Xác nhận hình vẽ",
|
"maps.editor.tool.confirmDraw": "Xác nhận hình vẽ",
|
||||||
"maps.editor.drawNeedMorePoints": "Cần thêm điểm trước khi xác nhận (wall ≥ 2, polygon ≥ 3).",
|
"maps.editor.drawNeedMorePoints": "Cần thêm điểm trước khi xác nhận (line ≥ 2, polygon ≥ 3).",
|
||||||
"maps.editor.position.title": "Position",
|
"maps.editor.position.title": "Position",
|
||||||
"maps.editor.position.name": "Tên",
|
"maps.editor.position.name": "Tên",
|
||||||
"maps.editor.position.x": "X (m)",
|
"maps.editor.position.x": "X (m)",
|
||||||
@@ -452,10 +459,10 @@
|
|||||||
"maps.editor.directional.direction": "Hướng",
|
"maps.editor.directional.direction": "Hướng",
|
||||||
"maps.editor.directional.degOption": "{deg}°",
|
"maps.editor.directional.degOption": "{deg}°",
|
||||||
"maps.editor.directional.shapeHint": "Robot không được di chuyển ngược hướng mũi tên (bước 45°).",
|
"maps.editor.directional.shapeHint": "Robot không được di chuyển ngược hướng mũi tên (bước 45°).",
|
||||||
"maps.editor.directional.lineWidth": "Độ rộng line (px)",
|
"maps.editor.directional.lineWidth": "Độ rộng line (cm)",
|
||||||
"maps.editor.directional.reversed": "Đảo hướng",
|
"maps.editor.directional.reversed": "Đảo hướng",
|
||||||
"maps.editor.directional.lineHint": "Hướng theo line từ điểm đầu đến điểm cuối.",
|
"maps.editor.directional.lineHint": "Hướng theo line từ điểm đầu đến điểm cuối.",
|
||||||
"maps.editor.directional.lineWidthInvalid": "Nhập độ rộng line hợp lệ (≥ 2 px).",
|
"maps.editor.directional.lineWidthInvalid": "Nhập độ rộng line hợp lệ (≥ 10 cm).",
|
||||||
"maps.editor.planner.title": "Planner zone",
|
"maps.editor.planner.title": "Planner zone",
|
||||||
"maps.editor.planner.noLocalization": "No localization (chỉ encoder)",
|
"maps.editor.planner.noLocalization": "No localization (chỉ encoder)",
|
||||||
"maps.editor.planner.lookAhead": "Look-ahead (thu hẹp field of view)",
|
"maps.editor.planner.lookAhead": "Look-ahead (thu hẹp field of view)",
|
||||||
@@ -1031,6 +1038,13 @@
|
|||||||
"maps.editor.filter.unpreferred": "Unpreferred",
|
"maps.editor.filter.unpreferred": "Unpreferred",
|
||||||
"maps.editor.filter.speed": "Speed",
|
"maps.editor.filter.speed": "Speed",
|
||||||
"maps.editor.filter.sound": "Sound",
|
"maps.editor.filter.sound": "Sound",
|
||||||
|
"maps.editor.tool.drawLine": "Draw line — polyline (wall, drive zone line…)",
|
||||||
|
"maps.editor.tool.drawShape": "Draw shape — polygon (floor, zones…)",
|
||||||
|
"maps.editor.drawLine.title": "Draw a new line",
|
||||||
|
"maps.editor.drawLine.lineWidth": "Line width (cm)",
|
||||||
|
"maps.editor.drawLine.hint": "Set the line width in cm, confirm, then click on the map to place points.",
|
||||||
|
"maps.editor.drawLine.confirm": "Confirm",
|
||||||
|
"maps.editor.drawLine.lineWidthInvalid": "Enter a valid line width (wall ≥ 5 cm, zone line ≥ 10 cm).",
|
||||||
"maps.editor.tool.draw": "Draw — line or polygon (walls, zones…)",
|
"maps.editor.tool.draw": "Draw — line or polygon (walls, zones…)",
|
||||||
"maps.editor.tool.select": "Select object",
|
"maps.editor.tool.select": "Select object",
|
||||||
"maps.editor.tool.erase": "Eraser",
|
"maps.editor.tool.erase": "Eraser",
|
||||||
@@ -1038,7 +1052,7 @@
|
|||||||
"maps.editor.tool.eraseShape": "Erase shape or line",
|
"maps.editor.tool.eraseShape": "Erase shape or line",
|
||||||
"maps.editor.tool.eraseSelection": "Erase by selection — clear pixels in rectangle",
|
"maps.editor.tool.eraseSelection": "Erase by selection — clear pixels in rectangle",
|
||||||
"maps.editor.tool.confirmDraw": "Confirm shape",
|
"maps.editor.tool.confirmDraw": "Confirm shape",
|
||||||
"maps.editor.drawNeedMorePoints": "Add more points before confirming (wall ≥ 2, polygon ≥ 3).",
|
"maps.editor.drawNeedMorePoints": "Add more points before confirming (line ≥ 2, polygon ≥ 3).",
|
||||||
"maps.editor.position.title": "Position",
|
"maps.editor.position.title": "Position",
|
||||||
"maps.editor.position.name": "Name",
|
"maps.editor.position.name": "Name",
|
||||||
"maps.editor.position.x": "X (m)",
|
"maps.editor.position.x": "X (m)",
|
||||||
@@ -1059,10 +1073,10 @@
|
|||||||
"maps.editor.directional.direction": "Direction",
|
"maps.editor.directional.direction": "Direction",
|
||||||
"maps.editor.directional.degOption": "{deg}°",
|
"maps.editor.directional.degOption": "{deg}°",
|
||||||
"maps.editor.directional.shapeHint": "Robot cannot move opposite to the arrow (45° steps).",
|
"maps.editor.directional.shapeHint": "Robot cannot move opposite to the arrow (45° steps).",
|
||||||
"maps.editor.directional.lineWidth": "Line width (px)",
|
"maps.editor.directional.lineWidth": "Line width (cm)",
|
||||||
"maps.editor.directional.reversed": "Reverse direction",
|
"maps.editor.directional.reversed": "Reverse direction",
|
||||||
"maps.editor.directional.lineHint": "Direction follows the line from first to last point.",
|
"maps.editor.directional.lineHint": "Direction follows the line from first to last point.",
|
||||||
"maps.editor.directional.lineWidthInvalid": "Enter a valid line width (≥ 2 px).",
|
"maps.editor.directional.lineWidthInvalid": "Enter a valid line width (≥ 10 cm).",
|
||||||
"maps.editor.planner.title": "Planner zone",
|
"maps.editor.planner.title": "Planner zone",
|
||||||
"maps.editor.planner.noLocalization": "No localization (encoders only)",
|
"maps.editor.planner.noLocalization": "No localization (encoders only)",
|
||||||
"maps.editor.planner.lookAhead": "Look-ahead (narrow field of view)",
|
"maps.editor.planner.lookAhead": "Look-ahead (narrow field of view)",
|
||||||
|
|||||||
@@ -1135,12 +1135,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<span data-i18n="maps.editor.objectType.directional">Directional zones</span>
|
<span data-i18n="maps.editor.objectType.directional">Directional zones</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="mapEditorObjectTypeOption" role="option" data-value="directional_line" tabindex="-1">
|
|
||||||
<span class="mapEditorObjectTypeOptionIcon mapEditorObjectTypeOptionIcon--directionalLine" aria-hidden="true">
|
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18"><path d="M3 14L15 4" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/><path d="M11 4h4v4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
||||||
</span>
|
|
||||||
<span data-i18n="maps.editor.objectType.directionalLine">Directional lines</span>
|
|
||||||
</li>
|
|
||||||
<li class="mapEditorObjectTypeOption" role="option" data-value="planner" tabindex="-1">
|
<li class="mapEditorObjectTypeOption" role="option" data-value="planner" tabindex="-1">
|
||||||
<span class="mapEditorObjectTypeOptionIcon mapEditorObjectTypeOptionIcon--planner" aria-hidden="true">
|
<span class="mapEditorObjectTypeOptionIcon mapEditorObjectTypeOptionIcon--planner" aria-hidden="true">
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18"><polygon points="3,14 9,4 15,14" fill="currentColor" fill-opacity="0.14" stroke="currentColor" stroke-width="1.5" stroke-dasharray="4 2"/><circle cx="9" cy="9" r="2" fill="currentColor"/></svg>
|
<svg width="18" height="18" viewBox="0 0 18 18"><polygon points="3,14 9,4 15,14" fill="currentColor" fill-opacity="0.14" stroke="currentColor" stroke-width="1.5" stroke-dasharray="4 2"/><circle cx="9" cy="9" r="2" fill="currentColor"/></svg>
|
||||||
@@ -1155,8 +1149,11 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="mapEditorMapTool" id="mapEditorDrawBtn" data-tool="draw" disabled data-i18n-title="maps.editor.tool.draw" title="Draw">
|
<button type="button" class="mapEditorMapTool" id="mapEditorDrawLineBtn" data-tool="drawLine" disabled data-i18n-title="maps.editor.tool.drawLine" title="Draw line">
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.5 3.5l2 2-9 9H5.5v-2l9-9z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M12.5 5.5l2 2" stroke="currentColor" stroke-width="1.4"/></svg>
|
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 16L16 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="mapEditorMapTool" id="mapEditorDrawShapeBtn" data-tool="drawShape" disabled data-i18n-title="maps.editor.tool.drawShape" title="Draw shape">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><polygon points="10,3 17,16 3,16" fill="currentColor" fill-opacity="0.15" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="mapEditorMapTool" id="mapEditorSelectBtn" data-tool="select" disabled data-i18n-title="maps.editor.tool.select" title="Select">
|
<button type="button" class="mapEditorMapTool" id="mapEditorSelectBtn" data-tool="select" disabled data-i18n-title="maps.editor.tool.select" title="Select">
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 3l12 7-5.5 1.5L8 17 6.5 11.5 4 3z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
<svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 3l12 7-5.5 1.5L8 17 6.5 11.5 4 3z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
||||||
@@ -1420,6 +1417,21 @@
|
|||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="mapEditorDrawLineDialog" class="mapsMirDialog">
|
||||||
|
<form id="mapEditorDrawLineForm" method="dialog">
|
||||||
|
<h2 class="mapsMirDialogTitle" data-i18n="maps.editor.drawLine.title">Draw a new line</h2>
|
||||||
|
<label class="mapsMirField">
|
||||||
|
<span class="mapsMirFieldLabel" data-i18n="maps.editor.drawLine.lineWidth">Line width (cm)</span>
|
||||||
|
<input type="number" id="mapDrawLineWidth" min="5" max="500" step="1" value="40" required />
|
||||||
|
</label>
|
||||||
|
<p class="mapsMirDialogHint" data-i18n="maps.editor.drawLine.hint">Set the line width, confirm, then click on the map to place points.</p>
|
||||||
|
<div class="mapsMirDialogFooter">
|
||||||
|
<button type="button" class="mapsMirBtn mapsMirBtn--outline" id="mapDrawLineCancelBtn" data-i18n="common.cancel">Cancel</button>
|
||||||
|
<button type="submit" class="mapsMirBtn mapsMirBtn--primary" data-i18n="maps.editor.drawLine.confirm">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<dialog id="mapEditorDirectionalDialog" class="mapsMirDialog">
|
<dialog id="mapEditorDirectionalDialog" class="mapsMirDialog">
|
||||||
<form id="mapEditorDirectionalForm" method="dialog">
|
<form id="mapEditorDirectionalForm" method="dialog">
|
||||||
<h2 class="mapsMirDialogTitle" data-i18n="maps.editor.directional.title">Directional zone</h2>
|
<h2 class="mapsMirDialogTitle" data-i18n="maps.editor.directional.title">Directional zone</h2>
|
||||||
@@ -1432,8 +1444,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="mapDirectionalLinePanel" hidden>
|
<div id="mapDirectionalLinePanel" hidden>
|
||||||
<label class="mapsMirField">
|
<label class="mapsMirField">
|
||||||
<span class="mapsMirFieldLabel" data-i18n="maps.editor.directional.lineWidth">Line width (px)</span>
|
<span class="mapsMirFieldLabel" data-i18n="maps.editor.directional.lineWidth">Line width (cm)</span>
|
||||||
<input type="number" id="mapDirectionalLineWidth" min="2" max="48" step="1" value="8" />
|
<input type="number" id="mapDirectionalLineWidth" min="10" max="500" step="1" value="40" />
|
||||||
</label>
|
</label>
|
||||||
<label class="mapsMirField mapsMirField--checkbox">
|
<label class="mapsMirField mapsMirField--checkbox">
|
||||||
<input type="checkbox" id="mapDirectionalReversed" />
|
<input type="checkbox" id="mapDirectionalReversed" />
|
||||||
|
|||||||
@@ -117,14 +117,14 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function zonesAtPoint(zones, px, py) {
|
function zonesAtPoint(zones, px, py, mapMeta = null) {
|
||||||
const list = Array.isArray(zones) ? zones : [];
|
const list = Array.isArray(zones) ? zones : [];
|
||||||
const hits = [];
|
const hits = [];
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
for (let i = list.length - 1; i >= 0; i--) {
|
||||||
const z = list[i];
|
const z = list[i];
|
||||||
if (!isAdvancedZoneType(z?.type) || !z.points?.length) continue;
|
if (!isAdvancedZoneType(z?.type) || !z.points?.length) continue;
|
||||||
if (z.type === TYPES.directional_line) {
|
if (z.type === TYPES.directional_line) {
|
||||||
const width = Number(z.line_width) || 8;
|
const width = window.MapObjects?.zoneLineWidthPx(z, mapMeta) ?? 8;
|
||||||
if (pointNearPolyline(px, py, z.points, width / 2 + 4)) hits.push(z);
|
if (pointNearPolyline(px, py, z.points, width / 2 + 4)) hits.push(z);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@
|
|||||||
pendingBehavior: null,
|
pendingBehavior: null,
|
||||||
/** Pending directional / planner / I/O zone dialog payload. */
|
/** Pending directional / planner / I/O zone dialog payload. */
|
||||||
pendingAdvanced: null,
|
pendingAdvanced: null,
|
||||||
|
/** Line width chosen in Draw line dialog (cm). */
|
||||||
|
lineDrawWidthCm: null,
|
||||||
/** Cached sounds for sound-zone picker. */
|
/** Cached sounds for sound-zone picker. */
|
||||||
soundsCache: [],
|
soundsCache: [],
|
||||||
/** Suppress click after pointer drag. */
|
/** Suppress click after pointer drag. */
|
||||||
@@ -132,6 +134,11 @@
|
|||||||
};
|
};
|
||||||
const ioDialogEl = el("mapEditorIoDialog");
|
const ioDialogEl = el("mapEditorIoDialog");
|
||||||
const ioFormEl = el("mapEditorIoForm");
|
const ioFormEl = el("mapEditorIoForm");
|
||||||
|
const drawLineDialogEl = el("mapEditorDrawLineDialog");
|
||||||
|
const drawLineFormEl = el("mapEditorDrawLineForm");
|
||||||
|
const drawLineFields = {
|
||||||
|
width: el("mapDrawLineWidth"),
|
||||||
|
};
|
||||||
const ioFields = {
|
const ioFields = {
|
||||||
module: el("mapIoModule"),
|
module: el("mapIoModule"),
|
||||||
plcRegister: el("mapIoPlcRegister"),
|
plcRegister: el("mapIoPlcRegister"),
|
||||||
@@ -284,7 +291,7 @@
|
|||||||
|
|
||||||
function rebakeComposite() {
|
function rebakeComposite() {
|
||||||
const edit = OccEdit();
|
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();
|
state.bakedZoneIds = floorPlanZoneIds();
|
||||||
refreshDisplayFromSource();
|
refreshDisplayFromSource();
|
||||||
return true;
|
return true;
|
||||||
@@ -326,7 +333,7 @@
|
|||||||
canvasWrapEl.classList.toggle("is-panning", !!state.panning);
|
canvasWrapEl.classList.toggle("is-panning", !!state.panning);
|
||||||
canvasWrapEl.classList.toggle(
|
canvasWrapEl.classList.toggle(
|
||||||
"is-draw-tool",
|
"is-draw-tool",
|
||||||
state.activeTool === "draw" ||
|
isDrawTool(state.activeTool) ||
|
||||||
state.activeTool === "select" ||
|
state.activeTool === "select" ||
|
||||||
state.activeTool === "eraseShape" ||
|
state.activeTool === "eraseShape" ||
|
||||||
state.activeTool === "eraseSelection",
|
state.activeTool === "eraseSelection",
|
||||||
@@ -335,6 +342,19 @@
|
|||||||
canvasWrapEl.classList.toggle("is-erase-selection-tool", state.activeTool === "eraseSelection");
|
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() {
|
function isInteractionBlocked() {
|
||||||
return !!(
|
return !!(
|
||||||
state.draft ||
|
state.draft ||
|
||||||
@@ -354,10 +374,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setActiveTool(tool) {
|
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 (!allowed.includes(tool)) return;
|
||||||
if (tool !== "pan" && !state.objectType) 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;
|
state.eraserStroke = null;
|
||||||
clearTransientDrag();
|
clearTransientDrag();
|
||||||
state.activeTool = tool;
|
state.activeTool = tool;
|
||||||
@@ -368,8 +390,42 @@
|
|||||||
renderObjects();
|
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() {
|
function syncZonesFromMap() {
|
||||||
state.zones = Objects()?.parseZones(state.map?.zones) || [];
|
state.zones = Objects()?.parseZones(state.map?.zones, mapMetaForEditor()) || [];
|
||||||
state.undoStack = [];
|
state.undoStack = [];
|
||||||
state.draft = null;
|
state.draft = null;
|
||||||
state.selectedZoneId = null;
|
state.selectedZoneId = null;
|
||||||
@@ -478,15 +534,25 @@
|
|||||||
function commitDraft() {
|
function commitDraft() {
|
||||||
const obj = Objects();
|
const obj = Objects();
|
||||||
if (!state.draft || state.draft.kind !== "shape" || !obj) return false;
|
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 = {};
|
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) {
|
if (state.draft.type === obj.TYPES.speed) {
|
||||||
extra.speed_mps = Behavior()?.DEFAULT_SPEED_MPS ?? 0.8;
|
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;
|
extra.direction_deg = 0;
|
||||||
}
|
}
|
||||||
if (state.draft.type === obj.TYPES.directional_line) {
|
if (zoneType === obj.TYPES.directional_line || zoneType === obj.TYPES.wall) {
|
||||||
extra.line_width = 8;
|
extra.line_width_cm = lineWidthCm;
|
||||||
}
|
}
|
||||||
if (state.draft.type === obj.TYPES.planner) {
|
if (state.draft.type === obj.TYPES.planner) {
|
||||||
Object.assign(extra, Advanced()?.DEFAULT_PLANNER || {});
|
Object.assign(extra, Advanced()?.DEFAULT_PLANNER || {});
|
||||||
@@ -494,7 +560,7 @@
|
|||||||
if (state.draft.type === obj.TYPES.io) {
|
if (state.draft.type === obj.TYPES.io) {
|
||||||
extra.io_module = "";
|
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;
|
if (!zone) return false;
|
||||||
pushUndo();
|
pushUndo();
|
||||||
state.zones = [...state.zones, zone];
|
state.zones = [...state.zones, zone];
|
||||||
@@ -532,7 +598,9 @@
|
|||||||
const obj = Objects();
|
const obj = Objects();
|
||||||
const shapeDraft = state.draft?.kind === "shape" ? state.draft : null;
|
const shapeDraft = state.draft?.kind === "shape" ? state.draft : null;
|
||||||
const canConfirm =
|
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");
|
const confirmBtn = el("mapEditorConfirmDrawBtn");
|
||||||
if (confirmBtn) {
|
if (confirmBtn) {
|
||||||
confirmBtn.hidden = !shapeDraft;
|
confirmBtn.hidden = !shapeDraft;
|
||||||
@@ -660,9 +728,13 @@
|
|||||||
const hasType = !!state.objectType && canEdit;
|
const hasType = !!state.objectType && canEdit;
|
||||||
const obj = Objects();
|
const obj = Objects();
|
||||||
const isOverlay = obj?.isOverlayObjectType(state.objectType);
|
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);
|
objectTypeBtnEl?.toggleAttribute("disabled", !canEdit);
|
||||||
if (!canEdit) closeObjectTypeMenu();
|
if (!canEdit) closeObjectTypeMenu();
|
||||||
el("mapEditorDrawBtn")?.toggleAttribute("disabled", !hasType);
|
el("mapEditorDrawLineBtn")?.toggleAttribute("disabled", !canLine);
|
||||||
|
el("mapEditorDrawShapeBtn")?.toggleAttribute("disabled", !canShape);
|
||||||
el("mapEditorSelectBtn")?.toggleAttribute("disabled", !hasType);
|
el("mapEditorSelectBtn")?.toggleAttribute("disabled", !hasType);
|
||||||
el("mapEditorEraserBtn")?.toggleAttribute("disabled", !hasType || isOverlay);
|
el("mapEditorEraserBtn")?.toggleAttribute("disabled", !hasType || isOverlay);
|
||||||
el("mapEditorEraseShapeBtn")?.toggleAttribute("disabled", !hasType);
|
el("mapEditorEraseShapeBtn")?.toggleAttribute("disabled", !hasType);
|
||||||
@@ -691,8 +763,13 @@
|
|||||||
cancelDraft();
|
cancelDraft();
|
||||||
closeObjectTypeMenu();
|
closeObjectTypeMenu();
|
||||||
updateObjectTypePickerUi();
|
updateObjectTypePickerUi();
|
||||||
if (state.objectType) setActiveTool("draw");
|
const obj = Objects();
|
||||||
else setActiveTool("pan");
|
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();
|
updateObjectToolsUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,7 +906,7 @@
|
|||||||
function beginPositionDrag(evt) {
|
function beginPositionDrag(evt) {
|
||||||
const obj = Objects();
|
const obj = Objects();
|
||||||
if (
|
if (
|
||||||
state.activeTool !== "draw" ||
|
state.activeTool !== "select" ||
|
||||||
state.objectType !== obj?.TYPES.position ||
|
state.objectType !== obj?.TYPES.position ||
|
||||||
state.readOnly
|
state.readOnly
|
||||||
) {
|
) {
|
||||||
@@ -1081,7 +1158,9 @@
|
|||||||
if (directionalFields.linePanel) directionalFields.linePanel.hidden = false;
|
if (directionalFields.linePanel) directionalFields.linePanel.hidden = false;
|
||||||
if (directionalFields.reversed) directionalFields.reversed.checked = !!zone.reversed;
|
if (directionalFields.reversed) directionalFields.reversed.checked = !!zone.reversed;
|
||||||
if (directionalFields.lineWidth) {
|
if (directionalFields.lineWidth) {
|
||||||
directionalFields.lineWidth.value = String(zone.line_width ?? 8);
|
directionalFields.lineWidth.value = String(
|
||||||
|
Math.round(obj.zoneLineWidthCm(zone, mapMetaForEditor())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
directionalDialogEl?.showModal();
|
directionalDialogEl?.showModal();
|
||||||
return;
|
return;
|
||||||
@@ -1137,14 +1216,15 @@
|
|||||||
z.id === zoneId ? { ...z, direction_deg } : z,
|
z.id === zoneId ? { ...z, direction_deg } : z,
|
||||||
);
|
);
|
||||||
} else if (zone.type === obj.TYPES.directional_line) {
|
} else if (zone.type === obj.TYPES.directional_line) {
|
||||||
const line_width = Number(directionalFields.lineWidth?.value);
|
const line_width_cm = Number(directionalFields.lineWidth?.value);
|
||||||
if (!Number.isFinite(line_width) || line_width < 2) {
|
const minCm = obj.minLineWidthCm(obj.TYPES.directional_line);
|
||||||
|
if (!Number.isFinite(line_width_cm) || line_width_cm < minCm) {
|
||||||
alert(t("maps.editor.directional.lineWidthInvalid"));
|
alert(t("maps.editor.directional.lineWidthInvalid"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state.zones = state.zones.map((z) =>
|
state.zones = state.zones.map((z) =>
|
||||||
z.id === zoneId
|
z.id === zoneId
|
||||||
? { ...z, reversed: !!directionalFields.reversed?.checked, line_width }
|
? { ...z, reversed: !!directionalFields.reversed?.checked, line_width_cm }
|
||||||
: z,
|
: z,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1224,14 +1304,23 @@
|
|||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
state.activeTool === "draw" &&
|
isDrawTool(state.activeTool) &&
|
||||||
(obj.isFloorPlanType(state.objectType) ||
|
(obj.isFloorPlanType(state.objectType) ||
|
||||||
obj.isPlannerZoneType(state.objectType) ||
|
obj.isPlannerZoneType(state.objectType) ||
|
||||||
obj.isBehaviorZoneType(state.objectType) ||
|
obj.isBehaviorZoneType(state.objectType) ||
|
||||||
obj.isAdvancedZoneType(state.objectType))
|
obj.isAdvancedZoneType(state.objectType))
|
||||||
) {
|
) {
|
||||||
if (!state.draft || state.draft.kind !== "shape") {
|
const drawMode = state.activeTool === "drawLine" ? "line" : "shape";
|
||||||
state.draft = { kind: "shape", type: state.objectType, points: [], hover: null };
|
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();
|
updateUndoUi();
|
||||||
}
|
}
|
||||||
let next = { x: pt.x, y: pt.y };
|
let next = { x: pt.x, y: pt.y };
|
||||||
@@ -1239,7 +1328,7 @@
|
|||||||
if (evt.shiftKey && pts.length) {
|
if (evt.shiftKey && pts.length) {
|
||||||
next = obj.constrainAxis(pts[pts.length - 1], next);
|
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;
|
if (pts.length && obj.nearPoint(next, pts[pts.length - 1])) return;
|
||||||
pts.push(next);
|
pts.push(next);
|
||||||
renderObjects();
|
renderObjects();
|
||||||
@@ -1694,6 +1783,7 @@
|
|||||||
state.selectionRect = null;
|
state.selectionRect = null;
|
||||||
state.positionDrag = null;
|
state.positionDrag = null;
|
||||||
state.pendingPosition = null;
|
state.pendingPosition = null;
|
||||||
|
drawLineDialogEl?.close();
|
||||||
menuDialogEl?.close();
|
menuDialogEl?.close();
|
||||||
settingsDialogEl?.close();
|
settingsDialogEl?.close();
|
||||||
positionDialogEl?.close();
|
positionDialogEl?.close();
|
||||||
@@ -1980,7 +2070,7 @@
|
|||||||
|
|
||||||
viewportEl?.addEventListener("mousemove", (evt) => {
|
viewportEl?.addEventListener("mousemove", (evt) => {
|
||||||
updateStatusBar({ x: evt.clientX, y: evt.clientY });
|
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 pt = imagePointFromEvent(evt);
|
||||||
const obj = Objects();
|
const obj = Objects();
|
||||||
if (pt && obj) {
|
if (pt && obj) {
|
||||||
@@ -2098,7 +2188,8 @@
|
|||||||
saveMap().catch((e) => alert(e.message));
|
saveMap().catch((e) => alert(e.message));
|
||||||
});
|
});
|
||||||
el("mapEditorPanBtn")?.addEventListener("click", () => setActiveTool("pan"));
|
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("mapEditorSelectBtn")?.addEventListener("click", () => setActiveTool("select"));
|
||||||
el("mapEditorEraserBtn")?.addEventListener("click", () => setActiveTool("eraser"));
|
el("mapEditorEraserBtn")?.addEventListener("click", () => setActiveTool("eraser"));
|
||||||
el("mapEditorEraseShapeBtn")?.addEventListener("click", () => setActiveTool("eraseShape"));
|
el("mapEditorEraseShapeBtn")?.addEventListener("click", () => setActiveTool("eraseShape"));
|
||||||
@@ -2163,6 +2254,15 @@
|
|||||||
state.pendingBehavior = null;
|
state.pendingBehavior = null;
|
||||||
window.LmApp?.setActivePage?.("sounds");
|
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());
|
el("mapDirectionalCancelBtn")?.addEventListener("click", () => cancelAdvancedDialog());
|
||||||
directionalDialogEl?.addEventListener("cancel", (evt) => {
|
directionalDialogEl?.addEventListener("cancel", (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|||||||
@@ -25,6 +25,20 @@
|
|||||||
return res > 0 ? 1 / res : 20;
|
return res > 0 ? 1 / res : 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Centimetres → image pixels (uses map resolution m/px). */
|
||||||
|
function cmToPixels(cm, map) {
|
||||||
|
const res = meta(map).resolution;
|
||||||
|
if (!Number.isFinite(Number(cm)) || res <= 0) return 0;
|
||||||
|
return Number(cm) / 100 / res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Image pixels → centimetres. */
|
||||||
|
function pixelsToCm(px, map) {
|
||||||
|
const res = meta(map).resolution;
|
||||||
|
if (!Number.isFinite(Number(px)) || res <= 0) return 0;
|
||||||
|
return Number(px) * res * 100;
|
||||||
|
}
|
||||||
|
|
||||||
function imageSize(map, imageEl) {
|
function imageSize(map, imageEl) {
|
||||||
const w = imageEl?.naturalWidth || meta(map).width || 0;
|
const w = imageEl?.naturalWidth || meta(map).width || 0;
|
||||||
const h = imageEl?.naturalHeight || meta(map).height || 0;
|
const h = imageEl?.naturalHeight || meta(map).height || 0;
|
||||||
@@ -143,6 +157,8 @@
|
|||||||
window.MapGeo = {
|
window.MapGeo = {
|
||||||
meta,
|
meta,
|
||||||
pixelsPerMeter,
|
pixelsPerMeter,
|
||||||
|
cmToPixels,
|
||||||
|
pixelsToCm,
|
||||||
imageSize,
|
imageSize,
|
||||||
createView,
|
createView,
|
||||||
applyViewTransform,
|
applyViewTransform,
|
||||||
|
|||||||
@@ -27,6 +27,21 @@
|
|||||||
TYPES.io,
|
TYPES.io,
|
||||||
]);
|
]);
|
||||||
const POINT_SHAPE_TYPES = new Set([TYPES.wall, TYPES.directional_line, ...POLYGON_TYPES]);
|
const POINT_SHAPE_TYPES = new Set([TYPES.wall, TYPES.directional_line, ...POLYGON_TYPES]);
|
||||||
|
const DUAL_DRAW_TYPES = new Set([
|
||||||
|
TYPES.forbidden,
|
||||||
|
TYPES.preferred,
|
||||||
|
TYPES.unpreferred,
|
||||||
|
TYPES.directional,
|
||||||
|
]);
|
||||||
|
const LINE_ONLY_OBJECT_TYPES = new Set([TYPES.wall]);
|
||||||
|
const SHAPE_ONLY_OBJECT_TYPES = new Set([
|
||||||
|
TYPES.floor,
|
||||||
|
TYPES.speed,
|
||||||
|
TYPES.sound,
|
||||||
|
TYPES.planner,
|
||||||
|
TYPES.io,
|
||||||
|
]);
|
||||||
|
const GEOMETRY_LINE = "line";
|
||||||
|
|
||||||
function newId() {
|
function newId() {
|
||||||
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
||||||
@@ -45,6 +60,91 @@
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function supportsDrawLine(type) {
|
||||||
|
if (!type) return false;
|
||||||
|
return LINE_ONLY_OBJECT_TYPES.has(type) || DUAL_DRAW_TYPES.has(type) || type === TYPES.directional_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsDrawShape(type) {
|
||||||
|
if (!type) return false;
|
||||||
|
return SHAPE_ONLY_OBJECT_TYPES.has(type) || DUAL_DRAW_TYPES.has(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultDrawTool(type) {
|
||||||
|
if (type === TYPES.wall) return "drawLine";
|
||||||
|
if (supportsDrawShape(type) && !supportsDrawLine(type)) return "drawShape";
|
||||||
|
if (supportsDrawLine(type) && !supportsDrawShape(type)) return "drawLine";
|
||||||
|
return "drawShape";
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultLineWidthCm(type) {
|
||||||
|
if (type === TYPES.wall) return 15;
|
||||||
|
return 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minLineWidthCm(type) {
|
||||||
|
if (type === TYPES.wall) return 5;
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeUsesLineWidthCm(type, geometry = null) {
|
||||||
|
if (type === TYPES.wall || type === TYPES.directional_line) return true;
|
||||||
|
return isPlannerZoneType(type) && geometry === GEOMETRY_LINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneLineWidthCm(zone, mapMeta) {
|
||||||
|
if (Number.isFinite(Number(zone?.line_width_cm))) return Number(zone.line_width_cm);
|
||||||
|
if (Number.isFinite(Number(zone?.line_width))) {
|
||||||
|
return window.MapGeo?.pixelsToCm(Number(zone.line_width), mapMeta) ?? defaultLineWidthCm(zone?.type);
|
||||||
|
}
|
||||||
|
return defaultLineWidthCm(zone?.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneLineWidthPx(zone, mapMeta) {
|
||||||
|
const geo = window.MapGeo;
|
||||||
|
const cm = zoneLineWidthCm(zone, mapMeta);
|
||||||
|
const px = geo?.cmToPixels(cm, mapMeta);
|
||||||
|
if (Number.isFinite(px) && px > 0) return px;
|
||||||
|
return zone?.type === TYPES.wall ? 3 : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLineWidthCmFromRaw(z, mapMeta) {
|
||||||
|
if (Number.isFinite(Number(z?.line_width_cm))) return Number(z.line_width_cm);
|
||||||
|
if (Number.isFinite(Number(z?.line_width))) {
|
||||||
|
return window.MapGeo?.pixelsToCm(Number(z.line_width), mapMeta) ?? defaultLineWidthCm(z?.type);
|
||||||
|
}
|
||||||
|
return defaultLineWidthCm(z?.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignLineWidthCm(zone, cm, type) {
|
||||||
|
const min = minLineWidthCm(type);
|
||||||
|
zone.line_width_cm = Math.max(min, Number(cm) || defaultLineWidthCm(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveZoneTypeForDraw(objectType, drawMode) {
|
||||||
|
if (objectType === TYPES.directional && drawMode === "line") return TYPES.directional_line;
|
||||||
|
return objectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function minPointsForDraw(type, drawMode) {
|
||||||
|
const zoneType = resolveZoneTypeForDraw(type, drawMode || "shape");
|
||||||
|
if (drawMode === "line" || isPolylineType(zoneType)) return 2;
|
||||||
|
return minPoints(zoneType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLineGeometry(z) {
|
||||||
|
if (!z) return false;
|
||||||
|
if (z.type === TYPES.wall || z.type === TYPES.directional_line) return true;
|
||||||
|
return isPlannerZoneType(z.type) && z.geometry === GEOMETRY_LINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDraftPolyline(draft) {
|
||||||
|
if (!draft || draft.kind !== "shape") return false;
|
||||||
|
if (draft.drawMode === "line") return true;
|
||||||
|
if (draft.drawMode === "shape") return false;
|
||||||
|
return isPolylineType(draft.type);
|
||||||
|
}
|
||||||
|
|
||||||
function isFloorPlanType(type) {
|
function isFloorPlanType(type) {
|
||||||
return FLOOR_PLAN_TYPES.has(type);
|
return FLOOR_PLAN_TYPES.has(type);
|
||||||
}
|
}
|
||||||
@@ -92,10 +192,17 @@
|
|||||||
|
|
||||||
function isValidPolygon(z) {
|
function isValidPolygon(z) {
|
||||||
if (!z || !isPolygonType(z.type)) return false;
|
if (!z || !isPolygonType(z.type)) return false;
|
||||||
|
if (isLineGeometry(z)) return false;
|
||||||
const points = Array.isArray(z.points) ? z.points.map(normalizePoint).filter(Boolean) : [];
|
const points = Array.isArray(z.points) ? z.points.map(normalizePoint).filter(Boolean) : [];
|
||||||
return points.length >= minPoints(z.type);
|
return points.length >= minPoints(z.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidPlannerLineZone(z) {
|
||||||
|
if (!z || !isPlannerZoneType(z.type) || z.geometry !== GEOMETRY_LINE) return false;
|
||||||
|
const points = Array.isArray(z.points) ? z.points.map(normalizePoint).filter(Boolean) : [];
|
||||||
|
return points.length >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
function isValidWall(z) {
|
function isValidWall(z) {
|
||||||
if (!z || z.type !== TYPES.wall) return false;
|
if (!z || z.type !== TYPES.wall) return false;
|
||||||
const points = Array.isArray(z.points) ? z.points.map(normalizePoint).filter(Boolean) : [];
|
const points = Array.isArray(z.points) ? z.points.map(normalizePoint).filter(Boolean) : [];
|
||||||
@@ -148,12 +255,13 @@
|
|||||||
if (z?.type === TYPES.directional_line) return isValidDirectionalLine(z);
|
if (z?.type === TYPES.directional_line) return isValidDirectionalLine(z);
|
||||||
if (z?.type === TYPES.planner) return isValidPlannerZone(z);
|
if (z?.type === TYPES.planner) return isValidPlannerZone(z);
|
||||||
if (z?.type === TYPES.io) return isValidIoZone(z);
|
if (z?.type === TYPES.io) return isValidIoZone(z);
|
||||||
|
if (isPlannerZoneType(z?.type) && z.geometry === GEOMETRY_LINE) return isValidPlannerLineZone(z);
|
||||||
if (isPolygonType(z?.type)) return isValidPolygon(z);
|
if (isPolygonType(z?.type)) return isValidPolygon(z);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parse all map objects from API / database payload. */
|
/** Parse all map objects from API / database payload. */
|
||||||
function parseZones(raw) {
|
function parseZones(raw, mapMeta = null) {
|
||||||
if (!Array.isArray(raw)) return [];
|
if (!Array.isArray(raw)) return [];
|
||||||
return raw
|
return raw
|
||||||
.map((z) => {
|
.map((z) => {
|
||||||
@@ -185,7 +293,10 @@
|
|||||||
}
|
}
|
||||||
if (z.type === TYPES.directional_line) {
|
if (z.type === TYPES.directional_line) {
|
||||||
base.reversed = !!z.reversed;
|
base.reversed = !!z.reversed;
|
||||||
base.line_width = Number.isFinite(Number(z.line_width)) ? Number(z.line_width) : 8;
|
assignLineWidthCm(base, readLineWidthCmFromRaw(z, mapMeta), TYPES.directional_line);
|
||||||
|
}
|
||||||
|
if (z.type === TYPES.wall) {
|
||||||
|
assignLineWidthCm(base, readLineWidthCmFromRaw(z, mapMeta), TYPES.wall);
|
||||||
}
|
}
|
||||||
if (z.type === TYPES.planner) {
|
if (z.type === TYPES.planner) {
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -200,6 +311,10 @@
|
|||||||
base.plc_value = io.plc_value;
|
base.plc_value = io.plc_value;
|
||||||
base.plc_mode = io.plc_mode;
|
base.plc_mode = io.plc_mode;
|
||||||
}
|
}
|
||||||
|
if (isPlannerZoneType(z.type) && z.geometry === GEOMETRY_LINE) {
|
||||||
|
base.geometry = GEOMETRY_LINE;
|
||||||
|
assignLineWidthCm(base, readLineWidthCmFromRaw(z, mapMeta), z.type);
|
||||||
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -209,8 +324,14 @@
|
|||||||
|
|
||||||
function createZone(type, points, extra = {}) {
|
function createZone(type, points, extra = {}) {
|
||||||
const pts = points.map(normalizePoint).filter(Boolean);
|
const pts = points.map(normalizePoint).filter(Boolean);
|
||||||
if (pts.length < minPoints(type)) return null;
|
const lineGeom = isPlannerZoneType(type) && extra.geometry === GEOMETRY_LINE;
|
||||||
|
const min = lineGeom ? 2 : minPoints(type);
|
||||||
|
if (pts.length < min) return null;
|
||||||
const zone = { id: newId(), type, points: pts };
|
const zone = { id: newId(), type, points: pts };
|
||||||
|
if (lineGeom) {
|
||||||
|
zone.geometry = GEOMETRY_LINE;
|
||||||
|
assignLineWidthCm(zone, extra.line_width_cm, type);
|
||||||
|
}
|
||||||
if (type === TYPES.speed) zone.speed_mps = clampSpeedMps(extra.speed_mps);
|
if (type === TYPES.speed) zone.speed_mps = clampSpeedMps(extra.speed_mps);
|
||||||
if (type === TYPES.sound) zone.sound_id = typeof extra.sound_id === "string" ? extra.sound_id : "";
|
if (type === TYPES.sound) zone.sound_id = typeof extra.sound_id === "string" ? extra.sound_id : "";
|
||||||
if (type === TYPES.directional) {
|
if (type === TYPES.directional) {
|
||||||
@@ -218,7 +339,10 @@
|
|||||||
}
|
}
|
||||||
if (type === TYPES.directional_line) {
|
if (type === TYPES.directional_line) {
|
||||||
zone.reversed = !!extra.reversed;
|
zone.reversed = !!extra.reversed;
|
||||||
zone.line_width = Number.isFinite(Number(extra.line_width)) ? Number(extra.line_width) : 8;
|
assignLineWidthCm(zone, extra.line_width_cm, type);
|
||||||
|
}
|
||||||
|
if (type === TYPES.wall) {
|
||||||
|
assignLineWidthCm(zone, extra.line_width_cm, type);
|
||||||
}
|
}
|
||||||
if (type === TYPES.planner) {
|
if (type === TYPES.planner) {
|
||||||
Object.assign(zone, window.MapAdvancedZones?.normalizePlannerSettings(extra) || {});
|
Object.assign(zone, window.MapAdvancedZones?.normalizePlannerSettings(extra) || {});
|
||||||
@@ -269,10 +393,20 @@
|
|||||||
return geo.worldToPixel(mapMeta, imgW, imgH, z.x, z.y);
|
return geo.worldToPixel(mapMeta, imgW, imgH, z.x, z.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hitTestPlannerZone(zones, px, py) {
|
function hitTestPlannerZone(zones, px, py, tolerance = 8, mapMeta = null) {
|
||||||
for (let i = zones.length - 1; i >= 0; i--) {
|
for (let i = zones.length - 1; i >= 0; i--) {
|
||||||
const z = zones[i];
|
const z = zones[i];
|
||||||
if (isPlannerZoneType(z.type) && pointInPolygon(px, py, z.points)) return z;
|
if (!isPlannerZoneType(z.type) || !z.points?.length) continue;
|
||||||
|
if (isLineGeometry(z)) {
|
||||||
|
const half = zoneLineWidthPx(z, mapMeta) / 2;
|
||||||
|
for (let j = 0; j < z.points.length - 1; j++) {
|
||||||
|
const p1 = z.points[j];
|
||||||
|
const p2 = z.points[j + 1];
|
||||||
|
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= half + tolerance) return z;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pointInPolygon(px, py, z.points)) return z;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -285,7 +419,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hitTestAdvancedZone(zones, px, py, tolerance = 8) {
|
function hitTestAdvancedZone(zones, px, py, tolerance = 8, mapMeta = null) {
|
||||||
for (let i = zones.length - 1; i >= 0; i--) {
|
for (let i = zones.length - 1; i >= 0; i--) {
|
||||||
const z = zones[i];
|
const z = zones[i];
|
||||||
if (!isAdvancedZoneType(z.type)) continue;
|
if (!isAdvancedZoneType(z.type)) continue;
|
||||||
@@ -293,7 +427,7 @@
|
|||||||
for (let j = 0; j < z.points.length - 1; j++) {
|
for (let j = 0; j < z.points.length - 1; j++) {
|
||||||
const p1 = z.points[j];
|
const p1 = z.points[j];
|
||||||
const p2 = z.points[j + 1];
|
const p2 = z.points[j + 1];
|
||||||
const width = Number(z.line_width) || 8;
|
const width = zoneLineWidthPx(z, mapMeta);
|
||||||
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= width / 2 + tolerance) return z;
|
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= width / 2 + tolerance) return z;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -304,22 +438,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Find topmost floor-plan shape at image pixel. */
|
/** Find topmost floor-plan shape at image pixel. */
|
||||||
function hitTest(zones, px, py, tolerance = 8) {
|
function hitTest(zones, px, py, tolerance = 8, mapMeta = null) {
|
||||||
for (let i = zones.length - 1; i >= 0; i--) {
|
for (let i = zones.length - 1; i >= 0; i--) {
|
||||||
const z = zones[i];
|
const z = zones[i];
|
||||||
if (z.type === TYPES.floor && pointInPolygon(px, py, z.points)) return z;
|
if (z.type === TYPES.floor && pointInPolygon(px, py, z.points)) return z;
|
||||||
if (z.type === TYPES.wall) {
|
if (z.type === TYPES.wall) {
|
||||||
|
const half = zoneLineWidthPx(z, mapMeta) / 2;
|
||||||
for (let j = 0; j < z.points.length - 1; j++) {
|
for (let j = 0; j < z.points.length - 1; j++) {
|
||||||
const p1 = z.points[j];
|
const p1 = z.points[j];
|
||||||
const p2 = z.points[j + 1];
|
const p2 = z.points[j + 1];
|
||||||
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= tolerance) return z;
|
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= half + tolerance) return z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (z.type === TYPES.directional_line) {
|
if (z.type === TYPES.directional_line) {
|
||||||
for (let j = 0; j < z.points.length - 1; j++) {
|
for (let j = 0; j < z.points.length - 1; j++) {
|
||||||
const p1 = z.points[j];
|
const p1 = z.points[j];
|
||||||
const p2 = z.points[j + 1];
|
const p2 = z.points[j + 1];
|
||||||
const width = Number(z.line_width) || 8;
|
const width = zoneLineWidthPx(z, mapMeta);
|
||||||
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= width / 2 + tolerance) return z;
|
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= width / 2 + tolerance) return z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,11 +477,11 @@
|
|||||||
if (pos) return pos;
|
if (pos) return pos;
|
||||||
const behavior = hitTestBehaviorZone(zones, px, py);
|
const behavior = hitTestBehaviorZone(zones, px, py);
|
||||||
if (behavior) return behavior;
|
if (behavior) return behavior;
|
||||||
const advanced = hitTestAdvancedZone(zones, px, py, tolerance);
|
const advanced = hitTestAdvancedZone(zones, px, py, tolerance, mapMeta);
|
||||||
if (advanced) return advanced;
|
if (advanced) return advanced;
|
||||||
const planner = hitTestPlannerZone(zones, px, py);
|
const planner = hitTestPlannerZone(zones, px, py, tolerance, mapMeta);
|
||||||
if (planner) return planner;
|
if (planner) return planner;
|
||||||
return hitTest(zones, px, py, tolerance);
|
return hitTest(zones, px, py, tolerance, mapMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hit vertex handle on selected or any point-based shape. */
|
/** Hit vertex handle on selected or any point-based shape. */
|
||||||
@@ -528,6 +663,7 @@
|
|||||||
[TYPES.speed]: "mapObjSpeed",
|
[TYPES.speed]: "mapObjSpeed",
|
||||||
[TYPES.sound]: "mapObjSound",
|
[TYPES.sound]: "mapObjSound",
|
||||||
[TYPES.directional]: "mapObjDirectional",
|
[TYPES.directional]: "mapObjDirectional",
|
||||||
|
[TYPES.directional_line]: "mapObjDirectional",
|
||||||
[TYPES.planner]: "mapObjPlannerSettings",
|
[TYPES.planner]: "mapObjPlannerSettings",
|
||||||
[TYPES.io]: "mapObjIo",
|
[TYPES.io]: "mapObjIo",
|
||||||
}[type];
|
}[type];
|
||||||
@@ -537,6 +673,10 @@
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zonePolygonClass(type, selected, draft = false) {
|
||||||
|
return polygonClass(type === TYPES.directional_line ? TYPES.directional : type, selected, draft);
|
||||||
|
}
|
||||||
|
|
||||||
function filterVisible(zones, visibility) {
|
function filterVisible(zones, visibility) {
|
||||||
const vis = visibility || {};
|
const vis = visibility || {};
|
||||||
return zones.filter((z) => {
|
return zones.filter((z) => {
|
||||||
@@ -575,12 +715,15 @@
|
|||||||
list.forEach((z) => {
|
list.forEach((z) => {
|
||||||
const selected = z.id === opts.selectedId;
|
const selected = z.id === opts.selectedId;
|
||||||
if (z.type === TYPES.wall) {
|
if (z.type === TYPES.wall) {
|
||||||
appendPolyline(svgEl, z.points, selected ? "mapObjWall mapObjWall--selected" : "mapObjWall");
|
appendPolylineStyled(
|
||||||
|
svgEl,
|
||||||
|
z.points,
|
||||||
|
selected ? "mapObjWall mapObjWall--selected" : "mapObjWall",
|
||||||
|
zoneLineWidthPx(z, mapMeta),
|
||||||
|
);
|
||||||
} else if (z.type === TYPES.directional_line) {
|
} else if (z.type === TYPES.directional_line) {
|
||||||
const cls = selected
|
const cls = zonePolygonClass(TYPES.directional_line, selected);
|
||||||
? "mapObjDirectionalLine mapObjDirectionalLine--selected"
|
appendPolylineStyled(svgEl, z.points, cls, zoneLineWidthPx(z, mapMeta));
|
||||||
: "mapObjDirectionalLine";
|
|
||||||
appendPolylineStyled(svgEl, z.points, cls, z.line_width || 8);
|
|
||||||
const mid = lineMidpoint(z.points);
|
const mid = lineMidpoint(z.points);
|
||||||
if (mid) {
|
if (mid) {
|
||||||
appendDirectionArrow(
|
appendDirectionArrow(
|
||||||
@@ -591,8 +734,15 @@
|
|||||||
selected,
|
selected,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (isPlannerZoneType(z.type) && isLineGeometry(z)) {
|
||||||
|
appendPolylineStyled(
|
||||||
|
svgEl,
|
||||||
|
z.points,
|
||||||
|
zonePolygonClass(z.type, selected),
|
||||||
|
zoneLineWidthPx(z, mapMeta),
|
||||||
|
);
|
||||||
} else if (isPolygonType(z.type)) {
|
} else if (isPolygonType(z.type)) {
|
||||||
appendPolygon(svgEl, z.points, polygonClass(z.type, selected));
|
appendPolygon(svgEl, z.points, zonePolygonClass(z.type, selected));
|
||||||
if (z.type === TYPES.directional) {
|
if (z.type === TYPES.directional) {
|
||||||
const c = polygonCentroid(z.points);
|
const c = polygonCentroid(z.points);
|
||||||
if (c) appendDirectionArrow(svgEl, c.x, c.y, z.direction_deg ?? 0, selected);
|
if (c) appendDirectionArrow(svgEl, c.x, c.y, z.direction_deg ?? 0, selected);
|
||||||
@@ -621,15 +771,22 @@
|
|||||||
if (draft?.kind === "shape" && draft.type && Array.isArray(draft.points) && draft.points.length) {
|
if (draft?.kind === "shape" && draft.type && Array.isArray(draft.points) && draft.points.length) {
|
||||||
const pts = draft.points;
|
const pts = draft.points;
|
||||||
const hover = draft.hover;
|
const hover = draft.hover;
|
||||||
if (draft.type === TYPES.wall || draft.type === TYPES.directional_line) {
|
if (isDraftPolyline(draft)) {
|
||||||
const draftCls =
|
const draftZone = { type: draft.type, line_width_cm: draft.line_width_cm };
|
||||||
draft.type === TYPES.directional_line
|
const strokeW = zoneLineWidthPx(draftZone, mapMeta);
|
||||||
? "mapObjDirectionalLine mapObjDirectionalLine--draft"
|
let draftCls = "mapObjWall mapObjWall--draft";
|
||||||
: "mapObjWall mapObjWall--draft";
|
const zoneType = resolveZoneTypeForDraw(draft.type, draft.drawMode || "line");
|
||||||
appendPolylineStyled(svgEl, pts, draftCls, draft.type === TYPES.directional_line ? 8 : null);
|
if (zoneType === TYPES.directional_line || isPlannerZoneType(draft.type)) {
|
||||||
|
draftCls = zonePolygonClass(
|
||||||
|
zoneType === TYPES.directional_line ? TYPES.directional_line : draft.type,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
appendPolylineStyled(svgEl, pts, draftCls, strokeW);
|
||||||
if (hover && pts.length) appendLine(svgEl, pts[pts.length - 1], hover, "mapObjDraftLine");
|
if (hover && pts.length) appendLine(svgEl, pts[pts.length - 1], hover, "mapObjDraftLine");
|
||||||
} else if (isPolygonType(draft.type)) {
|
} else if (isPolygonType(draft.type)) {
|
||||||
if (pts.length >= 3) appendPolygon(svgEl, pts, polygonClass(draft.type, false, true));
|
if (pts.length >= 3) appendPolygon(svgEl, pts, zonePolygonClass(draft.type, false, true));
|
||||||
else if (pts.length === 2) appendPolyline(svgEl, pts, "mapObjDraftLine");
|
else if (pts.length === 2) appendPolyline(svgEl, pts, "mapObjDraftLine");
|
||||||
if (hover && pts.length) appendLine(svgEl, pts[pts.length - 1], hover, "mapObjDraftLine");
|
if (hover && pts.length) appendLine(svgEl, pts[pts.length - 1], hover, "mapObjDraftLine");
|
||||||
appendVertex(svgEl, pts[0], "mapObjCloseHint", 6);
|
appendVertex(svgEl, pts[0], "mapObjCloseHint", 6);
|
||||||
@@ -670,6 +827,19 @@
|
|||||||
createZone,
|
createZone,
|
||||||
createPosition,
|
createPosition,
|
||||||
minPoints,
|
minPoints,
|
||||||
|
minPointsForDraw,
|
||||||
|
supportsDrawLine,
|
||||||
|
supportsDrawShape,
|
||||||
|
defaultDrawTool,
|
||||||
|
defaultLineWidthCm,
|
||||||
|
minLineWidthCm,
|
||||||
|
zoneLineWidthCm,
|
||||||
|
zoneLineWidthPx,
|
||||||
|
typeUsesLineWidthCm,
|
||||||
|
resolveZoneTypeForDraw,
|
||||||
|
isLineGeometry,
|
||||||
|
isDraftPolyline,
|
||||||
|
GEOMETRY_LINE,
|
||||||
isFloorPlanType,
|
isFloorPlanType,
|
||||||
isPlannerZoneType,
|
isPlannerZoneType,
|
||||||
isBehaviorZoneType,
|
isBehaviorZoneType,
|
||||||
|
|||||||
@@ -35,12 +35,13 @@
|
|||||||
* @param {HTMLCanvasElement} outCanvas
|
* @param {HTMLCanvasElement} outCanvas
|
||||||
* @param {object[]} zones
|
* @param {object[]} zones
|
||||||
*/
|
*/
|
||||||
function rebakeComposite(baseCanvas, outCanvas, zones) {
|
function rebakeComposite(baseCanvas, outCanvas, zones, mapMeta = null) {
|
||||||
if (!baseCanvas?.width || !outCanvas) return false;
|
if (!baseCanvas?.width || !outCanvas) return false;
|
||||||
copyCanvas(baseCanvas, outCanvas);
|
copyCanvas(baseCanvas, outCanvas);
|
||||||
const list = Array.isArray(zones) ? zones : [];
|
const list = Array.isArray(zones) ? zones : [];
|
||||||
|
const linePx = (z) => window.MapObjects?.zoneLineWidthPx(z, mapMeta) ?? DEFAULT_LINE_WIDTH;
|
||||||
list.filter((z) => z?.type === "floor").forEach((z) => bakeFloor(outCanvas, z.points));
|
list.filter((z) => z?.type === "floor").forEach((z) => bakeFloor(outCanvas, z.points));
|
||||||
list.filter((z) => z?.type === "wall").forEach((z) => bakeWall(outCanvas, z.points));
|
list.filter((z) => z?.type === "wall").forEach((z) => bakeWall(outCanvas, z.points, linePx(z)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,16 +137,22 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bakeZone(sourceCanvas, zone) {
|
function bakeZone(sourceCanvas, zone, mapMeta = null) {
|
||||||
if (!zone?.points?.length) return false;
|
if (!zone?.points?.length) return false;
|
||||||
if (zone.type === "wall") return bakeWall(sourceCanvas, zone.points);
|
if (zone.type === "wall") {
|
||||||
|
const px = window.MapObjects?.zoneLineWidthPx(zone, mapMeta) ?? DEFAULT_LINE_WIDTH;
|
||||||
|
return bakeWall(sourceCanvas, zone.points, px);
|
||||||
|
}
|
||||||
if (zone.type === "floor") return bakeFloor(sourceCanvas, zone.points);
|
if (zone.type === "floor") return bakeFloor(sourceCanvas, zone.points);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function unbakeZone(sourceCanvas, zone) {
|
function unbakeZone(sourceCanvas, zone, mapMeta = null) {
|
||||||
if (!zone?.points?.length) return false;
|
if (!zone?.points?.length) return false;
|
||||||
if (zone.type === "wall") return unbakeWall(sourceCanvas, zone.points);
|
if (zone.type === "wall") {
|
||||||
|
const px = window.MapObjects?.zoneLineWidthPx(zone, mapMeta) ?? DEFAULT_LINE_WIDTH;
|
||||||
|
return unbakeWall(sourceCanvas, zone.points, px + 2);
|
||||||
|
}
|
||||||
if (zone.type === "floor") {
|
if (zone.type === "floor") {
|
||||||
const ctx = sourceCanvas?.getContext("2d");
|
const ctx = sourceCanvas?.getContext("2d");
|
||||||
if (!ctx) return false;
|
if (!ctx) return false;
|
||||||
|
|||||||
@@ -33,6 +33,31 @@
|
|||||||
return inside;
|
return inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function distPointToSegment(px, py, x1, y1, x2, y2) {
|
||||||
|
const dx = x2 - x1;
|
||||||
|
const dy = y2 - y1;
|
||||||
|
const lenSq = dx * dx + dy * dy;
|
||||||
|
if (lenSq === 0) return Math.hypot(px - x1, py - y1);
|
||||||
|
let t = ((px - x1) * dx + (py - y1) * dy) / lenSq;
|
||||||
|
t = Math.max(0, Math.min(1, t));
|
||||||
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointNearPolyline(px, py, points, halfWidth, tolerance = 4) {
|
||||||
|
if (!points?.length || points.length < 2) return false;
|
||||||
|
const limit = halfWidth + tolerance;
|
||||||
|
for (let j = 0; j < points.length - 1; j++) {
|
||||||
|
const p1 = points[j];
|
||||||
|
const p2 = points[j + 1];
|
||||||
|
if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= limit) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLineGeometry(z) {
|
||||||
|
return z?.geometry === "line";
|
||||||
|
}
|
||||||
|
|
||||||
function isPlannerZoneType(type) {
|
function isPlannerZoneType(type) {
|
||||||
return PLANNER_TYPES.has(type);
|
return PLANNER_TYPES.has(type);
|
||||||
}
|
}
|
||||||
@@ -42,46 +67,50 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Topmost planner zones containing image pixel (newest wins for overlaps). */
|
/** Topmost planner zones containing image pixel (newest wins for overlaps). */
|
||||||
function zonesAtPoint(zones, px, py) {
|
function zonesAtPoint(zones, px, py, mapMeta = null) {
|
||||||
const list = Array.isArray(zones) ? zones : [];
|
const list = Array.isArray(zones) ? zones : [];
|
||||||
const hits = [];
|
const hits = [];
|
||||||
|
const linePx = (z) => window.MapObjects?.zoneLineWidthPx(z, mapMeta) ?? 8;
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
for (let i = list.length - 1; i >= 0; i--) {
|
||||||
const z = list[i];
|
const z = list[i];
|
||||||
if (!isPlannerZoneType(z?.type) || !z.points?.length) continue;
|
if (!isPlannerZoneType(z?.type) || !z.points?.length) continue;
|
||||||
if (pointInPolygon(px, py, z.points)) hits.push(z);
|
if (isLineGeometry(z)) {
|
||||||
|
const half = linePx(z) / 2;
|
||||||
|
if (pointNearPolyline(px, py, z.points, half)) hits.push(z);
|
||||||
|
} else if (pointInPolygon(px, py, z.points)) hits.push(z);
|
||||||
}
|
}
|
||||||
return hits;
|
return hits;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pixelCost(zones, px, py) {
|
function pixelCost(zones, px, py, mapMeta = null) {
|
||||||
const hits = zonesAtPoint(zones, px, py);
|
const hits = zonesAtPoint(zones, px, py, mapMeta);
|
||||||
if (hits.some((z) => z.type === TYPES.forbidden)) return COST.forbidden;
|
if (hits.some((z) => z.type === TYPES.forbidden)) return COST.forbidden;
|
||||||
if (hits.some((z) => z.type === TYPES.unpreferred)) return COST.unpreferred;
|
if (hits.some((z) => z.type === TYPES.unpreferred)) return COST.unpreferred;
|
||||||
if (hits.some((z) => z.type === TYPES.preferred)) return COST.preferred;
|
if (hits.some((z) => z.type === TYPES.preferred)) return COST.preferred;
|
||||||
return COST.neutral;
|
return COST.neutral;
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyPoint(zones, px, py) {
|
function classifyPoint(zones, px, py, mapMeta = null) {
|
||||||
const hits = zonesAtPoint(zones, px, py);
|
const hits = zonesAtPoint(zones, px, py, mapMeta);
|
||||||
return {
|
return {
|
||||||
forbidden: hits.some((z) => z.type === TYPES.forbidden),
|
forbidden: hits.some((z) => z.type === TYPES.forbidden),
|
||||||
preferred: hits.some((z) => z.type === TYPES.preferred),
|
preferred: hits.some((z) => z.type === TYPES.preferred),
|
||||||
unpreferred: hits.some((z) => z.type === TYPES.unpreferred),
|
unpreferred: hits.some((z) => z.type === TYPES.unpreferred),
|
||||||
zones: hits,
|
zones: hits,
|
||||||
cost: pixelCost(zones, px, py),
|
cost: pixelCost(zones, px, py, mapMeta),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPathBlocked(zones, pathPoints) {
|
function isPathBlocked(zones, pathPoints, mapMeta = null) {
|
||||||
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
||||||
return pts.some((p) => pixelCost(zones, p.x, p.y) === COST.forbidden);
|
return pts.some((p) => pixelCost(zones, p.x, p.y, mapMeta) === COST.forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pathCost(zones, pathPoints) {
|
function pathCost(zones, pathPoints, mapMeta = null) {
|
||||||
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (const p of pts) {
|
for (const p of pts) {
|
||||||
const c = pixelCost(zones, p.x, p.y);
|
const c = pixelCost(zones, p.x, p.y, mapMeta);
|
||||||
if (!Number.isFinite(c)) return Infinity;
|
if (!Number.isFinite(c)) return Infinity;
|
||||||
sum += c;
|
sum += c;
|
||||||
}
|
}
|
||||||
|
|||||||
206
www/style.css
206
www/style.css
@@ -4280,21 +4280,16 @@ body.auth-readonly-maps-page .mapsMirMapMenuCancelBtn {
|
|||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjFloor {
|
.mapEditorObjectsSvg .mapObjFloor {
|
||||||
fill: rgba(255, 255, 255, 0.82);
|
fill: rgba(255, 255, 255, 0.82);
|
||||||
stroke: #888;
|
stroke: none;
|
||||||
stroke-width: 1.5;
|
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjFloor--selected {
|
.mapEditorObjectsSvg .mapObjFloor--selected {
|
||||||
fill: rgba(230, 126, 34, 0.25);
|
fill: rgba(230, 126, 34, 0.25);
|
||||||
stroke: #e67e22;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjFloor--draft {
|
.mapEditorObjectsSvg .mapObjFloor--draft {
|
||||||
fill: rgba(52, 152, 219, 0.2);
|
fill: rgba(52, 152, 219, 0.2);
|
||||||
stroke: #3498db;
|
|
||||||
stroke-dasharray: 6 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDraftLine {
|
.mapEditorObjectsSvg .mapObjDraftLine {
|
||||||
@@ -4317,134 +4312,158 @@ body.auth-readonly-maps-page .mapsMirMapMenuCancelBtn {
|
|||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Zone colours: polygon = fill only, polyline = stroke (same colour). */
|
||||||
.mapEditorObjectsSvg .mapObjForbidden {
|
.mapEditorObjectsSvg .mapObjForbidden {
|
||||||
fill: rgba(231, 76, 60, 0.28);
|
--map-obj-color: rgba(231, 76, 60, 0.28);
|
||||||
stroke: #c0392b;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjForbidden {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjForbidden {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjForbidden--selected {
|
.mapEditorObjectsSvg .mapObjForbidden--selected {
|
||||||
fill: rgba(231, 76, 60, 0.42);
|
--map-obj-color: rgba(231, 76, 60, 0.42);
|
||||||
stroke: #e74c3c;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjForbidden--draft {
|
.mapEditorObjectsSvg .mapObjForbidden--draft {
|
||||||
fill: rgba(231, 76, 60, 0.18);
|
--map-obj-color: rgba(231, 76, 60, 0.18);
|
||||||
stroke: #e74c3c;
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjForbidden--draft {
|
||||||
stroke-dasharray: 6 4;
|
stroke-dasharray: 6 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPreferred {
|
.mapEditorObjectsSvg .mapObjPreferred {
|
||||||
fill: rgba(92, 184, 92, 0.28);
|
--map-obj-color: rgba(92, 184, 92, 0.28);
|
||||||
stroke: #27ae60;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjPreferred {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjPreferred {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPreferred--selected {
|
.mapEditorObjectsSvg .mapObjPreferred--selected {
|
||||||
fill: rgba(92, 184, 92, 0.42);
|
--map-obj-color: rgba(92, 184, 92, 0.42);
|
||||||
stroke: #2ecc71;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPreferred--draft {
|
.mapEditorObjectsSvg .mapObjPreferred--draft {
|
||||||
fill: rgba(92, 184, 92, 0.18);
|
--map-obj-color: rgba(92, 184, 92, 0.18);
|
||||||
stroke: #2ecc71;
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjPreferred--draft {
|
||||||
stroke-dasharray: 6 4;
|
stroke-dasharray: 6 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjUnpreferred {
|
.mapEditorObjectsSvg .mapObjUnpreferred {
|
||||||
fill: rgba(155, 89, 182, 0.24);
|
--map-obj-color: rgba(155, 89, 182, 0.24);
|
||||||
stroke: #8e44ad;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjUnpreferred {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjUnpreferred {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjUnpreferred--selected {
|
.mapEditorObjectsSvg .mapObjUnpreferred--selected {
|
||||||
fill: rgba(155, 89, 182, 0.38);
|
--map-obj-color: rgba(155, 89, 182, 0.38);
|
||||||
stroke: #9b59b6;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjUnpreferred--draft {
|
.mapEditorObjectsSvg .mapObjUnpreferred--draft {
|
||||||
fill: rgba(155, 89, 182, 0.16);
|
--map-obj-color: rgba(155, 89, 182, 0.16);
|
||||||
stroke: #9b59b6;
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjUnpreferred--draft {
|
||||||
stroke-dasharray: 6 4;
|
stroke-dasharray: 6 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSpeed {
|
.mapEditorObjectsSvg .mapObjSpeed {
|
||||||
fill: rgba(243, 156, 18, 0.28);
|
--map-obj-color: rgba(243, 156, 18, 0.28);
|
||||||
stroke: #e67e22;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjSpeed {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSpeed--selected {
|
.mapEditorObjectsSvg .mapObjSpeed--selected {
|
||||||
fill: rgba(243, 156, 18, 0.42);
|
--map-obj-color: rgba(243, 156, 18, 0.42);
|
||||||
stroke: #f39c12;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSpeed--draft {
|
.mapEditorObjectsSvg .mapObjSpeed--draft {
|
||||||
fill: rgba(243, 156, 18, 0.18);
|
--map-obj-color: rgba(243, 156, 18, 0.18);
|
||||||
stroke: #f39c12;
|
|
||||||
stroke-dasharray: 6 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSound {
|
.mapEditorObjectsSvg .mapObjSound {
|
||||||
fill: rgba(52, 152, 219, 0.24);
|
--map-obj-color: rgba(52, 152, 219, 0.24);
|
||||||
stroke: #2980b9;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjSound {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSound--selected {
|
.mapEditorObjectsSvg .mapObjSound--selected {
|
||||||
fill: rgba(52, 152, 219, 0.38);
|
--map-obj-color: rgba(52, 152, 219, 0.38);
|
||||||
stroke: #3498db;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjSound--draft {
|
.mapEditorObjectsSvg .mapObjSound--draft {
|
||||||
fill: rgba(52, 152, 219, 0.16);
|
--map-obj-color: rgba(52, 152, 219, 0.16);
|
||||||
stroke: #3498db;
|
|
||||||
stroke-dasharray: 6 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectional {
|
.mapEditorObjectsSvg .mapObjDirectional {
|
||||||
fill: rgba(26, 188, 156, 0.22);
|
--map-obj-color: rgba(26, 188, 156, 0.22);
|
||||||
stroke: #16a085;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjDirectional {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjDirectional {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectional--selected {
|
.mapEditorObjectsSvg .mapObjDirectional--selected {
|
||||||
fill: rgba(26, 188, 156, 0.36);
|
--map-obj-color: rgba(26, 188, 156, 0.36);
|
||||||
stroke: #1abc9c;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectional--draft {
|
.mapEditorObjectsSvg .mapObjDirectional--draft {
|
||||||
fill: rgba(26, 188, 156, 0.14);
|
--map-obj-color: rgba(26, 188, 156, 0.14);
|
||||||
stroke: #1abc9c;
|
|
||||||
stroke-dasharray: 6 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectionalLine {
|
.mapEditorObjectsSvg polyline.mapObjDirectional--draft {
|
||||||
fill: none;
|
|
||||||
stroke: #16a085;
|
|
||||||
stroke-linecap: round;
|
|
||||||
stroke-linejoin: round;
|
|
||||||
vector-effect: non-scaling-stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectionalLine--selected {
|
|
||||||
stroke: #1abc9c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjDirectionalLine--draft {
|
|
||||||
stroke: #48c9b0;
|
|
||||||
stroke-dasharray: 6 4;
|
stroke-dasharray: 6 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4473,42 +4492,51 @@ body.auth-readonly-maps-page .mapsMirMapMenuCancelBtn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPlannerSettings {
|
.mapEditorObjectsSvg .mapObjPlannerSettings {
|
||||||
fill: rgba(127, 140, 141, 0.22);
|
--map-obj-color: rgba(127, 140, 141, 0.22);
|
||||||
stroke: #7f8c8d;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
stroke-dasharray: 5 3;
|
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjPlannerSettings {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjPlannerSettings {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPlannerSettings--selected {
|
.mapEditorObjectsSvg .mapObjPlannerSettings--selected {
|
||||||
fill: rgba(127, 140, 141, 0.34);
|
--map-obj-color: rgba(127, 140, 141, 0.34);
|
||||||
stroke: #95a5a6;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjPlannerSettings--draft {
|
.mapEditorObjectsSvg .mapObjPlannerSettings--draft {
|
||||||
fill: rgba(127, 140, 141, 0.14);
|
--map-obj-color: rgba(127, 140, 141, 0.14);
|
||||||
stroke: #95a5a6;
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polyline.mapObjPlannerSettings--draft {
|
||||||
stroke-dasharray: 6 4;
|
stroke-dasharray: 6 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjIo {
|
.mapEditorObjectsSvg .mapObjIo {
|
||||||
fill: rgba(86, 101, 115, 0.24);
|
--map-obj-color: rgba(86, 101, 115, 0.24);
|
||||||
stroke: #566573;
|
fill: var(--map-obj-color);
|
||||||
stroke-width: 2;
|
stroke: var(--map-obj-color);
|
||||||
vector-effect: non-scaling-stroke;
|
vector-effect: non-scaling-stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapEditorObjectsSvg polygon.mapObjIo {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjIo--selected {
|
.mapEditorObjectsSvg .mapObjIo--selected {
|
||||||
fill: rgba(86, 101, 115, 0.38);
|
--map-obj-color: rgba(86, 101, 115, 0.38);
|
||||||
stroke: #5d6d7e;
|
|
||||||
stroke-width: 2.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjIo--draft {
|
.mapEditorObjectsSvg .mapObjIo--draft {
|
||||||
fill: rgba(86, 101, 115, 0.16);
|
--map-obj-color: rgba(86, 101, 115, 0.16);
|
||||||
stroke: #5d6d7e;
|
|
||||||
stroke-dasharray: 6 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapEditorObjectsSvg .mapObjVertex--handle {
|
.mapEditorObjectsSvg .mapObjVertex--handle {
|
||||||
|
|||||||
Reference in New Issue
Block a user