This commit is contained in:
2026-07-10 18:12:17 +09:00
parent 50d75fcfcd
commit e3d66e717c
28 changed files with 2542 additions and 99 deletions
+37 -39
View File
@@ -1,5 +1,3 @@
/* B03 파일 입력 페이지 - 가로형 드롭존 + 슬롯 그리드 + 청크 업로드 */
import {
CURRENT_PROJECT_ID_KEY,
PROGRESS_UPDATE_INTERVAL_MS,
@@ -9,15 +7,7 @@ import {
UPLOAD_MAX_MB,
} from "@config/config_frontend";
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
import {
createButton,
createCard,
createTag,
createWorkflowShell,
hideLoadingOverlay,
showLoadingOverlay,
showToast,
} from "@ui/ui_template_elements";
import { createButton, createTag, createWorkflowShell, showToast } from "@ui/ui_template_elements";
import {
checkWF1AnalysisStatus,
createUploadSession,
@@ -27,6 +17,11 @@ import {
} from "./B03_FileInput_Api_Fetch";
import { navigateTo } from "../A00_Common/router";
import { ROUTES } from "@config/config_frontend";
import {
restoreB03ProjectState,
saveB03UploadedFile,
updateB03AnalysisState,
} from "./B03_FileInput_State";
import "./B03_FileInput_UI_Style.css";
type FileSlot = "las_laz" | "prj" | "tfw" | "tif" | "dxf";
@@ -196,9 +191,8 @@ export function renderB03FileInput(root: HTMLElement): void {
});
shell.root.classList.add("b03-file");
// 레이아웃 전면 리팩토링: 좌측/우측 패널 구분 없이 하나의 와이드 컴포넌트로 결합
shell.leftPanel.remove(); // 기존 좁은 세로 영역 제거
shell.leftPanel.remove();
const contentContainer = document.createElement("div");
contentContainer.className = "b03-file__main-layout";
@@ -247,8 +241,7 @@ export function renderB03FileInput(root: HTMLElement): void {
);
const cssState = stateName === "failed" ? "error" : stateName;
card.classList.add(`b03-file__card--${cssState}`);
// 배지 상태 연동 업데이트
const badgeContainer = card.querySelector<HTMLDivElement>(".b03-file__card-badge-container");
if (badgeContainer) {
badgeContainer.replaceChildren();
@@ -542,16 +535,16 @@ export function renderB03FileInput(root: HTMLElement): void {
state.speedMbs > 0 ? (file.size - end) / 1024 / 1024 / state.speedMbs : null;
const stored: StoredUploadSession = {
key: storageKey,
projectId,
slot: state.slot,
fileName: file.name,
fileSize: file.size,
uploadSessionId: session,
chunkSizeBytes,
totalChunks,
completedChunks: chunkIndex + 1,
updatedAt: Date.now(),
key: storageKey,
projectId,
slot: state.slot,
fileName: file.name,
fileSize: file.size,
uploadSessionId: session,
chunkSizeBytes,
totalChunks,
completedChunks: chunkIndex + 1,
updatedAt: Date.now(),
};
localStorage.setItem(storageKey, JSON.stringify(stored));
@@ -564,6 +557,11 @@ export function renderB03FileInput(root: HTMLElement): void {
const response = await finalizeUploadSession(projectId, session, totalChunks);
localStorage.removeItem(storageKey);
saveB03UploadedFile(projectId, {
slot: state.slot,
fileName: file.name,
fileSize: file.size,
});
state.progressBytes = file.size;
state.speedMbs =
file.size / 1024 / 1024 / Math.max(0.001, (performance.now() - startedAt) / 1000);
@@ -577,13 +575,11 @@ export function renderB03FileInput(root: HTMLElement): void {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
try {
const status = await checkWF1AnalysisStatus(projectId);
updateB03AnalysisState(projectId, status.status, status.progress_percent);
if (status.status === "completed") {
return true;
}
} catch {
// 상태 조회 실패는 무시하고 계속 폴링
}
// 5초마다 폴링 (최대 30분)
} catch {}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
return false;
@@ -599,24 +595,20 @@ export function renderB03FileInput(root: HTMLElement): void {
pageError.textContent = "";
const uploaded: UploadedFileResult[] = [];
showLoadingOverlay();
try {
for (const state of targetStates) {
uploaded.push(...(await uploadOneFile(activeProjectId, state)));
}
renderUploadResults(uploaded);
showToast(L("B03_File_Upload_Success"), "success");
hideLoadingOverlay();
// WF1 분석 상태 폴링 시작
showToast("WF1 분석 진행 중... 완료되면 자동으로 이동합니다.", "info");
showToast(L("B03_File_Analysis_InProgress"), "info");
const analysisComplete = await pollWF1Analysis(activeProjectId);
if (analysisComplete) {
// 분석 완료 시 B04 페이지로 이동
navigateTo(ROUTES.B04_WF1_SURFACE);
} else {
showToast("분석이 진행 중입니다. 잠시 후 새로고침해주세요.", "warning");
showToast(L("B03_File_Analysis_StillRunning"), "warning");
}
} catch (error) {
const failed = targetStates.find((state) => state.uploadStatus === "uploading");
@@ -624,8 +616,6 @@ export function renderB03FileInput(root: HTMLElement): void {
if (failed) showErrorMessage(failed.slot, detail);
pageError.textContent = `${L("B03_File_Upload_Failed")} ${detail}`;
showToast(L("B03_File_Upload_Failed"), "error");
} finally {
hideLoadingOverlay();
}
}
@@ -694,5 +684,13 @@ export function renderB03FileInput(root: HTMLElement): void {
for (const slot of slots.keys()) renderSlot(slot);
void registerB03ServiceWorker();
void detectPausedUploads();
if (activeProjectId) {
restoreB03ProjectState({
projectId: activeProjectId,
label: L("B03_File_Restore_State"),
container: resumeBanner,
poll: pollWF1Analysis,
onComplete: () => navigateTo(ROUTES.B04_WF1_SURFACE),
});
}
}