Files
Aislo/resources/tester/helper_233_4_empty_click.cjs
T
eomsangdonandClaude Opus 5.5 78bab9eeda fix(design): 종단 · 유토곡선 · 횡단 그리드 빈 곳 누름도 고름 안 풂 (233-4)
- 빈 곳 누름 해제 듣개 뗌 — 3D 와 같은 한 규칙
- 풀기는 Esc · 같은 것 다시 누름 한 길 · 도구 중 빈 곳 누름은 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-10 19:18:19 +09:00

145 lines
6.0 KiB
JavaScript

/* 233-4 빈 곳 누름 = 어디서도 안 풂 — 진짜 소스의 누름 처리 본문을 떼어(TS 그 자리 트랜스파일) 돌림.
* ① 종단 그래프 빈 곳 누름(`_Profile_Render` chartWrap click) ② 횡단 그리드 빈 곳 누름(`_Section_View` grid click)
* ③ 같은 것 다시 누름 = 풂(종단 측점선 `design_profile` selectStation · 횡단 카드 `_Section_View` selectStation)
* ④ Esc = 풂(`design_pick_escape` + `design_selection`). 표준출력 JSON 한 벌 · 짝 = test_233_4_empty_click.py.
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const read = (...parts) => fs.readFileSync(path.join(ROOT, ...parts), "utf8").replace(/\r/g, "");
const run = (code, names, values) => {
const js = ts.transpileModule(code, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
return new Function(...names, js)(...values);
};
/** `start` 줄부터 같은 들여쓰기로 닫는 `end` 줄까지 떼어 냄. */
const block = (src, start, end) => {
const from = src.indexOf(start);
if (from < 0) throw new Error(`본문 없음: ${start}`);
const to = src.indexOf(end, from);
return src.slice(from, to + end.length);
};
const out = {};
const target = (inStation = false, inCard = false) => ({
closest: (sel) =>
(sel.includes("b06-chart__station") && inStation) || (sel.includes("b06-cross-card") && inCard)
? {}
: null,
});
/* ① 종단 그래프 빈 곳 누름 */
const render = read("B05_Profile", "B05_Profile_UI_Profile_Render.ts");
const chartClick = block(render, ` chartWrap.addEventListener("click", (event) => {`, "\n });");
const calls = [];
let tool = false;
const ctx = {
toolActive: () => tool,
handleToolPick: (at) => (calls.push(`tool:${at}`), true),
selectedStationId: () => "s120",
selectedStructureId: () => "wall",
selectStation: (id) => calls.push(`station:${id}`),
setSelectedStructureId: (id) => calls.push(`structure:${id}`),
redraw: () => calls.push("redraw"),
};
const callbacks = { onStructureSelect: (id) => calls.push(`onStructureSelect:${id}`) };
let handler = null;
const chartWrap = {
addEventListener: (_type, fn) => (handler = fn),
getBoundingClientRect: () => ({ left: 0 }),
scrollLeft: 0,
};
run(
chartClick,
["ctx", "callbacks", "chartWrap", "chainageInverter", "longitudinal", "width", "layout"],
[ctx, callbacks, chartWrap, () => (x) => x, null, 100, { originOffset: 0 }],
);
handler({ target: target(), clientX: 42 });
out.profileEmpty = calls.splice(0);
tool = true; // 도구 중 빈 곳 누름은 도구 몫 그대로
handler({ target: target(), clientX: 42 });
out.profileEmptyTool = calls.splice(0);
/* ② 횡단 그리드 빈 곳 누름 — 그리드에 걸린 누름 듣개를 모두 돌림 */
const view = read("B06_Section", "B06_Section_UI_Section_View.ts");
const gridStart = view.indexOf(' grid.className = "b06-section__grid";');
const gridCode = view.slice(gridStart, view.indexOf(" // 열 수 = 사용자 값", gridStart)); // 그리드 누름 듣개 전부
const gridCalls = [];
const grid = { listeners: [], addEventListener: (_t, fn) => grid.listeners.push(fn) };
run(
gridCode.replace(' grid.className = "b06-section__grid";', ""),
["grid", "clearSelection", "pavementAt", "spanMarks", "isPavement", "pavementSelectListener"],
[grid, () => gridCalls.push("clearSelection"), () => null, [], () => false, null],
);
for (const fn of grid.listeners)
fn({ target: target(), stopPropagation() {}, stopImmediatePropagation() {} });
out.gridListeners = grid.listeners.length;
out.gridEmpty = gridCalls.splice(0);
/* ③ 같은 것 다시 누름 = 풂 */
const dpSrc = read("A00_Common", "design", "design_profile.ts");
const dpSelect = block(
dpSrc,
" function selectStation(stationId: string | null): void {",
"\n }",
);
const profileState = run(
`let selectedStationId = null; let selectedStructureId = null;
${dpSelect}
return { selectStation, line: () => selectedStationId };`,
["structureIdAtStation", "draw", "onSelectStation"],
[() => null, () => undefined, () => undefined],
);
profileState.selectStation("s120");
out.profileFirst = profileState.line();
profileState.selectStation("s120");
out.profileAgain = profileState.line();
const svSelect = block(
view,
" const selectStation = (stationId: string, scroll: boolean): void => {",
"\n };",
);
const cardState = run(
`let selectedStationId = null; let activeAreaKey = null;
${svSelect}
return { selectStation, card: () => selectedStationId };`,
["applySelection", "revealCard"],
[() => undefined, () => undefined],
);
cardState.selectStation("s120", true);
out.cardFirst = cardState.card();
cardState.selectStation("s120", true);
out.cardAgain = cardState.card();
/* ④ Esc = 풂 */
const store = new Map();
const loadTs = (file, stubs) => {
const box = { exports: {} };
const js = ts.transpileModule(read(...file.split("/")), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
new Function("exports", "module", "require", js)(box.exports, box, (r) => stubs[r]);
return box.exports;
};
const DSel = loadTs("A00_Common/design/design_selection.ts", {
"../b_page_state": {
readState: (k, p) => store.get(`${k}:${p}`) ?? null,
writeState: (k, v, p) => store.set(`${k}:${p}`, v),
},
});
let onKey = null;
globalThis.window = { addEventListener: (_t, fn) => (onKey = fn), removeEventListener() {} };
globalThis.document = { querySelector: () => null };
const ESC = loadTs("A00_Common/design/design_pick_escape.ts", { "./design_selection": DSel });
ESC.bindEscapeRelease("p", { visible: () => true, onDispose() {} });
DSel.selectDesign("p", { chainageM: 120, structureId: null }, { from: "b06" });
out.beforeEsc = DSel.readSelection("p");
onKey({ key: "Escape", defaultPrevented: false, target: { closest: () => null } });
out.afterEsc = DSel.readSelection("p");
process.stdout.write(JSON.stringify(out));