- 얹기: attachShapes · attach_shapes 에 소단(section.berm · 덮는 소단 없음 = null) · SHAPE_KEYS 에 berm · TS attachBermSpecs ↔ 파이썬 attach_berm_specs · berm_spans_from 짝 - 읽기: 저장 노선 다시 셈(브라우저 · Node cross.refresh) = 얹힌 section.berm(칸 없음 = 저장분 design.berm) · 노선 초안 · 서버 Node 묶음 cross = crossWithShapes 의 얹힌 제원(bermAt · crossWithBerm 걷음) - 계단은 계산 안쪽 그대로 · [저장] 길(구조물 정본 → cross.berm_spans) 그대로 - 지움: 시설 줄 syncBerm · putBerm(C4.copy) · writeBermSpans · 세션 칸 berm · bermRequest · design_shared bermAt - 시험: test_272_berm_source 측점 얹기 거울로 · test_323_4 소단 얹음 · 477 · 511-A · 472 떠남 · 365 · 272 고름 헬퍼가 사본 대신 얹힌 소단 · design_pipeline_page 700 줄 → 699(주석 한 줄) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
194 lines
7.2 KiB
JavaScript
194 lines
7.2 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({}));
|
|
// 부품 켜기 칩(553-2) — 글은 안쪽 span 에 · 자식 글을 이어 읽음
|
|
const text = (node) =>
|
|
typeof node === "string"
|
|
? node
|
|
: (node?.textContent ?? "") + (node?.children ?? []).map(text).join("");
|
|
out.soilPressed = buttons[0]?.getAttribute("aria-pressed");
|
|
press((b) => /측구/.test(text(b)));
|
|
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));
|
|
// 결과 통 칸(계산 묶음 결과) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘(소단 사본 칸은 걷음 · 577-2)
|
|
sessionStorage.clear();
|
|
m.writeState("design-stations", [{ chainage_m: 0 }], "pB");
|
|
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);
|