Files
Aislo/B11_Status/B11_Status_UI_Loading.ts
T
eomsangdonandClaude Opus 5 96561de120 perf(B04/B05): 확정 지표면 요약 API 신설 + 포인트클라우드 24MB 수신 제거
- GET /surface/confirmed 추가: 확정 구성(모델·필터·표현·평활·등고선간격)과
  지형 가장자리만 반환(수 KB, 0.02s). 진입 판정·준비화면·B05의 단일 출처.
- B05 진입이 받던 포인트클라우드 JSON 23.8MB 제거 — 실제로 쓰던 값은 bounds뿐.
- 준비 표식을 프로젝트 ID에서 확정 signature로 변경: B04에서 다시 확정하면
  대시보드 복귀·새 브라우저·B그룹 단계 이동 어느 경로로 들어와도 최신본을 담는다.
- preloadSurfaceAssets가 평활 여부를 추측하던 부분 제거(확정 저장값 사용) —
  추측이 어긋나면 같은 지형을 두 번 내려받았다.
- B04 진입 시 필터·표현·평활·등고선간격을 확정본 값으로 초기화(확정 없으면 기존 기본값).
- 모델 확정 직후 준비 표식과 B05 세션 캐시를 비워 옛 지형이 남지 않게 함.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 11:34:44 +09:00

95 lines
3.7 KiB
TypeScript

import { CURRENT_PROJECT_ID_KEY, ROUTES, type RoutePath } from "@config/config_frontend";
import { createButton } from "@ui/ui_template_elements";
import { createGeneralLayout } from "@ui/ui_template_general_layout";
import { t } from "@ui/ui_template_locale";
import { createProgressCircle } from "@ui/ui_template_progress";
import {
markProjectPreloaded,
preloadSurfaceAssets,
purgeOtherProjects,
readPreloadTarget,
} from "../A00_Common/b_asset_cache";
import { navigateTo } from "../A00_Common/router";
import "./B11_Status_UI_Style.css";
/* =============================================================================
* 자료 준비 화면 (B11)
*
* 대시보드에서 프로젝트를 골라 작업 화면으로 들어갈 때, 먼저 이 화면이 3D 지표면과
* 등고선을 브라우저에 담아 둔다. 담아 두면 B04·B05가 그것을 그대로 쓰므로 화면이 바로 뜬다.
* 이미 담겨 있으면 순식간에 지나간다.
*
* 자료를 준비하지 못하면(분석 실패·저장 경로 문제) 넘어가지 않고 사유를 알린다.
* ========================================================================== */
/** 준비에 실패한 채 그냥 넘어갔을 때 남기는 표식 — 어떤 확정 구성과도 일치하지 않는다. */
const PRELOAD_SKIPPED_SIGNATURE = "skipped";
export async function renderB11Loading(root: HTMLElement): Promise<void> {
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
const target = (readPreloadTarget() ?? ROUTES.B03_FILE_INPUT) as RoutePath;
const progress = createProgressCircle({ label: t("B11_Loading_Start"), size: 96 });
const message = document.createElement("p");
message.className = "b11-loading__message";
message.textContent = t("B11_Loading_Message");
const actions = document.createElement("div");
actions.className = "b11-loading__actions";
const body = document.createElement("div");
body.className = "b11-loading__body";
body.append(progress.root, message, actions);
const layout = createGeneralLayout({
pageClass: "b11-status",
title: t("B11_Loading_Title"),
subtitle: t("B11_Loading_Subtitle"),
content: [body],
});
root.replaceChildren(layout.root);
if (!projectId) {
showFailure(t("B11_Loading_NoProject"));
return;
}
try {
// 다른 프로젝트 자료가 남아 있으면 지운다 — 프로젝트끼리 섞이면 안 된다.
await purgeOtherProjects(projectId);
const signature = await preloadSurfaceAssets(projectId, (label, ratio) => {
progress.set(ratio, label);
});
// 표식은 담아 둔 구성 그대로 남긴다 — 확정이 바뀌면 다음 진입에서 다시 준비한다.
markProjectPreloaded(projectId, signature);
navigateTo(target);
} catch (error) {
const detail = error instanceof Error ? ` (${error.message})` : "";
showFailure(`${t("B11_Loading_Failed")}${detail}`);
}
function showFailure(text: string): void {
progress.root.hidden = true;
message.textContent = text;
message.classList.add("b11-loading__message--error");
actions.replaceChildren(
createButton({
label: t("B11_Loading_Btn_Continue"),
variant: "ghost",
// 준비를 못 했어도 같은 세션에서 다시 붙잡지 않도록 표시해 둔다.
// 확정 구성을 모르는 상태이므로 전용 표식을 남긴다 — 확정이 정상화되면 표식이
// 어긋나 준비 화면이 다시 뜬다.
onClick: () => {
if (projectId) markProjectPreloaded(projectId, PRELOAD_SKIPPED_SIGNATURE);
navigateTo(target);
},
}),
createButton({
label: t("B11_Loading_Btn_Dashboard"),
variant: "filled",
onClick: () => navigateTo(ROUTES.B01_ACCOUNT),
}),
);
}
}