- 카드 하단 ◀/▶/↺(개별 표시 반폭) 삭제. 조작구는 우측 상단 줌 버튼 하나로. - 줌 버튼 역할 확장(원배율에서만 반폭 조작): − 배율>1이면 축소, 원배율이면 표시 반폭 +1m + 배율>1이면 확대, 원배율이면 표시 반폭 −1m(하한 2m) ⤢ 배율 원복 + 절·성토 교차점이 다 보이는 폭으로 맞춤 - 교차점 맞춤(_UI_Cross_Fit): 보유 샘플(계산 반폭 20m) 안에서 교차점을 찾고, 샘플 끝까지 안 만나면 끝단 사면·지반 기울기 차로 만나는 자리를 추세 외삽해 올림 +1m을 요청 반폭으로 쓴다(상한 60m). - 요청 반폭 > 보유 샘플이면 그 폭으로 재생성(regenerate) 후 해당 측점만 세션 반폭으로 넓게 표시. 계산 기본 20m·캐시는 그대로, 확정 시 기존 경로대로 display_half_width_m에 영구 저장. - 재생성 로직을 _UI_Page_Common.createSampleWidener로 분리(700줄 제한), [전체 측점 반영]도 같은 경로 사용. 연타 중복 요청 차단. 자체검증: node tmp/tests/test_toe_extrapolate.mjs (외삽 좌/우·발산 지형·+1m 올림), tsc --noEmit 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
68 lines
3.0 KiB
TypeScript
68 lines
3.0 KiB
TypeScript
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
|
import { hideLoadingOverlay, showLoadingOverlay, showToast } from "@ui/ui_template_elements";
|
|
import {
|
|
fetchSectionDetail,
|
|
regenerateSections,
|
|
type SectionDetailResponse,
|
|
} from "./B06_Section_Api_Fetch";
|
|
import { replaceSectionDetail } from "./B06_Section_Section_Store";
|
|
|
|
export function L(key: keyof typeof ui_locales): string {
|
|
return ui_locales[key][currentLanguageIndex];
|
|
}
|
|
|
|
export function buildGroup(legend: string, collapsed = false): HTMLElement {
|
|
const group = document.createElement("section");
|
|
group.className = `b06-profile__group ui-collapsible ui-sidebar-section${collapsed ? " is-collapsed" : ""}`;
|
|
const title = document.createElement("h3");
|
|
title.className = "b06-profile__group-legend ui-collapsible__title";
|
|
title.textContent = legend;
|
|
group.append(title);
|
|
return group;
|
|
}
|
|
|
|
/** 반폭 확대(백엔드 재생성)에 필요한 페이지 상태 — 클로저 대신 함수로 받아 결합을 끊는다. */
|
|
export interface SampleWidenerDeps {
|
|
/** 현재 대상 경로. 없으면 확대 불가. */
|
|
target: () => { projectId: string; routeId: number } | null;
|
|
/** 보유 샘플의 최대 반폭(m). */
|
|
sampledHalfWidth: () => number;
|
|
/** 재생성으로 받은 새 상세를 페이지 상태에 반영하고 다시 그린다. */
|
|
applyDetail: (detail: SectionDetailResponse) => void;
|
|
}
|
|
|
|
/**
|
|
* 요청 반폭까지 지반 샘플을 넓히는 함수를 만든다 — 보유 샘플로 충분하면 즉시 true
|
|
* (재계산 없음). 모자라면 그 폭으로 B05부터 재생성(영구 저장)하고 상세를 다시 받는다.
|
|
* 느려도 허용(2026-08-06 사용자 확정). 전역 [전체 측점 반영]과 카드 줌 버튼의 반폭
|
|
* 확대가 같은 경로를 쓴다(2026-08-23 사용자 지시).
|
|
*/
|
|
export function createSampleWidener(deps: SampleWidenerDeps): (widthM: number) => Promise<boolean> {
|
|
let running = false;
|
|
return async (widthM) => {
|
|
const target = deps.target();
|
|
if (!target) return false;
|
|
if (widthM <= deps.sampledHalfWidth() + 1e-6) return true;
|
|
if (running) return false; // 재생성 중 중복 요청은 무시(버튼 연타).
|
|
running = true;
|
|
showLoadingOverlay();
|
|
try {
|
|
await regenerateSections(target.projectId, target.routeId, widthM);
|
|
// 재생성 응답에는 설계가 없다 — 상세를 다시 받아 기본 설계 프리뷰까지 얹는다.
|
|
const fresh = await fetchSectionDetail(target.projectId, target.routeId);
|
|
// 공유 캐시도 새 상세로 바꿔 B05가 옛 값을 못 보게 한다.
|
|
replaceSectionDetail(target.projectId, target.routeId, fresh);
|
|
deps.applyDetail(fresh);
|
|
showToast(L("B06_Profile_Regenerate_Success"), "success");
|
|
return true;
|
|
} catch (error) {
|
|
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
showToast(`${L("B06_Profile_Regenerate_Failed")}${detail}`, "error");
|
|
return false;
|
|
} finally {
|
|
running = false;
|
|
hideLoadingOverlay();
|
|
}
|
|
};
|
|
}
|