fix(B04): 배수유역 상태 문구 전용 줄 분리 + 저장분 없을 때 안내
증상 두 가지
1. 재산정 버튼을 눌러도 프론트에 아무 안내가 없었다.
오버레이가 지도 자체 상태(.b04-map__status)에 문구를 썼는데,
loadLayers 가 같은 칸에 3곳에서 덮어써 즉시 지워졌다.
2. 페이지 접근 시 배수유역이 안 보였다.
응답 캐시 파일(00_watershed_response.json)이 아직 없어 자동 조회가
빈 손으로 끝나는데, 그 사실을 화면에 알리지 않아 이유를 알 수 없었다.
조치
- 배수유역 전용 상태 줄(.b04-map__watershed-status)을 컨트롤 영역에 따로 둔다.
지도 로딩 문구와 서로 덮어쓰지 않는다.
- 컨트롤에 배수유역 그룹을 만들어 재산정 버튼 + 갈래 토글 4종 + 상태 줄을 묶었다.
- 진행 상태를 버튼 라벨에도 반영: 배수유역 재산정 -> 분석 중… / 불러오는 중…
- 결과 문구에 출처와 소요를 붙인다: [저장분] ... / [재산정 28.4초] ...
- 저장분이 없으면 오류가 아니라 안내로 표시:
"저장된 배수유역 분석이 없습니다. [배수유역 재산정]을 누르세요."
- 실패하면 버튼 활성 상태를 되돌려 껐다 켠 것처럼 보이지 않게 했다.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -222,12 +222,18 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
gisButtons.append(contourLabelButton);
|
||||
|
||||
// 배수유역 분석 오버레이 — 계산은 백엔드가 하고 여기서는 겹쳐 그리기만 한다.
|
||||
const watershed = createWatershedOverlay(() => {
|
||||
const text = watershed.status();
|
||||
if (text) status.textContent = text;
|
||||
scheduleDraw();
|
||||
});
|
||||
gisButtons.append(watershed.button, ...watershed.partButtons);
|
||||
// 상태 문구는 오버레이 전용 줄에 쓴다 — 지도 자체 상태(레이어 로딩)와 같은 칸을 쓰면
|
||||
// 나중에 끝난 쪽이 상대 문구를 지워 버린다.
|
||||
const watershed = createWatershedOverlay(() => scheduleDraw());
|
||||
const watershedGroup = document.createElement("div");
|
||||
watershedGroup.className = "b04-map__control-group";
|
||||
const watershedTitle = document.createElement("span");
|
||||
watershedTitle.textContent = "배수유역";
|
||||
const watershedButtons = document.createElement("div");
|
||||
watershedButtons.className = "b04-map__layer-buttons";
|
||||
watershedButtons.append(watershed.button, ...watershed.partButtons);
|
||||
watershedGroup.append(watershedTitle, watershedButtons, watershed.statusElement);
|
||||
controls.insertBefore(watershedGroup, resetButton);
|
||||
|
||||
function updateImageTransform(): void {
|
||||
backgroundImages.forEach((image) => {
|
||||
|
||||
@@ -624,6 +624,25 @@
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
/* 배수유역 전용 상태 줄 — 지도 자체 상태(.b04-map__status)와 칸을 나눠 쓰면
|
||||
나중에 끝난 쪽이 상대 문구를 지운다. 그래서 컨트롤 영역에 따로 둔다. */
|
||||
.b04-map__watershed-status {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border-radius: var(--radius-inputs);
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
line-height: 1.5;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.b04-map__watershed-status[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.b04-map__header {
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -50,8 +50,10 @@ type PartKey = (typeof PARTS)[number]["key"];
|
||||
export interface WatershedOverlay {
|
||||
/** 분석 실행 + 전체 토글 버튼. 지도 헤더의 GIS 버튼 줄에 넣는다. */
|
||||
button: HTMLButtonElement;
|
||||
/** 갈래별 표시 토글 버튼(1차 유역 / 2차 유역 / 유역 방향). */
|
||||
/** 갈래별 표시 토글 버튼(1차 유역 / 2차 유역 / 유역 방향 / 평균 흐름). */
|
||||
partButtons: HTMLButtonElement[];
|
||||
/** 배수유역 전용 상태 줄. 지도 자체 상태(레이어 로딩 등)와 섞이면 서로 덮어쓴다. */
|
||||
statusElement: HTMLElement;
|
||||
/** 켜져 있는지. draw() 호출 전에 확인한다. */
|
||||
visible: () => boolean;
|
||||
/** 상태 문구(분석 요약 또는 오류). 없으면 빈 문자열. */
|
||||
@@ -70,6 +72,17 @@ export function createWatershedOverlay(onChange: () => void): WatershedOverlay {
|
||||
let flowCache: { source: string; bytes: Uint8Array } | null = null;
|
||||
let busy = false;
|
||||
|
||||
const statusElement = document.createElement("span");
|
||||
statusElement.className = "b04-map__watershed-status";
|
||||
statusElement.hidden = true;
|
||||
|
||||
/** 상태 줄을 갱신한다. 빈 문자열이면 줄 자체를 숨긴다. */
|
||||
function say(text: string): void {
|
||||
statusText = text;
|
||||
statusElement.textContent = text;
|
||||
statusElement.hidden = text === "";
|
||||
}
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b04-map__layer-button b04-map__layer-button--gis";
|
||||
@@ -457,31 +470,44 @@ export function createWatershedOverlay(onChange: () => void): WatershedOverlay {
|
||||
}
|
||||
|
||||
/** 분석 결과를 받아 화면에 올린다.
|
||||
*
|
||||
|
||||
* `refresh=false`면 영구저장소에 남은 결과를 그대로 받아 즉시 끝나므로 지도를 열 때
|
||||
* 자동으로 부른다. `refresh=true`(재산정 버튼)면 처음부터 다시 계산한다. */
|
||||
async function loadAnalysis(refresh: boolean): Promise<void> {
|
||||
if (!projectId || busy) return;
|
||||
busy = true;
|
||||
button.disabled = true;
|
||||
statusText = refresh
|
||||
? "배수유역을 다시 분석하는 중… (30초 안팎)"
|
||||
: "저장된 배수유역을 불러오는 중…";
|
||||
onChange();
|
||||
button.textContent = refresh ? "분석 중…" : "불러오는 중…";
|
||||
say(
|
||||
refresh
|
||||
? "배수유역을 처음부터 다시 분석하는 중입니다. 30초 안팎 걸립니다…"
|
||||
: "저장된 배수유역 분석을 불러오는 중…",
|
||||
);
|
||||
const started = performance.now();
|
||||
try {
|
||||
analysis = await fetchWatershedAnalysis(projectId, refresh);
|
||||
shown = true;
|
||||
button.classList.add("is-active");
|
||||
button.setAttribute("aria-pressed", "true");
|
||||
statusText = regionSummary(analysis);
|
||||
const seconds = ((performance.now() - started) / 1000).toFixed(1);
|
||||
const origin = analysis.from_cache ? "저장분" : `재산정 ${seconds}초`;
|
||||
say(`[${origin}] ${regionSummary(analysis)}`);
|
||||
} catch (error) {
|
||||
// 저장분이 없어 자동 조회가 실패한 경우는 오류가 아니다 — 재산정하면 된다.
|
||||
analysis = null;
|
||||
shown = false;
|
||||
button.classList.remove("is-active");
|
||||
button.setAttribute("aria-pressed", "false");
|
||||
const message = error instanceof Error ? error.message : "배수유역을 불러오지 못했습니다.";
|
||||
statusText = refresh ? message : "";
|
||||
if (!refresh) analysis = null;
|
||||
// 저장분이 아직 없는 것은 오류가 아니다 — 무엇을 눌러야 하는지 알려 준다.
|
||||
say(
|
||||
refresh
|
||||
? `배수유역 재산정 실패: ${message}`
|
||||
: "저장된 배수유역 분석이 없습니다. [배수유역 재산정]을 누르세요.",
|
||||
);
|
||||
} finally {
|
||||
busy = false;
|
||||
button.disabled = false;
|
||||
button.textContent = "배수유역 재산정";
|
||||
onChange();
|
||||
}
|
||||
}
|
||||
@@ -492,7 +518,7 @@ export function createWatershedOverlay(onChange: () => void): WatershedOverlay {
|
||||
shown = false;
|
||||
button.classList.remove("is-active");
|
||||
button.setAttribute("aria-pressed", "false");
|
||||
statusText = "";
|
||||
say("");
|
||||
onChange();
|
||||
return;
|
||||
}
|
||||
@@ -502,13 +528,14 @@ export function createWatershedOverlay(onChange: () => void): WatershedOverlay {
|
||||
return {
|
||||
button,
|
||||
partButtons,
|
||||
statusElement,
|
||||
visible: () => shown && analysis !== null,
|
||||
status: () => statusText,
|
||||
reset() {
|
||||
analysis = null;
|
||||
flowCache = null;
|
||||
shown = false;
|
||||
statusText = "";
|
||||
say("");
|
||||
button.classList.remove("is-active");
|
||||
button.setAttribute("aria-pressed", "false");
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user