- 알약 글 = 좌측 「종류」 칸 · 목록 · 정보 창 1행과 같은 이름(약호 · 기타 머리 자르기 뺌) · 같은 이름 여럿이면 번호 - 알약 겹침 판정 = 이름 글 폭 어림(고정 52px 뺌) - 공용 우클릭 메뉴 안 click 은 메뉴에서 멈춤 — 배관 고른 채 군 머리를 누르면 그래프 빈 곳 클릭으로 새어 폼 리셋 · 메뉴 사라짐 - 시험: test_54_9_etc_pill 새 규칙 · 헬퍼 json 불러오기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
97 lines
3.5 KiB
JavaScript
97 lines
3.5 KiB
JavaScript
/* 54-9 종단 알약 글 단위검증 헬퍼 — TS 를 그 자리에서 트랜스파일해 Node 로 돌린다.
|
|
* 결과는 표준출력에 JSON 한 벌. 화면 그릇은 쓰지 않는다(세션 저장소만 흉내).
|
|
*
|
|
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_structures_form_summary.cjs` 머리와 같다.
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const cache = new Map();
|
|
|
|
const session = new Map();
|
|
const fakeStorage = {
|
|
getItem: (k) => session.get(k) ?? null,
|
|
setItem: (k, v) => session.set(k, String(v)),
|
|
removeItem: (k) => session.delete(k),
|
|
};
|
|
globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage };
|
|
globalThis.sessionStorage = fakeStorage;
|
|
globalThis.localStorage = fakeStorage;
|
|
|
|
const STUBS = {
|
|
"@config/config_frontend": { API_BASE_URL: "/api", API_TIMEOUT_MS: 1000 },
|
|
};
|
|
|
|
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.endsWith(".json")) return require(path.join(path.dirname(full), request));
|
|
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
|
if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} });
|
|
return require(request);
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
cache.set(full, box.exports);
|
|
return box.exports;
|
|
}
|
|
|
|
const marks = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structures_Marks.ts"));
|
|
|
|
const typeMap = new Map([
|
|
["etc", { type_id: "etc", name: "기타", style: { abbr: "기타" }, options: [] }],
|
|
[
|
|
"retaining_wall",
|
|
{ type_id: "retaining_wall", name: "옹벽", style: { abbr: "옹" }, options: [] },
|
|
],
|
|
]);
|
|
const etc = (id, chainage, name) => ({
|
|
structure_id: id,
|
|
type_id: "etc",
|
|
placement: "point",
|
|
chainage_m: chainage,
|
|
options: name === undefined ? {} : { name },
|
|
});
|
|
const wall = (id, chainage) => ({
|
|
structure_id: id,
|
|
type_id: "retaining_wall",
|
|
placement: "point",
|
|
chainage_m: chainage,
|
|
options: {},
|
|
});
|
|
const run = (structures) =>
|
|
Object.fromEntries(
|
|
[...marks.structurePillTexts(structures, typeMap)].map(([id, text]) => [id, text]),
|
|
);
|
|
|
|
const out = {
|
|
// 53-2 시나리오 — 같은 이름 둘 · 다른 이름 · 빈 이름 둘(빈칸 이름 = 빈 이름)
|
|
scenario: run([
|
|
etc("e1", 40, "배수로정비"),
|
|
etc("e2", 60, "울타리"),
|
|
etc("e3", 80, ""),
|
|
etc("e4", 100, "배수로정비"),
|
|
etc("e5", 120, " "),
|
|
]),
|
|
// 머리가 같은 다른 이름 — 갈릴 때까지 늘림 · 이름이 남의 머리면 그 이름 그대로
|
|
prefix: run([
|
|
etc("p1", 10, "콘크리트 블록"),
|
|
etc("p2", 20, "콘크리트 옹벽"),
|
|
etc("p3", 30, "배수로"),
|
|
etc("p4", 40, "배수로공"),
|
|
etc("p5", 50, "기타 시설"),
|
|
etc("p6", 60),
|
|
]),
|
|
// 다른 종류 규칙은 그대로 — 약호 + 중복 번호 · 기타 하나 빈 이름 = 「기타」
|
|
others: run([wall("w1", 10), wall("w2", 30), etc("x1", 20, "가로등"), etc("x2", 40)]),
|
|
};
|
|
process.stdout.write(JSON.stringify(out));
|