- logic/B06_Section_Route_Carry.ts = 측점 열쇠 목록 · 측점 열쇠 칸 · 누가거리 칸 넘김(Station_Keys 몸통) · Station_Keys = 세션 겉 - B06_Section_Route_Carry_Patch.ts = 조정창 · 옮긴 고침 · 관 옵션 얹기 · crossPatchesFromCache · 서버용 routeCarryPatches(옛 carryAfterRouteSave 차례) · Section_Adjusts · Section_Store = 겉 · 내줌 - 칸 목록 USER_TOUCHED_KEYS · STRUCTURE_ADJUST_KEYS = logic/B06_Section_Design_Keys.ts · PIPE_MATCH_M = common_util_pipe_match.ts(옛 자리는 내줌) - 시험 헬퍼 · 글 단언 시험 = 옮긴 자리로(목록 한 벌 자리만 바뀜) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
199 lines
7.7 KiB
JavaScript
199 lines
7.7 KiB
JavaScript
/* PLAN 445 헬퍼 — [저장] 횡단 patch 바탕 · 세션 얹음 · 되돌림 · 옮긴 측점 입력.
|
|
* 조정창(`createStationControls`)으로 고친 뒤 횡단 보기 [저장] 본문(`buildCrossPatches` → `cachedCrossPatches`)을
|
|
* 그대로 만들어 내놓음 — 시험이 서버 병합(`_apply_section_edits` → `merge_cross_section_design_batches`)에 넣어
|
|
* DB 설계를 견줌. TS 는 그 자리에서 트랜스파일 · 화면 · 서버 창구는 가짜. 짝 = `test_445_save_patch.py`.
|
|
* node helper_445_save_patch.cjs <job.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 B06 = (name) => path.join(ROOT, "B06_Section", `${name}.ts`);
|
|
|
|
const memory = () => {
|
|
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(),
|
|
};
|
|
};
|
|
globalThis.sessionStorage = memory();
|
|
globalThis.localStorage = memory();
|
|
globalThis.window = globalThis;
|
|
|
|
const anything = () =>
|
|
new Proxy({}, { get: (_target, key) => (key === "__esModule" ? false : () => undefined) });
|
|
|
|
function makeLoader(real, stubs = {}) {
|
|
const cache = new Map();
|
|
const realSet = new Set(real.map((file) => path.resolve(file)));
|
|
const load = (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 need = (spec) => {
|
|
if (spec in stubs) return stubs[spec];
|
|
let target = null;
|
|
if (spec.startsWith("@util/")) target = path.join(ROOT, "common_util", `${spec.slice(6)}.ts`);
|
|
else if (spec.startsWith(".")) target = path.join(path.dirname(full), `${spec}.ts`);
|
|
if (target && realSet.has(path.resolve(target))) return load(target);
|
|
return anything();
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, need);
|
|
return box.exports;
|
|
};
|
|
return load;
|
|
}
|
|
|
|
const bodyStub = () => ({
|
|
control: { selectedFor: () => false, select: () => undefined },
|
|
byChainage: () => new Map(),
|
|
load: () => undefined,
|
|
});
|
|
const load = makeLoader(
|
|
[
|
|
B06("B06_Section_Section_Store"),
|
|
B06("B06_Section_Section_Canon"),
|
|
B06("B06_Section_Section_Adjusts"),
|
|
B06("B06_Section_Cross_Refresh"),
|
|
B06("B06_Section_Api_Culvert_Options"),
|
|
B06("B06_Section_UI_Page_Station_Controls"),
|
|
B06("B06_Section_UI_Page_Link_Session"),
|
|
B06("B06_Section_UI_Cross_Culvert_Types"),
|
|
B06("logic/B06_Section_Station_Swap"),
|
|
B06("logic/B06_Section_UI_Page_Patches"),
|
|
B06("logic/B06_Section_Design_Keys"), // 칸 목록 · 얹기 · patch 꼴 한 벌(472-6)
|
|
B06("logic/B06_Section_Route_Carry"),
|
|
B06("B06_Section_Route_Carry_Patch"),
|
|
path.join(ROOT, "common_util", "common_util_pipe_match.ts"),
|
|
path.join(ROOT, "A00_Common", "b_page_state.ts"),
|
|
path.join(ROOT, "A00_Common", "design", "design_selection.ts"),
|
|
],
|
|
{
|
|
"./B06_Section_Api_Fetch": {
|
|
fetchSectionDetail: async () => job.detail,
|
|
saveSections: async () => ({}),
|
|
},
|
|
"./B06_Section_UI_Page_Ford_Controls": {
|
|
createBodyControls: () => ({ ford: bodyStub(), box: bodyStub() }),
|
|
},
|
|
"./B06_Section_UI_Page_Span_Control": {
|
|
createSpanControl: () => ({
|
|
control: {},
|
|
spanKeyOf: (chainageM, wall) => `${chainageM.toFixed(2)}:${wall}`,
|
|
}),
|
|
},
|
|
"./B06_Section_Section_Drafts": {
|
|
withDraftStations: async () => undefined,
|
|
withDraftShapes: async () => undefined,
|
|
forgetDraftStations: () => undefined,
|
|
supersedeDraftStations: () => undefined,
|
|
},
|
|
"./B06_Section_Structure_Layouts": {
|
|
applyStructureAreaRows: () => undefined,
|
|
structureAreaRows: () => [],
|
|
computeStoredLayouts: () => null,
|
|
},
|
|
"../B05_Profile/B05_Profile_Api_PlusStations": {
|
|
loadSavedPlusStations: async () => undefined,
|
|
plusStationsOf: () => [],
|
|
},
|
|
},
|
|
);
|
|
|
|
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const P = "p445";
|
|
const R = 7;
|
|
const { stateKey } = load(path.join(ROOT, "A00_Common", "b_page_state.ts"));
|
|
const store = load(B06("B06_Section_Section_Store"));
|
|
const { buildCrossPatches } = load(B06("logic/B06_Section_UI_Page_Patches"));
|
|
const { createStationControls } = load(B06("B06_Section_UI_Page_Station_Controls"));
|
|
|
|
/** 새 탭 = 빈 세션 · 서버 상세(정본) 받아 캐시에 · 조정창 하나. */
|
|
async function freshTab(clearSession = true) {
|
|
if (clearSession) globalThis.sessionStorage.clear();
|
|
store.invalidateSectionDetail(P, R);
|
|
const detail = await store.loadSectionDetail(P, R);
|
|
const controls = createStationControls({
|
|
sessionKey: (kind) => stateKey(kind, P, R),
|
|
refreshCard: () => undefined,
|
|
detail: () => detail,
|
|
crossHalfWidth: () => undefined,
|
|
sampledHalfWidth: () => 20,
|
|
ensureSampledWidth: async () => true,
|
|
projectId: () => P,
|
|
});
|
|
controls.load();
|
|
return { detail, controls };
|
|
}
|
|
|
|
/** 횡단 보기 [저장] 본문 — 세션 고침(`buildCrossPatches`) → 바탕 위에 얹음(`cachedCrossPatches`). */
|
|
function savePatches(controls) {
|
|
const overlay = buildCrossPatches({
|
|
rockOffsets: new Map(),
|
|
cutSlopeRatios: new Map(),
|
|
stationWidths: controls.widths,
|
|
inletStructures: controls.inletStructures,
|
|
basinAdjustments: controls.basinAdjustments,
|
|
revetAdjusts: controls.revetAdjustsByChainage(),
|
|
extraCounts: controls.extraCountsByChainage(),
|
|
extraSpans: controls.extraSpansByChainage(),
|
|
fordAdjusts: new Map(),
|
|
boxAdjusts: new Map(),
|
|
linkFlags: controls.linkFlagsByChainage(),
|
|
});
|
|
return { overlay, patches: store.cachedCrossPatches(P, R, overlay) };
|
|
}
|
|
|
|
(async () => {
|
|
const at = job.at;
|
|
const out = {};
|
|
|
|
// H3 — 유출만 고침 → 유입 · 집수 쪽 단 수 남음
|
|
{
|
|
const { controls } = await freshTab();
|
|
controls.revetOffset.update(at, "outlet", { x: 3 });
|
|
out.h3 = savePatches(controls);
|
|
}
|
|
// M2 — 새 탭 ↺(유입) · 추가 벽 − · 빈 전체 폭 · 집수정 내공 2.1(M1)
|
|
{
|
|
const { controls } = await freshTab();
|
|
controls.revetOffset.reset(at, "inlet");
|
|
controls.extraWalls.setCount(at, 0, "outlet");
|
|
controls.applyGlobalWidth(undefined, [at]);
|
|
controls.inletStructure.updateAdjust(at, { innerWidthM: 2.1, innerHeightM: 0.5 });
|
|
out.m2 = savePatches(controls);
|
|
out.m2.countShown = controls.extraWalls.countFor({ chainage_m: at, design: {} }, "outlet");
|
|
// 저장 전 새로고침(세션은 남고 상세는 정본) — 되돌림이 살아 있어야 함
|
|
const again = await freshTab(false);
|
|
out.m2reload = savePatches(again.controls);
|
|
out.m2reload.cachedInlet = again.detail.cross_sections[0].design.revet_adjust?.inlet ?? null;
|
|
out.m2reload.cachedCounts = again.detail.cross_sections[0].design.extra_wall_counts ?? null;
|
|
}
|
|
// H2 — 정본 행이 없는 자리(옮긴 관)의 캐시 설계 = 지반 등 입력까지 실음
|
|
{
|
|
globalThis.sessionStorage.clear();
|
|
store.invalidateSectionDetail(P, R);
|
|
await store.loadSectionDetail(P, R); // 정본 = job.detail(at 한 측점)
|
|
const moved = await store.loadSectionDetail(P, R);
|
|
moved.cross_sections.push({ ...job.moved });
|
|
out.h2 = store.cachedCrossPatches(P, R, []);
|
|
moved.cross_sections.pop();
|
|
}
|
|
process.stdout.write(JSON.stringify(out));
|
|
})().catch((error) => {
|
|
process.stderr.write(String(error?.stack ?? error));
|
|
process.exit(1);
|
|
});
|