- 새 D03_PlanAnalysis_UI_Site — 지금 주소 · 찾기(createFindList · searchPlaces) · 실패 · 끊김 한 줄 + [다시 받기] · [임도망 올리기] - 위치 = 캐시 한 걸음(Snap site · 판 5 · 되돌리기 · 떠남 물음) → [저장] 몸 site · 지점 이름 point_names - 고름 → 지도 이동 → 둘레 받기(center) · 멂 · 다른 TM 띠 = 물음 한 번 뒤 reset · 아니오 = 위치 되돌림 - 임도망 올린 뒤 지도 맞춤 = 주소지 둘레 ∪ 잘린 임도 상자(없으면 주소지) - 조용히 받기 = 기준점 10 km 안 지점 · 선만 · 칸 글 걷음(실패 · 끊김 · 기준점 없음 · 못 불러옴만) - 등록(D01) 지도 머리 [위성] 오른쪽 찾기 = 같은 createPlaceFind · 사전 DE_Reg_Find* - 시험 553_4 위치 칸 · 등록 찾기 부품 · 560_4 savedJson 꼴 · 584_3 올리기 자리(위치 칸) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
/* =============================================================================
|
|
* D03_PlanAnalysis_UI_Site.ts
|
|
* D03 「위치」 칸(PLAN 596-2 · 요구 463-3~6 · 19) — 지금 기준점 주소 한 줄 · 주소 · 장소 찾기
|
|
* (서버 VWorld `searchPlaces` · 부품 `createFindList` · Enter = 찾기 · ↑ ↓ Enter = 고름) ·
|
|
* 알림 한 줄(받기 실패 · 끊김 · 기준점 없음 · 못 불러옴만) + [다시 받기] · [임도망 올리기].
|
|
* 고름 = 페이지가 받기 물음(멂 · 다른 TM 띠) → 기준점 캐시 → 지도 이동 → 둘레 받기 · [저장] 때 정본(등록과 한 칸).
|
|
* ========================================================================== */
|
|
|
|
import { pickFiles } from "@ui/ui_template_drop";
|
|
import { createButton } from "@ui/ui_template_elements";
|
|
import { createFindList, type FindList } from "@ui/ui_template_find_list";
|
|
import { t } from "@ui/ui_template_locale";
|
|
import { createText } from "@ui/ui_template_text";
|
|
import { searchPlaces, type PlaceHit } from "./D03_PlanAnalysis_Api";
|
|
|
|
/** 찾기 글 최소 길이(서버 VWorld 한 글자 = 쓸모없는 후보). */
|
|
const MIN_QUERY = 2;
|
|
|
|
export interface PlanSiteParams {
|
|
/** 후보 고름 — 받기 · 캐시 · 이동은 페이지. */
|
|
onPick: (hit: PlaceHit) => void;
|
|
onRetry: () => void;
|
|
onUpload: (files: File[]) => void;
|
|
}
|
|
|
|
export interface PlanSiteState {
|
|
address: string | null;
|
|
/** 알림(실패 · 끊김 · 기준점 없음 · 오류) — 빈 값 = 숨김. */
|
|
note: string;
|
|
retry: boolean;
|
|
editable: boolean;
|
|
}
|
|
|
|
export interface PlanSite {
|
|
/** 칸 몸(카드에 바로 붙임 · 카드 간격을 그대로 씀). */
|
|
parts: HTMLElement[];
|
|
render: (state: PlanSiteState) => void;
|
|
}
|
|
|
|
/** 주소 · 장소 찾기 칸(부품 `createFindList`) — Enter = 찾기 · ↑ ↓ Enter · 누름 = 고름 ·
|
|
* 등록(D01) 지도 머리 · 이 「위치」 칸이 같이 씀. */
|
|
export function createPlaceFind(onPick: (hit: PlaceHit) => void): FindList {
|
|
const find = createFindList({ placeholder: t("DE_Reg_Find") });
|
|
let asked = 0; // 옛 찾기 답 버림
|
|
|
|
async function run(): Promise<void> {
|
|
const query = find.search.value.trim();
|
|
find.setRows([]);
|
|
if (query.length < MIN_QUERY) return void (find.hint.textContent = t("DE_Reg_FindShort"));
|
|
const at = ++asked;
|
|
find.hint.textContent = t("DE_Reg_FindBusy");
|
|
try {
|
|
const hits = await searchPlaces(query);
|
|
if (at !== asked) return;
|
|
find.hint.textContent = hits.length ? "" : t("DE_Reg_FindNone");
|
|
find.setRows(
|
|
hits.map((hit, i) => ({
|
|
key: String(i),
|
|
tag: t(hit.kind === "place" ? "DE_Reg_FindPlace" : "DE_Reg_FindAddress"),
|
|
name: hit.title,
|
|
sub: hit.address !== hit.title ? hit.address : undefined,
|
|
onPick: () => {
|
|
find.setRows([]);
|
|
find.search.value = hit.title;
|
|
onPick(hit);
|
|
},
|
|
})),
|
|
);
|
|
} catch (error) {
|
|
if (at === asked)
|
|
find.hint.textContent = `${t("DE_Reg_FindFail")} — ${error instanceof Error ? error.message : error}`;
|
|
}
|
|
}
|
|
// 고를 줄이 있으면 부품이 Enter 를 먼저 씀(고름) · 없으면 찾기 · 글을 고치면 옛 줄 걷음
|
|
find.search.addEventListener("input", () => find.setRows([]));
|
|
find.search.addEventListener("keydown", (event) => {
|
|
if (event.key !== "Enter" || event.defaultPrevented || event.isComposing) return;
|
|
event.preventDefault();
|
|
void run();
|
|
});
|
|
return find;
|
|
}
|
|
|
|
export function createPlanSite(params: PlanSiteParams): PlanSite {
|
|
const address = createText("", "muted", 14);
|
|
const note = createText("", "error");
|
|
const find = createPlaceFind(params.onPick);
|
|
|
|
const retry = createButton({
|
|
label: "다시 받기",
|
|
variant: "filled",
|
|
size: 28,
|
|
onClick: params.onRetry,
|
|
});
|
|
const upload = createButton({
|
|
label: "임도망 올리기",
|
|
variant: "ghost",
|
|
size: 28,
|
|
onClick: () =>
|
|
pickFiles({ accept: ".zip,.shp,.shx,.dbf,.prj,.cpg", multiple: true }, params.onUpload),
|
|
});
|
|
upload.title = "임도망도 SHP(시군구 zip 하나 · 또는 shp · shx · dbf · prj 함께 · 100 MB 까지)";
|
|
return {
|
|
parts: [address, find.root, note, retry, upload],
|
|
render(state) {
|
|
address.textContent = state.address ?? "";
|
|
address.hidden = !state.address;
|
|
note.textContent = state.note;
|
|
note.hidden = !state.note;
|
|
find.root.hidden = upload.hidden = !state.editable;
|
|
retry.hidden = !state.editable || !state.retry;
|
|
},
|
|
};
|
|
}
|