Files
Aislo/resources/tester/helper_513_1_plan_structure_menu.cjs
T

130 lines
4.6 KiB
JavaScript

/* 513-1 B05 평면 우클릭 구조물 메뉴(요구 327-1) — `_Profile_Structures` · `_RouteEdit_StructureMenu` 를 Node 로
* 돌려 JSON 한 줄(`test_513_1_plan_structure_menu.py` 가 판정). 메뉴 판 · 측점 판정은 가짜 · 항목 짓기 · 노선 셈은 진짜.
* node helper_513_1_plan_structure_menu.cjs
*/
const path = require("path");
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
const B05 = path.join(__dirname, "..", "..", "B05_Profile");
const opens = [];
const calls = [];
/** 가짜 메뉴 — 연 자리 · 항목만 적음. */
const ui = () => ({
createMapContextMenu: () => ({
element: { parentElement: null },
open: (x, y, items) => opens.push({ x, y, items }),
close() {},
contains: () => false,
}),
});
const STUBS = {
"../A00_Common/touch_bridge": { pickPx: (v) => v },
"./B05_Profile_UI_IrregularStations": {
irregularStationId: (id) => `irr:${id}`,
isPipeStation: (station) => station.origin === "pipe",
},
"./B05_Profile_Api_Structures": { structureAnchorM: (s) => s.chainage_m ?? 0 },
"../A00_Common/design/design_selection": { PIPE_MATCH_M: 0.51 },
};
const { loadTs } = makeLoader({ root: path.join(__dirname, "..", ".."), stubs: STUBS, ui });
const shared = loadTs(path.join(B05, "B05_Profile_UI_Profile_Structures.ts"));
const plan = loadTs(path.join(B05, "B05_Profile_UI_RouteEdit_StructureMenu.ts"));
/** 듣기만 적는 가짜 요소. */
function fakeElement(connected = true) {
const listeners = {};
return {
isConnected: connected,
listeners,
addEventListener: (type, fn) => (listeners[type] = listeners[type] || []).push(fn),
getBoundingClientRect: () => ({ left: 0, top: 0 }),
append(child) {
child.parentElement = this;
},
parentElement: null,
fire(type, clientX, clientY) {
let prevented = false;
(listeners[type] || []).forEach((fn) =>
fn({ clientX, clientY, target: null, preventDefault: () => (prevented = true) }),
);
return prevented;
},
};
}
// 노선 0 → 100 m 직선 · 화면 2 px/m · 배관 측점 40 m · 추가 측점 70 m
const PLUS = loadTs(
path.join(__dirname, "..", "..", "common_util", "common_util_structure_station.ts"),
).PLUS_OWNER_PREFIX;
const stations = [
{ id: "pipe-1", chainage_m: 40, origin: "pipe" },
{ id: `${PLUS}x`, chainage_m: 70 },
];
const options = {
stations,
x: (m) => m * 2,
chainageAt: (px) => px / 2,
maxChainageM: 100,
onRemove: (station) => calls.push(["remove", station.id]),
onRemovePlus: (m) => calls.push(["removePlus", m]),
onAddPipe: (m) => calls.push(["addPipe", m]),
structureGroups: () => [{ label: "A군", types: [{ type: { name: "배수관", type_id: "pipe" } }] }],
onAddStructureType: (m, typeId) => calls.push(["addType", m, typeId]),
};
const labels = (items) =>
items.map((item) =>
Array.isArray(item) ? item[0] : [item.label, item.children.map((c) => c[0])],
);
const pickFirst = (items) => {
const first = items[0];
(Array.isArray(first) ? first[1] : first.children[0][1])();
};
const out = {};
out.liveBefore = shared.liveStructureMenu() === null;
// 종단 그래프 — 빈 자리 · 배관 위
const host = fakeElement();
shared.mountStructureMenu(host, options);
host.fire("contextmenu", 120, 5); // 60 m
out.profileEmpty = labels(opens.at(-1).items);
pickFirst(opens.at(-1).items);
host.fire("contextmenu", 81, 5); // 40.5 m — 배관
out.profilePipe = labels(opens.at(-1).items);
// 평면 — 같은 재료
const wrap = fakeElement();
const canvas = fakeElement();
canvas.parentElement = wrap;
const line = [
[0, 0],
[100, 0],
];
const params = {
canvas,
toScreen: (v) => [v[0] * 2, v[1] * 2],
unrotate: (x, y) => [x, y],
state: () => ({ plannedLine: line, planned: line }),
};
const nodeAt = (px, py) => (Math.hypot(px, py) <= 6 ? 0 : Math.hypot(px - 200, py) <= 6 ? 1 : -1);
plan.bindPlanStructureMenu(params, nodeAt);
const planAt = (x, y) => {
const before = opens.length;
canvas.fire("contextmenu", x, y);
return opens.length > before ? labels(opens.at(-1).items) : null;
};
out.planEmpty = planAt(120, 8); // 60 m · 노선에서 8 px
pickFirst(opens.at(-1).items);
out.planPipe = planAt(82, 6); // 40 m 배관 근처
pickFirst(opens.at(-1).items);
out.planPlus = planAt(141, -4); // 70 m 추가 측점
out.planMenuAt = [opens.at(-1).x, opens.at(-1).y]; // 누른 화면 자리(캔버스 그릇 기준)
pickFirst(opens.at(-1).items);
out.planFar = planAt(120, 40); // 노선에서 40 px
out.planNode = planAt(2, 1); // 시점 노드 위 = 노드 삭제 몫
host.isConnected = false; // 종단 그래프가 떠남
out.planGone = planAt(120, 8);
out.calls = calls;
process.stdout.write(JSON.stringify(out));