- 까닭: syncIrregularSelection 이 일반 측점이면 폼을 안 건드리고 끝남(옛 폼 남음 · 덮는 옹벽 안 열림) · 비정규 측점도 관 전용 selectPipeByChainage 만 불러 A군 수동 구조물 안 열림 - 측점 고름 포트 openAtStation = 폼 selectAt(resolveSelection) + coversAtStation(노면공 뺌) — B06 showAtChainage 와 한 규칙 - 고름 한 칸은 폼 동기화 뒤에 그 측점으로(폼 알림이 구조물 id 를 덮어 적지 않게 · 188-1) - coversAtStation 을 design_selection 한 곳에 두고 B06 coverable 도 그것으로 - 새 시험 test_216_1_b05_station_panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
194 lines
7.4 KiB
JavaScript
194 lines
7.4 KiB
JavaScript
/* PLAN 216-1 단위검증 헬퍼 — B05 종단 측점선 고름 → 좌측 구조물 폼이 B06 카드 고름과 같게.
|
|
* 화면 TS(`B05_Profile_UI_Selection` · `design_selection`)를 그 자리에서 트랜스파일해 Node 로 돌림 ·
|
|
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_216_1_b05_station_panel.py`.
|
|
* B05 포트 배선은 `design_view_profile.ts` 와 같은 꼴 · B06 은 `showAtChainage` 와 같은 꼴(resolveSelection + coverable).
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const cache = new Map();
|
|
|
|
const session = new Map();
|
|
const fakeStorage = {
|
|
getItem: (k) => session.get(k) ?? null,
|
|
setItem: (k, v) => session.set(k, String(v)),
|
|
removeItem: (k) => session.delete(k),
|
|
};
|
|
globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage };
|
|
globalThis.sessionStorage = fakeStorage;
|
|
globalThis.localStorage = fakeStorage;
|
|
|
|
// 비정규 측점 모듈은 설정 별칭(@config)을 끌어오므로 쓰는 두 함수만 같은 꼴로 흉내
|
|
const STUBS = {
|
|
"./B05_Profile_UI_IrregularStations": {
|
|
irregularStationId: (id) => `irregular:${id}`,
|
|
isPipeStation: (s) => s.origin === "pipe",
|
|
},
|
|
};
|
|
|
|
function loadTs(file) {
|
|
const full = path.resolve(file);
|
|
if (cache.has(full)) return cache.get(full);
|
|
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
cache.set(full, box.exports);
|
|
const localRequire = (request) =>
|
|
STUBS[request] ??
|
|
(request.startsWith(".")
|
|
? loadTs(path.join(path.dirname(full), `${request}.ts`))
|
|
: require(request));
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
cache.set(full, box.exports);
|
|
return box.exports;
|
|
}
|
|
|
|
const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
|
|
const sync = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Selection.ts"));
|
|
|
|
const P = "proj-216";
|
|
// 측점 = 일반(s0~s200) + 관 투영(irregular:p1 · 33.4) + A군 수동 횡단수로(irregular:x1 · 140)
|
|
const cards = [0, 20, 40, 60, 80, 100, 120, 160, 180, 200].map((c) => ({
|
|
station_id: `s${c}`,
|
|
chainage_m: c,
|
|
}));
|
|
const irregular = [
|
|
{ id: "p1", chainage_m: 33.4, origin: "pipe", structure: "배관" },
|
|
{ id: "x1", chainage_m: 140, structure: "노출형 횡단수로" },
|
|
];
|
|
const pipes = [{ chainage_m: 33.4 }];
|
|
const types = [
|
|
{ type_id: "retaining_wall", group: "C" },
|
|
{ type_id: "cross_channel", group: "A" },
|
|
{ type_id: "pavement", group: "G" },
|
|
];
|
|
const structures = [
|
|
{ structure_id: "wall", type_id: "retaining_wall", start_m: 60, end_m: 80, chainage_m: 60 },
|
|
{ structure_id: "chan", type_id: "cross_channel", chainage_m: 140 },
|
|
{ structure_id: "pave", type_id: "pavement", start_m: 150, end_m: 190, chainage_m: 150 },
|
|
];
|
|
const groupOf = (typeId) => types.find((t) => t.type_id === typeId)?.group;
|
|
|
|
/** 좌측 폼 흉내 — B05 `_Structures_Panel` 의 selectPipeByChainage · selectById · selectAt 꼴 그대로. */
|
|
function makeForm(onSelect) {
|
|
const form = { pipe: null, structure: null };
|
|
const panel = {
|
|
form,
|
|
selectPipeByChainage(c) {
|
|
if (c === null) {
|
|
form.pipe = null;
|
|
return;
|
|
}
|
|
const hit = pipes.find((p) => Math.abs(p.chainage_m - c) < sel.PIPE_MATCH_M);
|
|
if (!hit) return;
|
|
form.pipe = hit.chainage_m;
|
|
form.structure = null;
|
|
onSelect?.(null);
|
|
},
|
|
selectById(id) {
|
|
if (id === null) {
|
|
form.pipe = null;
|
|
form.structure = null;
|
|
return;
|
|
}
|
|
form.pipe = null;
|
|
form.structure = id;
|
|
onSelect?.(structures.find((s) => s.structure_id === id) ?? null);
|
|
},
|
|
selectAt(selection, coverable) {
|
|
const hit = sel.resolveSelection(selection, pipes, structures, coverable);
|
|
if (hit.pipeChainageM !== null) panel.selectPipeByChainage(hit.pipeChainageM);
|
|
else panel.selectById(hit.structureId);
|
|
return hit;
|
|
},
|
|
};
|
|
return panel;
|
|
}
|
|
const formOf = (panel) =>
|
|
panel.form.pipe !== null ? `pipe-${panel.form.pipe.toFixed(2)}` : panel.form.structure;
|
|
|
|
// ── B05 종단 보기 — design_view_profile 의 포트 배선 그대로 ─────────────────────
|
|
let syncing = false;
|
|
const rememberPick = (c, sid) =>
|
|
sel.selectDesign(P, c === null ? sel.NO_SELECTION : { chainageM: c, structureId: sid }, {
|
|
from: "b05",
|
|
});
|
|
// 폼이 구조물을 열면 view 의 onStructureSelect 가 그 구조물을 고름 한 칸에 적음(동기화 중이면 그 뒤 return)
|
|
const b05Panel = makeForm((structure) => {
|
|
if (structure) rememberPick(structure.chainage_m, structure.structure_id);
|
|
});
|
|
const sb = sync.createSelectionSync({
|
|
stations: () => irregular,
|
|
isSyncing: () => syncing,
|
|
setSyncing: (v) => {
|
|
syncing = v;
|
|
},
|
|
selectMarker: () => undefined,
|
|
selectGraph: () => undefined,
|
|
selectSidebar: (c) => b05Panel.selectPipeByChainage(c),
|
|
openAtStation: (c) =>
|
|
b05Panel.selectAt({ chainageM: c, structureId: null }, (entry) =>
|
|
sel.coversAtStation(groupOf(entry.type_id)),
|
|
),
|
|
selectBasin: () => undefined,
|
|
regularChainage: (id) => cards.find((c) => c.station_id === id)?.chainage_m,
|
|
remember: (c) => rememberPick(c, null),
|
|
});
|
|
|
|
// ── B06 카드 고름 — showAtChainage 와 같은 꼴(카드 = 관 투영은 가까운 카드) ──────────
|
|
const b06Panel = makeForm();
|
|
const b06Coverable = (entry) => sel.coversAtStation(groupOf(entry.type_id));
|
|
const b06Card = (chainageM) => {
|
|
const hit = sel.resolveSelection(
|
|
{ chainageM, structureId: null },
|
|
pipes,
|
|
structures,
|
|
b06Coverable,
|
|
);
|
|
if (hit.pipeChainageM !== null) b06Panel.selectPipeByChainage(hit.pipeChainageM);
|
|
else b06Panel.selectById(hit.structureId);
|
|
};
|
|
|
|
const chainageOf = (id) =>
|
|
irregular.find((s) => `irregular:${s.id}` === id)?.chainage_m ??
|
|
cards.find((c) => c.station_id === id)?.chainage_m;
|
|
|
|
/** 차례대로 고름 — 매 걸음 B05 폼 · B06 폼 · 고름 한 칸. */
|
|
function run(ids) {
|
|
b05Panel.form.pipe = b05Panel.form.structure = null;
|
|
b06Panel.form.pipe = b06Panel.form.structure = null;
|
|
sel.selectDesign(P, sel.NO_SELECTION);
|
|
return ids.map((id) => {
|
|
sb.syncIrregularSelection(id);
|
|
const at = id === null ? null : chainageOf(id);
|
|
if (at !== undefined) b06Card(at); // 모르는 측점은 B06 카드가 없음
|
|
return { id, b05: formOf(b05Panel), b06: formOf(b06Panel), pick: sel.readSelection(P) };
|
|
});
|
|
}
|
|
|
|
const out = {
|
|
pipe: run(["irregular:p1"]),
|
|
// 관 폼이 열린 채 일반 측점 = 폼 비움(새 배치 준비) — 옛 B05 는 관 폼이 남았음
|
|
pipeThenPlain: run(["irregular:p1", "s100"]),
|
|
// 옹벽 범위 안 일반 측점 = 옹벽 폼 · 고름 한 칸은 그 측점(구조물 id 없음)
|
|
wallStation: run(["s20", "s60"]),
|
|
wallMiddle: run(["s80"]),
|
|
// A군 수동 횡단수로 측점 = 그 구조물 폼
|
|
channel: run(["irregular:x1"]),
|
|
// 노면공만 덮는 측점 = 노면공 안 엶(189-1 · 152-5) — 연 폼은 풂
|
|
pave: run(["s60", "s160"]),
|
|
// 같은 측점 다시 = 풂(null)
|
|
release: run(["s60", null]),
|
|
// 모르는 측점 = 폼 · 고름 한 칸 안 건드림
|
|
unknown: run(["s60", "ghost"]),
|
|
covers: {
|
|
G: sel.coversAtStation("G"),
|
|
C: sel.coversAtStation("C"),
|
|
none: sel.coversAtStation(undefined),
|
|
},
|
|
};
|
|
process.stdout.write(JSON.stringify(out));
|