196 lines
7.1 KiB
JavaScript
196 lines
7.1 KiB
JavaScript
/* 472 덧(요구 272 C · D) 헬퍼 — 진짜 모듈(helper_472_open_no_draft_entry.ts)로
|
|
* ① 표준 횡단 패널 · 설정값 받기 = 세션 초안 칸 안 씀(같은 값이면 칸 없음) · 계산은 저장분
|
|
* ② 카드 단추 = 바꾼 칸만 보냄 · 세션 고름은 앞서 바꾼 칸에 얹음
|
|
* node helper_472_open_no_draft.mjs <묶음.mjs> <표준 횡단 고정 자료.json>
|
|
* 짝 = `test_472_open_no_draft.py` · 표준출력 JSON 한 벌.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const store = () => {
|
|
const m = new Map();
|
|
return {
|
|
getItem: (k) => (m.has(k) ? m.get(k) : null),
|
|
setItem: (k, v) => m.set(k, String(v)),
|
|
removeItem: (k) => m.delete(k),
|
|
key: (i) => [...m.keys()][i] ?? null,
|
|
get length() {
|
|
return m.size;
|
|
},
|
|
clear: () => m.clear(),
|
|
};
|
|
};
|
|
/** 작은 가짜 요소 — 자식 · 듣는 함수 · 글만 들고 나머지는 빈 함수. */
|
|
function element(tag) {
|
|
const base = {
|
|
tagName: String(tag).toUpperCase(),
|
|
children: [],
|
|
listeners: {},
|
|
attrs: {},
|
|
style: {},
|
|
dataset: {},
|
|
textContent: "",
|
|
classList: { add() {}, remove() {}, toggle() {}, contains: () => false },
|
|
append(...kids) {
|
|
base.children.push(...kids);
|
|
},
|
|
prepend(...kids) {
|
|
base.children.unshift(...kids);
|
|
},
|
|
insertBefore(kid) {
|
|
base.children.push(kid);
|
|
},
|
|
replaceChildren(...kids) {
|
|
base.children = kids;
|
|
},
|
|
addEventListener(type, fn) {
|
|
(base.listeners[type] ??= []).push(fn);
|
|
},
|
|
setAttribute(k, v) {
|
|
base.attrs[k] = String(v);
|
|
},
|
|
getAttribute: (k) => base.attrs[k] ?? null,
|
|
querySelector: () => null,
|
|
querySelectorAll: () => [],
|
|
closest: () => null,
|
|
clientWidth: 0,
|
|
offsetWidth: 0,
|
|
scrollWidth: 0,
|
|
};
|
|
return new Proxy(base, {
|
|
get: (t, k) => (k in t ? t[k] : typeof k === "string" ? () => undefined : undefined),
|
|
});
|
|
}
|
|
const noop = new Proxy(function () {}, {
|
|
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : noop),
|
|
apply: () => noop,
|
|
construct: () => noop,
|
|
});
|
|
globalThis.window = globalThis;
|
|
globalThis.sessionStorage = store();
|
|
globalThis.localStorage = store();
|
|
globalThis.document = new Proxy(
|
|
{ createElement: element, createElementNS: (_ns, tag) => element(tag) },
|
|
{ get: (t, k) => (k in t ? t[k] : noop) },
|
|
);
|
|
globalThis.ResizeObserver = class {
|
|
observe() {}
|
|
disconnect() {}
|
|
};
|
|
globalThis.getComputedStyle = () => ({ gap: "0" });
|
|
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
|
|
globalThis.addEventListener = () => undefined;
|
|
globalThis.removeEventListener = () => undefined;
|
|
globalThis.fetch = async () => new Response("{}", { status: 404 });
|
|
|
|
const m = await import(pathToFileURL(process.argv[2]).href);
|
|
const draftKeys = () =>
|
|
Object.keys(
|
|
Object.fromEntries([...Array(sessionStorage.length)].map((_, i) => [sessionStorage.key(i), 1])),
|
|
).filter((k) => k.startsWith("aislo:draft:"));
|
|
const out = {};
|
|
|
|
// ① 표준 횡단 — config 와 저장분이 다른 프로젝트
|
|
const config = JSON.parse(readFileSync(process.argv[3], "utf8")).standard_cross_section;
|
|
const stored = JSON.parse(JSON.stringify(config));
|
|
stored.rock.ditch.top_width_m = 0.9; // 저장분 = 사용자가 고쳐 저장한 값(config 와 다름)
|
|
const context = {
|
|
project_id: "pS",
|
|
standard_cross_section: config,
|
|
stored_standard_cross_section: stored,
|
|
};
|
|
m.writeState("section-context", context, "pS");
|
|
await m.fetchSectionContext("pS"); // 캐시 길 — 예전엔 여기서 저장분을 초안 칸에 베낌
|
|
out.ctxDrafts = draftKeys();
|
|
const panel = m.createStandardPanel("pS", config, () => {});
|
|
out.panelDrafts = draftKeys();
|
|
out.panelRockDitch = panel.getValues().rock.ditch.top_width_m;
|
|
out.effectiveRockDitch = m.effectiveStandardCross("pS").rock.ditch.top_width_m;
|
|
panel.applyStored(stored);
|
|
out.afterStored = draftKeys();
|
|
// 옛 판이 남긴 같은 값 칸 — 열면 걷음
|
|
m.writeState("std-cross", stored, "pS2");
|
|
m.writeState("section-context", { ...context, project_id: "pS2" }, "pS2");
|
|
m.createStandardPanel("pS2", config, () => {});
|
|
out.oldSameCell = m.readState("std-cross", "pS2");
|
|
// 저장분 없는 프로젝트 — config 그대로 = 칸 없음 · 계산은 config
|
|
m.writeState("section-context", { project_id: "pC", standard_cross_section: config }, "pC");
|
|
m.createStandardPanel("pC", config, () => {});
|
|
out.configDrafts = draftKeys();
|
|
out.configRockDitch = m.effectiveStandardCross("pC").rock.ditch.top_width_m;
|
|
// 사용자가 고친 값은 칸에 남음
|
|
const edited = JSON.parse(JSON.stringify(stored));
|
|
edited.soil.road_width_m = 3.5;
|
|
m.writeState("std-cross", edited, "pE");
|
|
m.writeState("section-context", { ...context, project_id: "pE" }, "pE");
|
|
m.createStandardPanel("pE", config, () => {});
|
|
out.editedRoadWidth = m.readState("std-cross", "pE")?.soil.road_width_m ?? null;
|
|
|
|
// ② 카드 단추 — 리핑암 · 좌절토 · 측구 자동 판정
|
|
sessionStorage.clear();
|
|
const section = {
|
|
station_id: "No.1",
|
|
chainage_m: 20,
|
|
uphill_side: "left",
|
|
design: {
|
|
ground_type: "ripping_rock",
|
|
section_mode: "left_cut",
|
|
ditch_side: null,
|
|
ditch_type: "standard",
|
|
paved: false,
|
|
two_stage_slope: false,
|
|
ditch_choice: null,
|
|
ditch_enabled: true,
|
|
},
|
|
samples: [],
|
|
};
|
|
const sent = [];
|
|
const { bar, groundSegment } = m.buildDesignControls(section, (_c, change) => sent.push(change));
|
|
const buttons = [];
|
|
const walk = (node) => {
|
|
if (!node || typeof node !== "object") return;
|
|
if (node.tagName === "BUTTON") buttons.push(node);
|
|
(node.children ?? []).forEach(walk);
|
|
};
|
|
walk(groundSegment);
|
|
walk(bar);
|
|
const press = (pick) => buttons.find(pick)?.listeners.click?.forEach((fn) => fn({}));
|
|
out.soilPressed = buttons[0]?.getAttribute("aria-pressed");
|
|
press(
|
|
(b) => b.textContent === "측구" || b.textContent === "측구 없음" || /측구/.test(b.textContent),
|
|
);
|
|
out.ditchChange = sent.at(-1) ?? null;
|
|
// 새 카드 · 토사 단추 — 지반만(측구형식은 이미 일반이라 안 실림)
|
|
buttons.length = 0;
|
|
const again = m.buildDesignControls(section, (_c, change) => sent.push(change));
|
|
walk(again.groundSegment);
|
|
press(() => true);
|
|
out.soilChange = sent.at(-1) ?? null;
|
|
|
|
// 세션 고름 — 앞서 바꾼 칸에 얹음(지반은 안 박음)
|
|
const detail = { cross_sections: [section] };
|
|
const sync = m.createDesignSync({
|
|
projectId: "pD",
|
|
routeId: () => 7,
|
|
detail: () => detail,
|
|
view: () => noop,
|
|
});
|
|
await sync.handleDesignChange(20, { ditch_choice: false }).catch(() => undefined);
|
|
await sync.handleDesignChange(20, { two_stage_slope: true }).catch(() => undefined);
|
|
out.choice = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
|
await sync.handleDesignChange(20, {}).catch(() => undefined);
|
|
out.choiceAfterEmpty = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
|
// 소단 사본(시설 줄이 열 때 구조물에서 씀) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘
|
|
sessionStorage.clear();
|
|
m.writeState(
|
|
"berm",
|
|
[{ start_m: 0, end_m: 40, width_m: 1, interval_m: 5, slope_deg: 45, depth_m: 1 }],
|
|
"pB",
|
|
7,
|
|
);
|
|
out.dirtyBermOnly = m.designDirty("pB");
|
|
m.writeCrossDesignChoice("pB", 7, 20, { ditch_choice: false });
|
|
out.dirtyWithEdit = m.designDirty("pB");
|
|
process.stdout.write(JSON.stringify(out));
|
|
process.exit(0);
|