Merge remote-tracking branch 'origin/dev' into sub_laptop_5
충돌 = UI_Page.ts 메뉴 블록 옆 주석(596-4) — 둘 다 살림
This commit is contained in:
@@ -19,8 +19,9 @@
|
||||
import "./D03_PlanAnalysis_UI_Style.css";
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { attachCollapsible, createPanelCard } from "@ui/ui_template_collapsible";
|
||||
import { pickFiles } from "@ui/ui_template_drop";
|
||||
import { askConfirm } from "@ui/ui_template_modal";
|
||||
import { createProgressBar } from "@ui/ui_template_progress";
|
||||
import { createProgressCircle } from "@ui/ui_template_progress";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import {
|
||||
createButton,
|
||||
@@ -71,10 +72,8 @@ import {
|
||||
import { lonLatToTm, tmParams } from "./logic/D03_PlanAnalysis_Tm";
|
||||
|
||||
const POLL_MS = 1000;
|
||||
/** 지도 보기 가운데(보기의 반)가 받은 범위 밖이면 이어 받음 — 그 상자가 이보다 넓으면(멀리 줄여 봄) 안 받음(m). */
|
||||
const VIEW_FETCH_MAX_M = 2000;
|
||||
/** 지도 보기 바뀐 뒤 이만큼 멈추면 범위를 봄(ms). */
|
||||
const VIEW_SETTLE_MS = 800;
|
||||
/** 받기가 끊김(서버 다시 켜짐 · 진행을 못 읽음) — [다시 받기]. */
|
||||
const INTERRUPTED = "끊김(서버 다시 켜짐 등)";
|
||||
/** 처음 맞춤 = 기준점 둘레(서버 `SITE_RADIUS_M`). */
|
||||
const SITE_RADIUS_M = 1000;
|
||||
/** 되돌리기 쌓는 수. */
|
||||
@@ -110,9 +109,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
let redoStack: Snap[] = [];
|
||||
let surface: PlanSurface | null = null;
|
||||
let have: [number, number, number, number] | null = null;
|
||||
let viewCore: Vertex[] = []; // 지도 보기 가운데 상자 두 귀(넓으면 빈 것)
|
||||
let viewMid: Vertex | null = null; // 지도 보기 가운데(지점 [추가] 자리)
|
||||
let viewTimer = 0;
|
||||
let lastTried = ""; // 같은 받기를 거듭하지 않음
|
||||
let ready = false; // 들어올 때 받기 판단이 끝남(그 앞 보기 바뀜은 안 받음)
|
||||
let busy = false;
|
||||
@@ -160,7 +157,10 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
addPoint: (at) => step({ network, points: [...points, at], pointNames: [...pointNames, ""] }),
|
||||
});
|
||||
const pieces = createPlanPieces({ network: () => network, edit });
|
||||
const measure = createPlanMeasure(() => z);
|
||||
const measure = createPlanMeasure(
|
||||
() => z,
|
||||
() => network,
|
||||
);
|
||||
const marks = createPlanPoints({
|
||||
points: () => points,
|
||||
network: () => network,
|
||||
@@ -184,30 +184,11 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
draw,
|
||||
editable: () => editable,
|
||||
});
|
||||
// 지도 보기 가운데 상자 — 그릴 때마다 보이는 화면 가운데 반을 작업 좌표로(바뀌면 잠시 뒤 범위 봄)
|
||||
// 지도 보기 가운데 — 그릴 때마다 작업 좌표로(지점 [추가] 자리 · 보기만 옮김 = 안 받음 596-4)
|
||||
const viewWatch: PlanMapLayer = {
|
||||
draw(context, screen) {
|
||||
const [w, h] = [context.canvas.clientWidth, context.canvas.clientHeight];
|
||||
const corners = [
|
||||
[w / 4, h / 4],
|
||||
[(3 * w) / 4, h / 4],
|
||||
[(3 * w) / 4, (3 * h) / 4],
|
||||
[w / 4, (3 * h) / 4],
|
||||
].map(([x, y]) => screen.toMetric(...screen.unrotate(x, y)));
|
||||
const xs = corners.map((c) => c[0]);
|
||||
const ys = corners.map((c) => c[1]);
|
||||
viewMid = w ? screen.toMetric(...screen.unrotate(w / 2, h / 2)) : null;
|
||||
const box: Vertex[] = [
|
||||
[Math.min(...xs), Math.min(...ys)],
|
||||
[Math.max(...xs), Math.max(...ys)],
|
||||
];
|
||||
const wide =
|
||||
box[1][0] - box[0][0] > VIEW_FETCH_MAX_M || box[1][1] - box[0][1] > VIEW_FETCH_MAX_M;
|
||||
const next = wide || !w ? [] : box;
|
||||
if (JSON.stringify(next) === JSON.stringify(viewCore)) return;
|
||||
viewCore = next;
|
||||
clearTimeout(viewTimer);
|
||||
viewTimer = window.setTimeout(() => void ensureCovered(), VIEW_SETTLE_MS);
|
||||
},
|
||||
};
|
||||
const map = createPlanMap({
|
||||
@@ -220,16 +201,18 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
projectId,
|
||||
map,
|
||||
points: () => [...points, ...allVertices()],
|
||||
have: () => have,
|
||||
});
|
||||
map.root.append(backdrop.bar, backdrop.legend, props.panel);
|
||||
// 받기 진행 = 지도 오른쪽 아래 작은 서클(받을 때만 · 지도 조작 안 막음 596-3)
|
||||
const circle = createProgressCircle({ size: 40, corner: true });
|
||||
circle.root.hidden = true;
|
||||
map.root.append(backdrop.bar, backdrop.legend, props.panel, circle.root);
|
||||
const main = document.createElement("div");
|
||||
main.className = "d03-plan";
|
||||
main.append(map.root);
|
||||
|
||||
const terrainText = createText("", "muted");
|
||||
terrainText.classList.add("d03-plan__hint");
|
||||
const bar = createProgressBar();
|
||||
bar.root.hidden = true;
|
||||
const retryButton = createButton({
|
||||
label: "다시 받기",
|
||||
variant: "filled",
|
||||
@@ -240,7 +223,18 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
},
|
||||
});
|
||||
const terrainCard = createPanelCard("지형", false);
|
||||
terrainCard.append(terrainText, bar.root, retryButton);
|
||||
const roadButton = createButton({
|
||||
label: "임도망 올리기",
|
||||
variant: "ghost",
|
||||
size: 28,
|
||||
onClick: () =>
|
||||
pickFiles({ accept: ".zip,.shp,.shx,.dbf,.prj,.cpg", multiple: true }, (files) => {
|
||||
void backdrop.uploadRoads(files);
|
||||
}),
|
||||
});
|
||||
roadButton.title =
|
||||
"임도망도 SHP(시군구 zip 하나 · 또는 shp · shx · dbf · prj 함께 · 100 MB 까지)";
|
||||
terrainCard.append(terrainText, retryButton, roadButton);
|
||||
const pointCard = createPanelCard("지점", false);
|
||||
pointCard.append(marks.panel);
|
||||
const drawCard = createPanelCard("그리기", false);
|
||||
@@ -341,13 +335,12 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
list.every(([x, y]) => x >= have![0] && x <= have![2] && y >= have![1] && y <= have![3]);
|
||||
const outside = (): boolean => !!have && !inside([...points, ...allVertices()]);
|
||||
|
||||
/** 조용히 받기(요구 453) — 지점 · 선 · 지도 보기 가운데가 받은 범위 밖이면 이어 받음 ·
|
||||
* 단추 · 막는 창 없음 · 실패 = [다시 받기] · 같은 요청은 한 번만. */
|
||||
/** 조용히 받기(요구 453) — 지점 · 선이 받은 범위 밖이면 이어 받음(지도 보기만 옮김 = 안 받음 596-4) ·
|
||||
* 단추 · 막는 창 없음 · 실패 · 끊김 = [다시 받기] · 같은 요청은 한 번만. */
|
||||
async function ensureCovered(): Promise<void> {
|
||||
if (!ready || !editable || busy || failed || !epsg || !root.isConnected) return;
|
||||
const need = [...points, ...allVertices()];
|
||||
if (inside(need) && inside(viewCore)) return; // 받을 때 서버가 여유를 더함
|
||||
const extra = [...need, ...viewCore];
|
||||
const extra = [...points, ...allVertices()];
|
||||
if (inside(extra)) return; // 받을 때 서버가 여유를 더함
|
||||
const key = JSON.stringify([have, extra]);
|
||||
if (key === lastTried) return;
|
||||
lastTried = key;
|
||||
@@ -369,6 +362,7 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
pieces.render();
|
||||
marks.render();
|
||||
retryButton.hidden = !editable || busy || !failed;
|
||||
roadButton.hidden = !editable;
|
||||
if (!busy)
|
||||
terrainText.textContent = failed
|
||||
? `지형 받기 실패 — ${failed}`
|
||||
@@ -407,29 +401,32 @@ export async function renderD03PlanAnalysis(root: HTMLElement): Promise<void> {
|
||||
await ensureCovered(); // 받는 동안 옮긴 지점 · 선 · 보기
|
||||
}
|
||||
|
||||
/** 지형 받기를 끝날 때까지 지켜봄 — 진행은 지형 칸에. */
|
||||
/** 지형 받기를 끝날 때까지 지켜봄 — 진행 = 지도 귀퉁이 서클 · 끊김(서버 다시 켜짐 = 끝 아닌데 멎음 ·
|
||||
* 진행을 못 읽음) = 실패 꼴 + [다시 받기]. */
|
||||
async function watch(): Promise<PlanJob | null> {
|
||||
busy = true;
|
||||
bar.root.hidden = false;
|
||||
circle.set(null, "지형");
|
||||
circle.root.hidden = false;
|
||||
refresh();
|
||||
try {
|
||||
for (;;) {
|
||||
if (!root.isConnected) return null;
|
||||
const job = (await fetchPlanProgress(projectId)).terrain;
|
||||
bar.set(job.percent / 100);
|
||||
terrainText.textContent = `지형 받기 ${job.percent}% · ${job.message ?? ""}`;
|
||||
circle.set(job.percent / 100, `지형 ${job.message ?? ""}`.trim());
|
||||
circle.root.title = `지형 받기 ${job.percent}% · ${job.message ?? ""}`;
|
||||
if (!job.running) {
|
||||
const cut = !job.error && (job.interrupted || job.percent < 100);
|
||||
if (job.error) showToast(job.error, "error");
|
||||
return job;
|
||||
return cut ? { ...job, error: INTERRUPTED } : job;
|
||||
}
|
||||
await sleep(POLL_MS);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(`진행을 읽지 못했습니다 — ${errorText(error)}`, "error");
|
||||
return null;
|
||||
return { running: false, percent: 0, message: null, error: INTERRUPTED };
|
||||
} finally {
|
||||
busy = false;
|
||||
bar.root.hidden = true;
|
||||
circle.root.hidden = true;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user