- design_pipeline_page.ts 새 — 프로젝트마다 묶음 하나(종단도 dispose 때 버림) · route · ground(sub8) · follow · planline · profile · stations · footprint(sub6) 붙임 · cross · masshaul · haul · drainage 는 붙으면 한 줄 · 첫 계산은 처음 고칠 때 · 가게 stations · profile · footprint · generation - B05_Profile_UI_RouteEdit_Draft.ts 새 — 사진첩 감싸기(쌓기 · 되돌리기 · 다시하기 · 열 때로 = 초안 넘김 · 같으면 null) · 저장 안 한 초안 되살림 · RouteEdit.ts 는 줄 셋(700줄 안) - logic/B05_Profile_Pipeline_Grade.ts 새 — follow(관 · 구조물 좌표 · 계획고 옮김) · planline(저장 기준 · 초안이면 바뀐 구간만 새 지반 첫 계획선) · profile(buildAlignment) · routeRange · gradeRange — sub1 짝 부르기만 - design_profile — listed 를 design_profile_base.ts 로 나눔 · rebuild = pipelineGrade · render · 구조물 측점 = 다시 셈 · adoptPipeline(초안 동안 편집은 메모리만 · [저장]이 안 보냄) · pipelineSource - design_pipeline — input() · dispose() - 시험 test_346_1_pipeline_page 12 건(실제 단계 · 노선 1,078 m 점 옮김 23 ms · 늦은 응답 버림 · 초안 캐시 칸 살아남음) · popout 시험 문맥에 pipelineGrade 자리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
230 lines
7.8 KiB
JavaScript
230 lines
7.8 KiB
JavaScript
/* 종단 새 창 최대화 — 같은 꼴 나머지(계획서 230-2) 단위검증 헬퍼.
|
|
* 메인 창은 「가려짐」(화면 틀 · 시계 멈춤) · 종단 요소는 새 창 소속. 실제 TS 의 해당 함수 본문을
|
|
* 잘라 그 자리에서 트랜스파일해 돌리고 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 read = (...p) => fs.readFileSync(path.join(ROOT, ...p), "utf8");
|
|
const run = (code, scope) => {
|
|
const js = ts.transpileModule(code, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const names = Object.keys(scope);
|
|
return new Function(...names, js)(...names.map((n) => scope[n]));
|
|
};
|
|
/** `start` 부터 짝 맞는 닫는 중괄호까지 잘라 냄 — 본문 여는 괄호가 반환 타입 뒤면 `open` 으로 짚음. */
|
|
function block(src, start, open = "{") {
|
|
const at = src.indexOf(start);
|
|
if (at < 0) throw new Error(`본문 없음: ${start}`);
|
|
let depth = 0;
|
|
for (let i = src.indexOf(open, at) + open.length - 1; i < src.length; i++) {
|
|
if (src[i] === "{") depth++;
|
|
else if (src[i] === "}" && --depth === 0) return src.slice(at, i + 1);
|
|
}
|
|
throw new Error(`닫힘 없음: ${start}`);
|
|
}
|
|
|
|
// ---- 가짜 창 — frozen 이면 화면 틀 · 시계가 안 돎 ----
|
|
function fakeWindow(name, frozen) {
|
|
const win = { name, frozen, now: 0, timers: [], seq: 0, frames: 0, listeners: [] };
|
|
win.setTimeout = (fn, ms) => (win.timers.push({ id: ++win.seq, at: win.now + ms, fn }), win.seq);
|
|
win.clearTimeout = (id) => (win.timers = win.timers.filter((t) => t.id !== id));
|
|
win.setInterval = (fn, ms) => {
|
|
const id = ++win.seq;
|
|
const again = (at) => win.timers.push({ id, at, fn: () => (fn(), again(win.now + ms)) });
|
|
again(win.now + ms);
|
|
return id;
|
|
};
|
|
win.clearInterval = win.clearTimeout;
|
|
win.requestAnimationFrame = (fn) => win.setTimeout(() => (win.frames++, fn(win.now)), 16);
|
|
win.cancelAnimationFrame = win.clearTimeout;
|
|
win.addEventListener = (type, fn) => win.listeners.push([type, fn]);
|
|
win.removeEventListener = (type, fn) =>
|
|
(win.listeners = win.listeners.filter(([t, f]) => t !== type || f !== fn));
|
|
win.tick = (ms) => {
|
|
if (win.frozen) return;
|
|
const end = win.now + ms;
|
|
for (;;) {
|
|
const next = win.timers.filter((t) => t.at <= end).sort((a, b) => a.at - b.at)[0];
|
|
if (!next) break;
|
|
win.now = next.at;
|
|
win.clearTimeout(next.id);
|
|
next.fn();
|
|
}
|
|
win.now = end;
|
|
};
|
|
return win;
|
|
}
|
|
|
|
const main = fakeWindow("main", true);
|
|
const popup = fakeWindow("popup", false);
|
|
globalThis.window = main;
|
|
globalThis.requestAnimationFrame = main.requestAnimationFrame; // 옛 코드가 쓰던 전역 = 메인 창
|
|
globalThis.cancelAnimationFrame = main.cancelAnimationFrame;
|
|
const inPopup = () => ({ ownerDocument: { defaultView: popup }, style: { setProperty() {} } });
|
|
|
|
const vw = {};
|
|
run(
|
|
read("common_util", "common_util_view_window.ts").replace(/export /g, "") +
|
|
"\nObject.assign(vw, { viewOf, frameOf });",
|
|
{ vw },
|
|
);
|
|
const { viewOf, frameOf } = vw;
|
|
const out = {};
|
|
const mainBusy = () => main.timers.length;
|
|
|
|
// ① 공용 함수 — 새 창 요소는 새 창 · 안 붙은 요소는 메인 창
|
|
out.viewOf = {
|
|
popup: viewOf(inPopup()).name,
|
|
detached: viewOf({ ownerDocument: { defaultView: null } }).name,
|
|
none: viewOf(null).name,
|
|
};
|
|
|
|
// ② 계획고 편집 뒤 다시 그리기(design_profile rebuild) — 두 번 불러도 한 번만 그림
|
|
{
|
|
const s = { draws: 0, previews: 0 };
|
|
const src = block(
|
|
read("A00_Common", "design", "design_profile.ts"),
|
|
"function rebuild(): void {",
|
|
);
|
|
run(`let redrawPending = false; ${src}\nrebuild(); rebuild(); s.rebuild = true;`, {
|
|
s,
|
|
base: null,
|
|
alignment: null,
|
|
buildAlignment: () => null,
|
|
store: { edits: () => ({}) },
|
|
projectId: "p",
|
|
pipelineGrade: () => undefined, // 계산 묶음(346-1) — 묶음 없으면 아무 일 없음
|
|
scheduleCrossPreview: () => s.previews++,
|
|
draw: () => s.draws++,
|
|
root: inPopup(),
|
|
frameOf,
|
|
});
|
|
popup.tick(50);
|
|
out.rebuild = { draws: s.draws, previews: s.previews, mainTimers: mainBusy() };
|
|
}
|
|
|
|
// ③ 높이 맞춤(Heights scheduleLightSync)
|
|
{
|
|
const s = { syncs: 0 };
|
|
const src = block(
|
|
read("B05_Profile", "B05_Profile_UI_Profile_Heights.ts"),
|
|
"function scheduleLightSync(): void {",
|
|
);
|
|
run(`let lightSyncPending = false; ${src}\nscheduleLightSync(); scheduleLightSync();`, {
|
|
deps: { body: inPopup() },
|
|
syncHeightsLight: () => s.syncs++,
|
|
frameOf,
|
|
});
|
|
popup.tick(50);
|
|
out.heights = { syncs: s.syncs, mainTimers: mainBusy() };
|
|
}
|
|
|
|
// ④ 표 · 유토곡선 크기 끌기(TableOverlay · MassHaul onResize)
|
|
for (const [key, file] of [
|
|
["table", "B05_Profile_UI_Profile_TableOverlay.ts"],
|
|
["massHaul", "B05_Profile_UI_Profile_MassHaul.ts"],
|
|
]) {
|
|
const s = { changed: 0, handles: 0 };
|
|
const src = block(read("B05_Profile", file), "onResize: () => {").replace(
|
|
/^onResize: /,
|
|
"const onResize = ",
|
|
);
|
|
run(`let resizeRedrawPending = false; ${src}\nonResize(); onResize();`, {
|
|
syncHandlePosition: () => s.handles++,
|
|
onChanged: () => s.changed++,
|
|
overlay: inPopup(),
|
|
frameOf,
|
|
});
|
|
popup.tick(50);
|
|
out[key] = { changed: s.changed, handles: s.handles, mainTimers: mainBusy() };
|
|
}
|
|
|
|
// ⑤ 유토곡선 세로창 미끄러짐(scheduleMassHaulSettle)
|
|
{
|
|
const s = { redraws: 0 };
|
|
const src = block(
|
|
read("common_util", "common_util_mass_haul_view.ts"),
|
|
"export function scheduleMassHaulSettle(",
|
|
).replace(/^export /, "");
|
|
const state = { held: null, settled: false, frame: 0 };
|
|
run(
|
|
`${src}\nscheduleMassHaulSettle(state, () => s.redraws++, node); scheduleMassHaulSettle(state, () => s.redraws++, node);`,
|
|
{
|
|
s,
|
|
state,
|
|
node: inPopup(),
|
|
viewOf,
|
|
},
|
|
);
|
|
popup.tick(50);
|
|
out.settle = {
|
|
redraws: s.redraws,
|
|
frame: state.frame,
|
|
view: state.view?.name,
|
|
mainTimers: mainBusy(),
|
|
};
|
|
}
|
|
|
|
// ⑥ 끌기 뒤 다시 그리기(Profile_Drag scheduleDraw · 120ms 모음)
|
|
{
|
|
const s = { draws: 0 };
|
|
const src = block(
|
|
read("B05_Profile", "B05_Profile_UI_Profile_Drag.ts"),
|
|
"const scheduleDraw = (): void => {",
|
|
);
|
|
run(`let resizeTimer = 0; let timerView = window; ${src}\nscheduleDraw(); scheduleDraw();`, {
|
|
window: main,
|
|
root: inPopup(),
|
|
ctx: { draw: () => s.draws++ },
|
|
viewOf,
|
|
});
|
|
popup.tick(200);
|
|
out.drag = { draws: s.draws, mainTimers: mainBusy() };
|
|
}
|
|
|
|
// ⑦ ▲▼ 길게 누르기(Profile_Edit createHoldRepeater) — 0.5초 뒤 초당 10회
|
|
{
|
|
const s = { acts: 0 };
|
|
const src = block(
|
|
read("B05_Profile", "B05_Profile_UI_Profile_Edit.ts"),
|
|
"function createHoldRepeater(",
|
|
"} {",
|
|
);
|
|
run(`${src}\nconst r = createHoldRepeater(node); r.start(() => s.acts++); s.r = r;`, {
|
|
s,
|
|
window: main,
|
|
node: inPopup(),
|
|
viewOf,
|
|
HOLD_DELAY_MS: 500,
|
|
HOLD_INTERVAL_MS: 100,
|
|
});
|
|
popup.tick(1000);
|
|
const acts = s.acts;
|
|
s.r.stop();
|
|
popup.tick(1000);
|
|
out.hold = { acts, afterStop: s.acts, mainTimers: mainBusy(), popupTimers: popup.timers.length };
|
|
}
|
|
|
|
// ⑧ 배수유역도(Drainage_Panel scheduleDraw)
|
|
{
|
|
const s = { draws: 0 };
|
|
const src = block(
|
|
read("B05_Profile", "B05_Profile_UI_Drainage_Panel.ts"),
|
|
"function scheduleDraw(): void {",
|
|
);
|
|
run(`let frameHandle = 0; let frameView = window; ${src}\nscheduleDraw(); scheduleDraw();`, {
|
|
window: main,
|
|
root: inPopup(),
|
|
draw: () => s.draws++,
|
|
viewOf,
|
|
});
|
|
popup.tick(50);
|
|
out.drainage = { draws: s.draws, mainTimers: mainBusy() };
|
|
}
|
|
|
|
process.stdout.write(JSON.stringify(out));
|