Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
242 lines
8.9 KiB
JavaScript
242 lines
8.9 KiB
JavaScript
/* 계획노선 편집 창 **측점 법선 · 고름 연동**(계획서 22-1 · 382-2) 단위검증 헬퍼 —
|
|
* `B05_Profile_UI_RouteEdit_Cross.ts` 를 트랜스파일해 Node 로 돌린다.
|
|
* 계산 묶음 · 가게 · 그림 조각은 가짜로 채운다 — 공용 고름(`design_selection`)은 진짜.
|
|
* 묶음 측점 = 일반측점 grid:2(40) · grid:3(60) · grid:5(100) · 관 pipe:a(80) · 원지반 표본 좌우 20 m.
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
|
|
function memoryStorage() {
|
|
const map = new Map();
|
|
return {
|
|
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
|
setItem: (key, value) => map.set(key, String(value)),
|
|
removeItem: (key) => map.delete(key),
|
|
key: (index) => [...map.keys()][index] ?? null,
|
|
get length() {
|
|
return map.size;
|
|
},
|
|
};
|
|
}
|
|
const session = memoryStorage();
|
|
Object.defineProperty(session, "length", { get: () => session._keys().length });
|
|
session._keys = () => {
|
|
const keys = [];
|
|
for (let i = 0; i < 1000; i += 1) {
|
|
const key = session.key(i);
|
|
if (key === null) break;
|
|
keys.push(key);
|
|
}
|
|
return keys;
|
|
};
|
|
// `Object.keys(sessionStorage)` 가 저장된 키를 돌려주도록 실제 브라우저처럼 속성으로도 둔다.
|
|
const sessionProxy = new Proxy(session, {
|
|
ownKeys: () => [...new Set(session._keys())],
|
|
getOwnPropertyDescriptor: (target, key) =>
|
|
session._keys().includes(key)
|
|
? { enumerable: true, configurable: true, value: target.getItem(key) }
|
|
: Reflect.getOwnPropertyDescriptor(target, key),
|
|
});
|
|
globalThis.window = { localStorage: memoryStorage(), sessionStorage: sessionProxy };
|
|
|
|
const fakeEl = () => ({
|
|
className: "",
|
|
textContent: "",
|
|
innerHTML: "",
|
|
querySelector: () => fakeEl(),
|
|
replaceChildren() {},
|
|
setAttribute() {},
|
|
append() {},
|
|
getBoundingClientRect: () => ({ width: 300, height: 200 }),
|
|
});
|
|
globalThis.document = { createElement: fakeEl };
|
|
// node 에는 ResizeObserver 가 없음 — 횡단 판 크기 따라가기는 이 시험 밖
|
|
globalThis.ResizeObserver = class {
|
|
observe() {}
|
|
disconnect() {}
|
|
};
|
|
|
|
const grid = (k, chainageM) => ({ key: `grid:${k}`, kind: "grid", chainageM });
|
|
let stations = [
|
|
grid(2, 40),
|
|
grid(3, 60),
|
|
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
|
|
grid(5, 100),
|
|
];
|
|
let footprint = null;
|
|
let route = { v: 1 };
|
|
// 접힌 판 펴기 — 몸(side)에 묶인 패널 묶음 흉내
|
|
// 393-1 — 측점 눈금이 연 판은 기억 안 함(remember 거짓)
|
|
const panels = {
|
|
now: null,
|
|
current: () => panels.now,
|
|
open: (id, remember = true) => ((panels.now = id), (panels.remember = remember)),
|
|
};
|
|
// 393-2 — 판 그림 = B06 카드(가짜) · 그린 단면 id 를 적음
|
|
const cards = [];
|
|
const side = { append() {} };
|
|
|
|
const STUBS = {
|
|
"../A00_Common/b_svg_zoom_pan": { attachSvgZoomPan: () => {} },
|
|
"../common_util/common_util_station": { stationLabel: (m) => String(m) },
|
|
"./B05_Profile_UI_RouteEdit_Cross_Draw": {
|
|
buildCrossSvg: () => ({ svg: {}, layer: {}, width: 1, height: 1, content: {} }),
|
|
CROSS_PLOT_PAD: 0,
|
|
summarizeCross: () => "",
|
|
},
|
|
"./B05_Profile_UI_RouteEdit_Chrome": { sidePanelsOf: new WeakMap([[side, panels]]) },
|
|
"./B05_Profile_UI_RouteEdit_Cross_Card": {
|
|
routeCrossSource: (projectId, pipe, chainageM, pane) => ({
|
|
section: { station_id: `b05-${pane}-s${chainageM}` },
|
|
}),
|
|
routeCrossCard: (source) => (cards.push(source.section.station_id), fakeEl()),
|
|
},
|
|
"../A00_Common/design/design_pipeline_page": {
|
|
pagePipeline: () => ({
|
|
output: (name) => (name === "ground" ? { stations } : null),
|
|
input: () => route,
|
|
subscribe: () => () => undefined,
|
|
}),
|
|
},
|
|
"../A00_Common/design/design_store": {
|
|
useDesignStore: () => ({
|
|
subscribe: () => () => undefined,
|
|
get: (name) => (name === "footprint" ? footprint : null),
|
|
}),
|
|
},
|
|
"./logic/B05_Profile_Pipeline_Detail": {
|
|
stationSection: (station) => ({
|
|
chainage_m: station.chainageM,
|
|
label: null,
|
|
samples: [-20, 0, 20].map((offset_m) => ({ offset_m, elevation_m: 100, valid: true })),
|
|
}),
|
|
},
|
|
};
|
|
|
|
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) => {
|
|
if (STUBS[request]) return STUBS[request];
|
|
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
|
return require(request);
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
cache.set(full, box.exports);
|
|
return box.exports;
|
|
}
|
|
|
|
const { createCrossPreview } = loadTs(
|
|
path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts"),
|
|
);
|
|
const pick = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
|
|
// 페이지 노선 id 한 칸(386-2b) — 판이 읽는 적용값 = 이 노선 것만
|
|
const pageRoute = loadTs(path.join(ROOT, "A00_Common", "design", "design_page_route.ts"));
|
|
|
|
const record = (key, chainage_m, left, right, leftCut, rightCut) => ({
|
|
key,
|
|
chainage_m,
|
|
left_edge_m: 2.5,
|
|
left_end_m: left,
|
|
right_edge_m: -2.5,
|
|
right_end_m: right,
|
|
left_cut: leftCut,
|
|
right_cut: rightCut,
|
|
});
|
|
|
|
async function run() {
|
|
let shownCount = 0;
|
|
const cross = createCrossPreview({
|
|
projectId: "p-1",
|
|
side,
|
|
request: () => ({ vertices: [], min_radius_m: 12, station_interval_m: 20 }),
|
|
onShown: () => (shownCount += 1),
|
|
});
|
|
const out = {};
|
|
out.before = cross.watched();
|
|
await cross.open(100);
|
|
out.empty = { watched: cross.watched() };
|
|
pageRoute.setPageRouteId("p-1", 3);
|
|
session.setItem("aislo:draft:cross-display:p-1:3", "15");
|
|
await cross.refresh();
|
|
out.applied15 = { watched: cross.watched() };
|
|
session.setItem("aislo:draft:cross-display:p-1:12", "8.5");
|
|
session.setItem("aislo:draft:cross-display:p-2:99", "40");
|
|
await cross.refresh();
|
|
out.otherRoute = { watched: cross.watched() }; // 다른 노선 · 다른 프로젝트 값은 안 봄
|
|
pageRoute.setPageRouteId("p-1", 12);
|
|
await cross.refresh();
|
|
out.latestRoute = { watched: cross.watched() };
|
|
session.setItem("aislo:draft:cross-display:p-1:12", "abc");
|
|
await cross.refresh();
|
|
out.badValue = { watched: cross.watched() };
|
|
|
|
// 382-2 발자국 기록 = 비탈 끝 좌 · 우 · 깎기 여부
|
|
footprint = { records: [record("grid:5", 100, 7.5, -6, true, false)] };
|
|
out.footprint = cross.watched();
|
|
// 노선 고침 — 같은 열쇠 측점이 102 로 밀리고 발자국도 새로(열쇠로 다시 짚음)
|
|
stations = [
|
|
grid(2, 40),
|
|
grid(3, 60),
|
|
{ key: "pipe:a", kind: "pipe", chainageM: 80 },
|
|
grid(5, 102),
|
|
];
|
|
footprint = { records: [record("grid:5", 102, 9, -4, false, true)] };
|
|
route = { v: 2 };
|
|
cards.length = 0;
|
|
await cross.refresh();
|
|
out.afterEdit = cross.watched();
|
|
out.cardsAfterEdit = cards.slice();
|
|
|
|
// 평면 눈금 클릭 = 공용 고름에 적음 · 판이 접혀 있으면 폄
|
|
const heard = [];
|
|
pick.subscribeSelection("p-1", (selection, from) =>
|
|
heard.push({ at: selection.chainageM, from }),
|
|
);
|
|
panels.now = null;
|
|
await cross.open(41);
|
|
out.planClick = {
|
|
selection: pick.readSelection("p-1"),
|
|
heard: heard.slice(),
|
|
panel: panels.now,
|
|
remember: panels.remember,
|
|
watched: cross.watched(),
|
|
};
|
|
|
|
// 다른 보기 고름 — 일반측점 = 그 측점(판 폄) · 관 측점 · 구조물 id = 마름모 · 풂 = 없음
|
|
panels.now = null;
|
|
pick.selectDesign("p-1", { chainageM: 60, structureId: null }, { from: "b06-card" });
|
|
out.otherGrid = { watched: cross.watched(), panel: panels.now };
|
|
pick.selectDesign("p-1", { chainageM: 80, structureId: null }, { from: "profile" });
|
|
out.otherPipe = cross.watched();
|
|
pick.selectDesign("p-1", { chainageM: 60, structureId: "s-1" }, { from: "list" });
|
|
out.otherStructure = cross.watched();
|
|
const before = shownCount;
|
|
pick.selectDesign("p-1", { chainageM: null, structureId: null }, { from: "escape" });
|
|
out.cleared = { watched: cross.watched(), redrawn: shownCount > before };
|
|
|
|
// 393-4 — 보기 반폭 한 벌(B06 입력칸 · B05 판) = 세션 조절값 > 측점 저장 최댓값 > 기본 10
|
|
const { displayHalfWidth } = loadTs(
|
|
path.join(ROOT, "B06_Section", "B06_Section_Display_Width.ts"),
|
|
);
|
|
const saved = [12, 14, null].map((w) => ({ design: { display_half_width_m: w } }));
|
|
out.halfWidth = {
|
|
none: displayHalfWidth("p-9", 5, []),
|
|
saved: displayHalfWidth("p-9", 5, saved),
|
|
};
|
|
session.setItem("aislo:draft:cross-display:p-9:5", "18");
|
|
out.halfWidth.session = displayHalfWidth("p-9", 5, saved);
|
|
out.halfWidth.otherRoute = displayHalfWidth("p-9", 6, []);
|
|
out.halfWidth.noRoute = displayHalfWidth("p-9", null, saved);
|
|
return out;
|
|
}
|
|
run().then((out) => console.log(JSON.stringify(out)));
|