- design_view_profile_sections: 노선마다 상세 갈아끼움을 듣고 종단도 · 표 · 3D 측점선 다시(보일 때만 · 숨으면 flushSwap 이 다시 보일 때) · 가게 stations 도 들어 횡단 보기를 안 열어도 갈아끼움(먼저 부른 쪽만 · 다음은 「같은 목록」) - design_view_profile: 받는 줄만(보임 · 떠날 때 끊음 · 다시 보일 때 flushSwap) - 갈아끼움 알림판 B06_Section_Swap_Bus(가져오기 없음 · 돌고 도는 가져오기 막음) — 조정창 값 · 암 경계 옮김을 어느 보기가 갈아끼웠든 한 번 · 횡단 보기는 알림으로 다시 그림 - 시험: 종단 보기 보임 7 → 8 · 숨음 = 안 그림 · 다시 보임 = 6 · 3D 측점선 같은 차례 · 옛 시험 둘(342-1 화면 선 = listedBase · 323-9 열쇠 모듈 진짜로) 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
266 lines
8.9 KiB
JavaScript
266 lines
8.9 KiB
JavaScript
/* 346-2 4파 헬퍼 — 측점 셋 늘고 둘 빠진 목록으로 상세 갈아끼움 · 카드 수 · 열쇠 이어 받기 · 보이는 카드 먼저.
|
|
* node helper_346_2_station_swap.mjs <묶음.mjs>
|
|
* vite ssr 로 묶은 진짜 모듈(helper_346_2_swap_entry.ts) · 표준출력 JSON 한 벌.
|
|
*/
|
|
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(),
|
|
};
|
|
};
|
|
globalThis.window = globalThis;
|
|
globalThis.sessionStorage = store();
|
|
globalThis.localStorage = store();
|
|
const any = new Proxy(function () {}, {
|
|
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
|
|
apply: () => any,
|
|
construct: () => any,
|
|
});
|
|
globalThis.document = any;
|
|
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
|
|
const toasts = [];
|
|
globalThis.__toasts = toasts;
|
|
|
|
const m = await import(pathToFileURL(process.argv[2]).href);
|
|
const P = "p346";
|
|
const R = 9;
|
|
const out = {};
|
|
|
|
/* 옛 목록 7 — 시점 · 격자 넷 · 관 하나 · 종점(간격 20 m) */
|
|
const section = (chainage_m, kind, extra = {}) => ({
|
|
station_id: `station_${String(Math.round(chainage_m * 1000)).padStart(12, "0")}`,
|
|
chainage_m,
|
|
label: "",
|
|
kind,
|
|
center_z: 100,
|
|
azimuth_deg: 90,
|
|
center_x: chainage_m,
|
|
center_y: 0,
|
|
frame: { left_xy: [0, 1] },
|
|
samples: [{ offset_m: 0, elevation_m: 100, valid: true }],
|
|
design: { design_elevation_m: 101, mark: `old-${chainage_m}` },
|
|
...extra,
|
|
});
|
|
const oldDetail = () => ({
|
|
longitudinal: {
|
|
length_m: 100,
|
|
stations: [],
|
|
profile_alignment: { policy: { station_interval_m: 20 } },
|
|
},
|
|
cross_sections: [
|
|
section(0, "bp"),
|
|
section(20, "regular"),
|
|
section(40, "regular"),
|
|
section(45.054, "irregular", { structure: "파형강관 D800", culvert: {} }),
|
|
section(60, "regular"),
|
|
section(80, "regular"),
|
|
section(100, "ep"),
|
|
],
|
|
});
|
|
const OLD_KEYS = ["end:BP", "grid:1", "grid:2", "pipe:pipe-s1", "grid:3", "grid:4", "end:EP"];
|
|
|
|
/* 새 목록 8 — 셋 늘고(st-1 · st-2 · st-3) 둘 빠짐(grid:2 · grid:4) · 관 47 m 로 · 종점 90 m 로 */
|
|
const station = (key, chainageM, kind, id = null, dy = 0) => ({
|
|
key,
|
|
chainageM,
|
|
x: chainageM,
|
|
y: dy,
|
|
azimuthDeg: 90,
|
|
left: [0, 1],
|
|
kind,
|
|
id,
|
|
ground: {
|
|
centerZ: 99,
|
|
left: [{ offsetM: 0.5, z: 99.1 }],
|
|
right: [{ offsetM: 0.5, z: 98.9 }],
|
|
},
|
|
planRadiusM: null,
|
|
curveOuterSide: null,
|
|
curveWideningM: null,
|
|
});
|
|
const NEW = (dy) => [
|
|
station("end:BP", 0, "end", null, dy),
|
|
station("grid:1", 20, "grid", null, dy),
|
|
station("pipe:pipe-s1", 47, "pipe", "pipe-s1", dy),
|
|
station("structure:st-1", 52, "structure", "st-1", dy),
|
|
station("grid:3", 60, "grid", null, dy),
|
|
station("structure:st-2", 65, "structure", "st-2", dy),
|
|
station("structure:st-3", 75, "structure", "st-3", dy),
|
|
station("end:EP", 90, "end", null, dy),
|
|
];
|
|
|
|
/* 1. 순수 갈아끼움 */
|
|
{
|
|
const before = oldDetail();
|
|
const swap = m.swapCrossSections(before, NEW(0), OLD_KEYS, 20);
|
|
out.pure = {
|
|
count: swap.detail.cross_sections.length,
|
|
longCount: swap.detail.longitudinal.stations.length,
|
|
added: swap.added,
|
|
removed: swap.removed,
|
|
moves: Object.fromEntries(swap.moves),
|
|
moved: swap.moved,
|
|
keptMark: swap.detail.cross_sections.map((s) => s.design?.mark ?? null),
|
|
kinds: swap.detail.cross_sections.map((s) => s.kind),
|
|
labels: [swap.detail.cross_sections[2].label, swap.detail.cross_sections[7].label],
|
|
samples: swap.detail.cross_sections[1].samples.map((s) => s.offset_m),
|
|
oldUntouched:
|
|
before.cross_sections.length === 7 && before.cross_sections[3].chainage_m === 45.054,
|
|
length: swap.detail.longitudinal.length_m,
|
|
same: m.sameStationList(before.cross_sections, OLD_KEYS, NEW(0)),
|
|
sameSelf: m.sameStationList(
|
|
swap.detail.cross_sections,
|
|
NEW(0).map((s) => s.key),
|
|
NEW(0),
|
|
),
|
|
};
|
|
out.format = [0, 45.054, 1234.5].map((c) => m.formatStation(c));
|
|
}
|
|
|
|
/* 2. 가게 → 상세 갈아끼움 · 열쇠 이어 받기 · 노선 밖 고침 */
|
|
{
|
|
m.writeState("section-detail", oldDetail(), P, R);
|
|
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 45.054 }] }, P);
|
|
const first = await m.loadSectionDetail(P, R); // 세션 한 벌 → 캐시 · 측점 열쇠 세움
|
|
const choice = (ground) => ({
|
|
ground_type: ground,
|
|
section_mode: "right_cut",
|
|
ditch_side: "right",
|
|
});
|
|
m.writeCrossDesignChoice(P, R, 20, choice("soil"));
|
|
m.writeCrossDesignChoice(P, R, 40, choice("soil"));
|
|
m.writeCrossDesignChoice(P, R, 45.054, choice("blasting_rock"));
|
|
const widths = m.createSessionMap(
|
|
() => "aislo:test:crossw",
|
|
(v) => v,
|
|
);
|
|
widths.values.set("45.05", 12);
|
|
widths.values.set("45.05:outlet", 3);
|
|
widths.values.set("20.00", 8);
|
|
widths.persist(); // 조정창은 바꿀 때마다 세션에 씀(제어기와 같은 차례)
|
|
const heard = [];
|
|
// 관 · 구조물 자리 장부 — 새 목록에서 관은 47 m · 구조물 id 는 측점 계획이 줌
|
|
m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 47 }] }, P);
|
|
m.useStructureIds(P, [
|
|
{ structure_id: "st-1", chainage_m: 52 },
|
|
{ structure_id: "st-2", chainage_m: 65 },
|
|
{ structure_id: "st-3", chainage_m: 75 },
|
|
]);
|
|
const stopListen = m.subscribeSectionDetail(P, R, (detail, moves) =>
|
|
heard.push({ count: detail.cross_sections.length, moves: Object.fromEntries(moves) }),
|
|
);
|
|
let redraws = 0;
|
|
const stop = m.followSwaps(
|
|
P,
|
|
() => R,
|
|
{ visible: () => true },
|
|
async () => {
|
|
redraws += 1;
|
|
},
|
|
);
|
|
m.useDesignStore(P, null).set("stations", NEW(0.5)); // 좌표가 움직임 = 노선 모양 바뀜
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
const now = await m.loadSectionDetail(P, R);
|
|
out.store = {
|
|
replaced: now !== first,
|
|
count: now.cross_sections.length,
|
|
heard,
|
|
choices: [...m.crossDesignChoices(P, R).entries()]
|
|
.sort((a, b) => a[0] - b[0])
|
|
.map(([at, c]) => [at, c.ground_type, c.section_mode ?? null]),
|
|
rawKeys: Object.keys(m.readState("crossdesign", P, R) ?? {})
|
|
.filter((k) => k !== "@at")
|
|
.sort(),
|
|
widths: Object.fromEntries(widths.values),
|
|
redraws,
|
|
};
|
|
// 같은 목록을 다시 내면 갈아끼우지 않음
|
|
const again = await m.swapSectionStations(P, R, NEW(0.5));
|
|
out.store.again = again === null;
|
|
stop();
|
|
stopListen();
|
|
}
|
|
|
|
/* 2-2. 종단 보기(B05) · 3D 측점선 — 횡단 보기 없이도 가게 측점으로 갈아끼우고 보일 때만 다시 그림 */
|
|
{
|
|
const R2 = 11;
|
|
m.writeState("section-detail", oldDetail(), P, R2);
|
|
const renders = [];
|
|
const lines = [];
|
|
let shown = true;
|
|
const sections = m.createProfileSections({
|
|
projectId: P,
|
|
state: {
|
|
confirmedSurface: null,
|
|
latest: { route_points: [] },
|
|
roadWidths: { g: 5 },
|
|
detail: null,
|
|
corridorVisible: false,
|
|
corridorPending: false,
|
|
routeReady: true,
|
|
restoring: false,
|
|
},
|
|
viewer: { renderStationLines: (list) => lines.push(list.length), markers: {} },
|
|
panel: () => ({
|
|
values: () => ({ gradeClass: "g", stationInterval: 20, enforcePipeClearance: false }),
|
|
gradeLimitPct: () => 12,
|
|
stationDisplayOffset: () => ({ station: 0, cumulative: 0 }),
|
|
setCorridorPending: () => undefined,
|
|
}),
|
|
profilePanel: () => ({
|
|
render: (detail) => renders.push(detail.cross_sections.length),
|
|
setGradeLimit: () => undefined,
|
|
setEnforceMinCover: () => undefined,
|
|
setStationDisplay: () => undefined,
|
|
setIrregularStations: () => undefined,
|
|
alignmentSamples: () => null,
|
|
setLoading: () => undefined,
|
|
clear: () => undefined,
|
|
}),
|
|
bridge: () => ({ irregularStations: () => [] }),
|
|
visible: () => shown,
|
|
});
|
|
await sections.restoreSections(R2);
|
|
const shop = m.useDesignStore(P, null);
|
|
shop.set("stations", NEW(0)); // 보임 — 바로 다시
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
const afterShown = [...renders];
|
|
shown = false;
|
|
shop.set("stations", NEW(0).slice(0, 6)); // 숨음 — 안 그림
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
const whileHidden = [...renders];
|
|
shown = true;
|
|
sections.flushSwap(); // 다시 보임
|
|
out.profile = { afterShown, whileHidden, afterShow: [...renders], lines: lines.length };
|
|
sections.stopSwap();
|
|
}
|
|
|
|
/* 3. 보이는 카드 먼저 — 바로 그릴 수 */
|
|
out.eager = [
|
|
m.eagerCardCount({ clientHeight: 600, scrollTop: 0 }, 3, 250, 12),
|
|
m.eagerCardCount({ clientHeight: 600, scrollTop: 1310 }, 3, 250, 12),
|
|
m.eagerCardCount({ clientHeight: 0, scrollTop: 0 }, 2, 250, 12),
|
|
];
|
|
out.rename = Object.fromEntries(
|
|
m.renameChainageKeys(
|
|
new Map([
|
|
["45.05", 1],
|
|
["b45.05", 2],
|
|
["45.05:outlet", 3],
|
|
["60.00", 4],
|
|
]),
|
|
new Map([["45.05", "47.00"]]),
|
|
),
|
|
);
|
|
|
|
process.stdout.write(JSON.stringify(out));
|