/* ============================================================================= * B05_Profile_UI_Panel_View.ts * 3D 화면 위에 얹는 **보기 조작 줄**(ISO·TOP·… · 표시 켬끔 · 뷰 초기화 · [3D 업데이트]). * 좌측 패널 칸이 아니라 뷰어에 붙는다 — `B05_Profile_UI_Panel.ts` 에서 떼어냈다(계획서 24-2b). * ========================================================================== */ import { L, button, toggleButton } from "./B05_Profile_UI_Panel_Fields"; /** 뷰 조작 줄이 부르는 것 — 좌측 패널 콜백의 일부다. */ export interface ViewControlCallbacks { onSurfaceVisible: (visible: boolean) => void; onCorridorVisible: (visible: boolean) => void; onContoursVisible: (visible: boolean) => void; onAxesVisible: (visible: boolean) => void; onStationLinesVisible: (visible: boolean) => void; onStationLabelsVisible: (visible: boolean) => void; onSurfaceGrayscale: (grayscale: boolean) => void; onView: (view: "iso" | "top" | "front" | "side") => void; onProjection: (kind: "perspective" | "ortho") => void; onResetView: () => void; onCorridorRefresh: () => void | Promise; } export function createViewControls(callbacks: ViewControlCallbacks): { viewControls: HTMLElement; /** [3D 업데이트] 대기 표시 — 편집이 3D에 아직 안 들어갔음을 버튼에 알린다. */ setCorridorPending: (pending: boolean) => void; } { const viewControls = document.createElement("div"); viewControls.className = "b05-route__view-controls"; const viewButtons = document.createElement("div"); viewButtons.className = "b05-route__view-group"; (["iso", "top", "front", "side"] as const).forEach((preset) => viewButtons.append(button(preset.toUpperCase(), () => callbacks.onView(preset), "glass")), ); // 직교/원근 전환(2026-09-04 사용자 지시) — 기본은 원근이고, 단추 글자는 **바뀔 쪽**을 // 가리킨다(누르면 그쪽으로 간다). let projection: "perspective" | "ortho" = "perspective"; const projectionButton = button( "직교로", () => { projection = projection === "perspective" ? "ortho" : "perspective"; projectionButton.textContent = projection === "perspective" ? "직교로" : "원근으로"; callbacks.onProjection(projection); }, "glass", ); viewButtons.append(projectionButton); const visibilityButtons = document.createElement("div"); visibilityButtons.className = "b05-route__view-group"; visibilityButtons.append( toggleButton("지표면", true, callbacks.onSurfaceVisible), // 예상형상(2026-08-23): ON=공사 후 형상(기본), OFF=원지반 완전체. toggleButton("예상형상", true, callbacks.onCorridorVisible), toggleButton("등고선", true, callbacks.onContoursVisible), toggleButton("축 표시", false, callbacks.onAxesVisible), toggleButton(L("B05_Route_Field_StationLines"), true, callbacks.onStationLinesVisible), toggleButton(L("B05_Route_Field_StationLabels"), true, callbacks.onStationLabelsVisible), // 무지개 고도색이 헷갈릴 때 명도만 남기는 흑백 표시(2026-08-05 사용자 요청). // 기본 켜짐(2026-08-23 사용자 확정) — 예상형상 색이 지형 고도색에 묻히지 않는다. toggleButton("흑백 지형", true, callbacks.onSurfaceGrayscale), ); // [3D 업데이트](2026-09-01 사용자 지시) — 계획선을 고칠 때마다 3D가 따라오면 조작이 // 무거워, 갱신을 이 버튼으로 모았다. 편집이 쌓이면 is-pending으로 알리고, 누르는 동안은 // 이 버튼만 잠근다(화면 전체 잠금 금지 — CLAUDE.md 5장). const corridorRefresh = button( "3D 업데이트", () => { corridorRefresh.disabled = true; corridorRefresh.textContent = "갱신 중…"; void Promise.resolve(callbacks.onCorridorRefresh()).finally(() => { corridorRefresh.disabled = false; corridorRefresh.textContent = "3D 업데이트"; corridorRefresh.classList.remove("is-pending"); }); }, "glass", ); corridorRefresh.title = "계획선 편집을 3D 예상형상·측점선에 반영합니다.\n편집이 쌓이면 버튼에 표시가 붙습니다."; const separator1 = document.createElement("span"); separator1.className = "b05-route__view-separator"; const separator2 = separator1.cloneNode() as HTMLSpanElement; const separator3 = separator1.cloneNode() as HTMLSpanElement; viewControls.append( viewButtons, separator1, visibilityButtons, separator2, button("뷰 초기화", callbacks.onResetView, "glass"), separator3, corridorRefresh, ); return { viewControls, setCorridorPending(pending: boolean) { corridorRefresh.classList.toggle("is-pending", pending); }, }; }