Files
Aislo/resources/tester/helper_345_3_undo_owner.cjs
T
eomsangdonandClaude Opus 5.5 d985c24f5c feat(종단보기): 345-3 노선 편집 → 종단 보기 가운데 위 본문(페이지 수명) · 종단도 판 흐름 배치 · [노선 반영] 뒤 새로고침 없이 다시 세움 · Ctrl+Z · Y 는 마지막에 만진 판 · 「계획노선」 칸 · 편집 단추 지움
- mountRouteEditor(parent, host, …) — 덮개 · 전체 화면 · 닫기 걷음 · 이력 · 키 듣기 = host 보임 · 숨김 · 떠남 · 칸 크기 = ResizeObserver
- [취소] → [반영 전으로](이력 처음으로 · 되돌리기로 다시 살림) · [확인] → [노선 반영] · 대기 막 = 화면 전체(노선 재탐색)
- [노선 반영] 끝 = 초안 · 결과 · 옛 노선 · 종단면 캐시 비움 → remountRoute(location.reload 없앰)
- R 라벨 = 칸 안 절대 위치 · 오른쪽 횡단 미리보기 = 접는 칸 · [3D] 단추 = 평면 머리 도구줄
- 새 _RouteEdit_UndoOwner(누른 판 기억) · 종단도 Ctrl+Z 줄이 물음
- 시험: test_345_3_route_body 5 · 칸 · 겉틀 시험 고쳐 씀 · 관련 411 통과(옛 3 실패 그대로) · typecheck 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
2026-10-16 11:23:27 +09:00

92 lines
3.4 KiB
JavaScript

/* 345-3 Ctrl+Z · Y 한 판만 헬퍼 — `_RouteEdit_History`(평면 이력 단추 · 단축키)와 `_RouteEdit_UndoOwner` 를
* 트랜스파일해 가짜 창(window)에서 돌림. 종단도 쪽 듣는 자리는 `_Profile_Panel_Tools` 와 같은 꼴
* (`undoOwner() === "plan"` 이면 안 받음)로 흉내. 결과는 JSON 한 벌(표준출력).
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const listeners = {};
global.window = {
addEventListener: (type, fn) => (listeners[type] ??= new Set()).add(fn),
removeEventListener: (type, fn) => listeners[type]?.delete(fn),
};
const fire = (type, event) => [...(listeners[type] ?? [])].forEach((fn) => fn(event));
const cache = new Map();
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.resolve(path.dirname(full), request)}.ts`)
: require(request);
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
const page = path.join(ROOT, "B05_Profile");
const { bindHistoryControls } = loadTs(path.join(page, "B05_Profile_UI_RouteEdit_History.ts"));
const { undoOwner } = loadTs(path.join(page, "B05_Profile_UI_RouteEdit_UndoOwner.ts"));
const button = () => ({ disabled: false, addEventListener: () => undefined });
const buttons = { '[data-act="undo"]': button(), '[data-act="redo"]': button() };
const overlay = { querySelector: (sel) => buttons[sel] };
const plan = [];
const history = {
undo: () => (plan.push("undo"), { tag: "u" }),
redo: () => (plan.push("redo"), { tag: "r" }),
canUndo: () => true,
canRedo: () => true,
};
const controls = bindHistoryControls({
overlay,
getHistory: () => history,
restore: () => undefined,
});
// 종단도 듣는 자리 흉내 — `_Profile_Panel_Tools` 의 Ctrl+Z 줄과 같은 거름.
const profile = [];
window.addEventListener("keydown", (event) => {
if (!(event.ctrlKey && event.key === "z")) return;
if (undoOwner() === "plan") return;
profile.push(event.shiftKey ? "redo" : "undo");
});
const key = (k, shift = false) =>
fire("keydown", { ctrlKey: true, key: k, code: "", shiftKey: shift, preventDefault() {} });
const press = (cls) =>
fire("pointerdown", { target: { closest: (sel) => (sel === `.${cls}` ? {} : null) } });
const snap = () => ({ plan: [...plan], profile: [...profile], owner: undoOwner() });
const out = {};
key("z"); // 듣기 전 — 종단도만
out.beforeListen = snap();
controls.listen(true);
key("z"); // 아직 아무 판도 안 만짐 — 종단도
out.untouched = snap();
press("b05-routeedit");
key("z");
key("y");
out.afterPlanPress = snap();
press("b05-route-profile");
key("z");
out.afterProfilePress = snap();
press("b05-routeedit");
controls.listen(false); // 보기 숨김 — 평면 이력은 안 들음
key("z");
out.hidden = snap();
controls.listen(true);
key("z", true); // 다시 보임 · 마지막에 만진 판 = 평면 · Shift+Z = 다시하기
out.shown = snap();
controls.dispose();
key("z");
out.disposed = snap();
console.log(JSON.stringify(out));