- 목록 · 종단 알약 · 3D · 횡단 카드 · 벽 어디서 골라도 selectDesign 한 길로 세션에 적음(구조물 id 포함) — B05 목록 구조물 고름도 B06 으로 이어짐 - 폼이 열 항목 풀이 resolveSelection 한 곳(관 먼저 · 구조물 다음 · 가장 가까운 것) — 공용 폼의 0.05 없앰 - B06 벽 · 구체 누름은 showAtChainage 하나를 탐(옛 관 전용 showPipeAt 없앰) · 벽 누름 중 폼이 카드를 먼저 고르는 이중 토글 막음 - B06 진입 때 구조물 id 로 카드를 찾고, 못 찾으면 다시 부르게 함 · B05 진입 때 구조물 id 가 있을 때만 구조물로 연다 - 시험 test_b05_b06_selection_single 21 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
179 lines
7.6 KiB
JavaScript
179 lines
7.6 KiB
JavaScript
/* 측점 · 구조물 고름 한 곳 단위검증 헬퍼(PLAN 24-2d) — TS 를 그 자리에서 트랜스파일해 Node 로 돌린다.
|
|
* 결과는 표준출력에 JSON 한 벌. 화면 그릇은 쓰지 않는다(세션 저장소와 즉시 실행 프레임만 흉내).
|
|
*
|
|
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_structures_form_summary.cjs` 머리와 같다.
|
|
*/
|
|
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;
|
|
globalThis.requestAnimationFrame = (fn) => fn();
|
|
|
|
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) =>
|
|
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 pickPage = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Structure_Pick.ts"));
|
|
const pickSession = loadTs(
|
|
path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structure_Pick_Session.ts"),
|
|
);
|
|
|
|
const out = { matchM: sel.PIPE_MATCH_M };
|
|
const P = "proj-1";
|
|
|
|
// ── 풀기: 관 → 구조물 순 · 한 거리 ───────────────────────────────────────
|
|
// 옹벽(구간 200~210) · 독립 기슭막이(구간 80~90) · BOX/세월교/배수관은 관 정본
|
|
const structures = [
|
|
{ structure_id: "wall", placement: "interval", start_m: 200, end_m: 210, chainage_m: 205 },
|
|
{ structure_id: "revet", placement: "interval", start_m: 80, end_m: 90, chainage_m: 85 },
|
|
];
|
|
const pipes = [{ chainage_m: 173.09 }, { chainage_m: 205.2 }, { chainage_m: 300 }];
|
|
const at = (chainageM, structureId = null) =>
|
|
sel.resolveSelection({ chainageM, structureId }, pipes, structures);
|
|
out.card = {
|
|
// 횡단 카드 측점이 관 측점에서 0.05 ~ 0.51 m 어긋나도 그 관을 찾는다(옛 B05 폼은 0.05 라 놓쳤다)
|
|
pipeNear: at(173.4),
|
|
pipeFar: at(173.09 + 0.6),
|
|
wallStart: at(200),
|
|
wallMiddle: at(207),
|
|
wallEdge: at(210.4),
|
|
revet: at(85),
|
|
none: at(250),
|
|
cleared: at(null),
|
|
// 같은 측점에 관과 구조물이 겹치면 관이 먼저(횡단도에 그려지는 것)
|
|
pipeBeatsStructure: at(205.0),
|
|
// 구조물 id 가 주어지고 목록에 있으면 측점보다 id 가 이긴다 · 없는 id 는 측점으로 되돌아간다
|
|
byId: at(300, "revet"),
|
|
staleId: at(200, "gone"),
|
|
};
|
|
|
|
// ── 세션 값 + 알림 ─────────────────────────────────────────────────────
|
|
const heard = [];
|
|
const off = sel.subscribeSelection(P, (s, from) => heard.push({ ...s, from }));
|
|
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "own", from: "b06" });
|
|
out.store1 = sel.readSelection(P);
|
|
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { from: "b06" });
|
|
out.sameQuiet = heard.length; // 같은 값 · 부재키 생략 → 알리지 않음
|
|
sel.selectDesign(P, { chainageM: 173.09, structureId: null }, { from: "b05" });
|
|
out.keyKept = sel.readSelection(P)?.partKey; // 부재키를 안 주면 지킨다(옛 규칙)
|
|
sel.selectDesign(P, sel.NO_SELECTION, { from: "b05" });
|
|
out.cleared = sel.readSelection(P);
|
|
out.heardFrom = heard.map((h) => [h.chainageM, h.structureId, h.from]);
|
|
off();
|
|
sel.selectDesign(P, { chainageM: 1, structureId: null });
|
|
out.afterOff = heard.length;
|
|
out.noProject = sel.readSelection(null);
|
|
// 알림 도중 다시 낸 값은 적기만 한다(되울림 방지)
|
|
let echoes = 0;
|
|
const off2 = sel.subscribeSelection(P, () => {
|
|
echoes += 1;
|
|
sel.selectDesign(P, { chainageM: 2, structureId: null });
|
|
});
|
|
sel.selectDesign(P, { chainageM: 3, structureId: null });
|
|
out.echoes = echoes;
|
|
out.lastWritten = sel.readSelection(P)?.chainageM;
|
|
off2();
|
|
sel.selectDesign(P, sel.NO_SELECTION);
|
|
|
|
// ── 넘김값 읽기 ────────────────────────────────────────────────────────
|
|
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "right" });
|
|
out.handoff = pickSession.readStructurePick(P);
|
|
sel.selectDesign(P, sel.NO_SELECTION);
|
|
out.handoffNone = pickSession.readStructurePick(P);
|
|
|
|
// ── B06 진입 때 카드 찾기 ──────────────────────────────────────────────
|
|
const cards = [80, 173.09, 200, 205.2, 220].map((c) => ({
|
|
chainage_m: c,
|
|
station_id: `s${c}`,
|
|
ford: null,
|
|
box: null,
|
|
}));
|
|
const detail = { cross_sections: cards };
|
|
const run = (handoff, list) => {
|
|
const calls = [];
|
|
calls.applied = pickPage.applyStructurePick(
|
|
handoff,
|
|
detail,
|
|
{
|
|
focusStation: (id) => calls.push(["focus", id]),
|
|
refreshCard: (c) => calls.push(["refresh", c]),
|
|
revetSelect: (c, k) => calls.push(["revet", c, k]),
|
|
fordSelect: (c, k) => calls.push(["ford", c, k]),
|
|
boxSelect: (c, k) => calls.push(["box", c, k]),
|
|
},
|
|
list,
|
|
);
|
|
return calls;
|
|
};
|
|
const midWall = { at: 207, structureId: "wall" }; // 구간 한가운데 — 카드 위치가 아님
|
|
out.retry = {
|
|
// 구조물 목록이 아직 안 실렸으면 카드를 못 찾고 false → 부르는 쪽이 나중에 다시 부른다
|
|
before: run({ at: 260 }, []).applied,
|
|
// 목록이 실린 뒤 같은 넘김값은 카드를 찾는다
|
|
after: run(midWall, structures).applied,
|
|
notFound: run({ at: 260 }, structures).applied,
|
|
emptyHandoff: run(null, structures).applied,
|
|
};
|
|
out.apply = {
|
|
pipeOffsetCard: run({ at: 173.4 }, structures),
|
|
wallByAnchor: run({ at: 205, structureId: "wall" }, structures),
|
|
wallNoId: run({ at: 207 }, structures),
|
|
revetCardFirst: run({ at: 85, structureId: "revet", key: "own" }, structures),
|
|
unknown: run({ at: 260 }, structures),
|
|
none: run(null, structures),
|
|
};
|
|
|
|
// ── B05 진입 때 되살리기 — 구조물 id 가 있을 때만 구조물로 연다 ─────────────
|
|
const restoreRun = (pickValue) => {
|
|
sel.selectDesign(P, sel.NO_SELECTION);
|
|
if (pickValue) sel.selectDesign(P, pickValue.selection, { partKey: pickValue.key });
|
|
const calls = [];
|
|
let opened = false;
|
|
const section = {
|
|
hasSelection: () => opened,
|
|
selectAt: (s) => {
|
|
calls.push(["selectAt", s.chainageM, s.structureId]);
|
|
opened = true;
|
|
},
|
|
};
|
|
const controls = { select: (p) => calls.push(["3d", p.chainageM, p.key ?? null]) };
|
|
const done = pickSession.restoreStructurePick(controls, P, section, (c) =>
|
|
calls.push(["station", c]),
|
|
);
|
|
return { calls, done };
|
|
};
|
|
out.restore = {
|
|
pipeOnly: restoreRun({ selection: { chainageM: 85.05, structureId: null } }),
|
|
structure: restoreRun({ selection: { chainageM: 205, structureId: "wall" }, key: "own" }),
|
|
nothing: restoreRun(null),
|
|
};
|
|
sel.selectDesign(P, sel.NO_SELECTION);
|
|
|
|
console.log(JSON.stringify(out));
|