260717_0
This commit is contained in:
@@ -9,7 +9,9 @@ import {
|
||||
} from "@config/config_frontend";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import { createButton, createTag, showToast } from "@ui/ui_template_elements";
|
||||
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
|
||||
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
||||
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
||||
import { createStepBar, WORKFLOW_STEP_ICONS } from "@ui/ui_template_workflow_layout";
|
||||
import {
|
||||
checkWF1AnalysisStatus,
|
||||
createUploadSession,
|
||||
@@ -29,151 +31,24 @@ import {
|
||||
saveB03UploadedFile,
|
||||
updateB03AnalysisState,
|
||||
} from "./B03_FileInput_State";
|
||||
import {
|
||||
createFileCardTemplate,
|
||||
formatBytes,
|
||||
formatEta,
|
||||
getExtension,
|
||||
initializeSlots,
|
||||
makeSessionKey,
|
||||
type FileSlot,
|
||||
type FileSlotState,
|
||||
type StoredUploadSession,
|
||||
type UploadStatus,
|
||||
} from "./B03_FileInput_UI_Support";
|
||||
import "./B03_FileInput_UI_Style.css";
|
||||
|
||||
type FileSlot = "las_laz" | "prj" | "tfw" | "tif" | "dxf";
|
||||
type UploadStatus = "pending" | "uploading" | "completed" | "failed";
|
||||
|
||||
interface SlotConfig {
|
||||
slot: FileSlot;
|
||||
labelKey: keyof typeof ui_locales;
|
||||
icon: string;
|
||||
extensions: readonly string[];
|
||||
isRequired: boolean;
|
||||
}
|
||||
|
||||
interface FileSlotState extends SlotConfig {
|
||||
file?: File;
|
||||
uploadSessionId?: string;
|
||||
uploadStatus: UploadStatus;
|
||||
progressBytes: number;
|
||||
speedMbs: number;
|
||||
etaSeconds: number | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface StoredUploadSession {
|
||||
key: string;
|
||||
projectId: string;
|
||||
slot: FileSlot;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
uploadSessionId: string;
|
||||
chunkSizeBytes: number;
|
||||
totalChunks: number;
|
||||
completedChunks: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
const SLOT_CONFIGS: readonly SlotConfig[] = [
|
||||
{
|
||||
slot: "las_laz",
|
||||
labelKey: "B03_File_Slot_PointCloud",
|
||||
icon: "●",
|
||||
extensions: [".las", ".laz"],
|
||||
isRequired: true,
|
||||
},
|
||||
{
|
||||
slot: "prj",
|
||||
labelKey: "B03_File_Slot_Projection",
|
||||
icon: "◇",
|
||||
extensions: [".prj"],
|
||||
isRequired: true,
|
||||
},
|
||||
{
|
||||
slot: "tfw",
|
||||
labelKey: "B03_File_Slot_RasterCoord",
|
||||
icon: "□",
|
||||
extensions: [".tfw"],
|
||||
isRequired: true,
|
||||
},
|
||||
{
|
||||
slot: "tif",
|
||||
labelKey: "B03_File_Slot_TerrainDem",
|
||||
icon: "▧",
|
||||
extensions: [".tif"],
|
||||
isRequired: false,
|
||||
},
|
||||
];
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
function getExtension(fileName: string): string {
|
||||
const index = fileName.lastIndexOf(".");
|
||||
return index >= 0 ? fileName.slice(index).toLowerCase() : "";
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
const gb = bytes / 1024 / 1024 / 1024;
|
||||
if (gb >= 1) return `${gb.toFixed(2)} GB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function formatEta(seconds: number | null): string {
|
||||
if (seconds === null || !Number.isFinite(seconds)) return "-";
|
||||
if (seconds < 60) return `${Math.ceil(seconds)}s`;
|
||||
const minutes = Math.ceil(seconds / 60);
|
||||
return `${minutes}m`;
|
||||
}
|
||||
|
||||
function makeSessionKey(projectId: string, file: File): string {
|
||||
return `b03_upload_${projectId}_${file.name}_${file.size}`;
|
||||
}
|
||||
|
||||
function initializeSlots(): Map<FileSlot, FileSlotState> {
|
||||
const map = new Map<FileSlot, FileSlotState>();
|
||||
for (const config of SLOT_CONFIGS) {
|
||||
map.set(config.slot, {
|
||||
...config,
|
||||
uploadStatus: "pending",
|
||||
progressBytes: 0,
|
||||
speedMbs: 0,
|
||||
etaSeconds: null,
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function createFileCardTemplate(): HTMLTemplateElement {
|
||||
const template = document.createElement("template");
|
||||
template.id = "file-card-template";
|
||||
template.innerHTML = `
|
||||
<article class="b03-file__card b03-file__card--empty">
|
||||
<div class="b03-file__card-header">
|
||||
<span class="b03-file__card-icon" aria-hidden="true"></span>
|
||||
<div class="b03-file__card-heading">
|
||||
<strong class="b03-file__card-label"></strong>
|
||||
<span class="b03-file__card-ext"></span>
|
||||
</div>
|
||||
<div class="b03-file__card-badge-container"></div>
|
||||
<button class="b03-file__card-remove" type="button" aria-label="파일 제거"></button>
|
||||
</div>
|
||||
<div class="b03-file__card-content">
|
||||
<button class="b03-file__card-select" type="button"></button>
|
||||
<input class="b03-file__slot-input" type="file" />
|
||||
<div class="b03-file__file-info">
|
||||
<span class="b03-file__file-name"></span>
|
||||
<span class="b03-file__file-size"></span>
|
||||
</div>
|
||||
<div class="b03-file__progress-section">
|
||||
<div class="b03-file__progress-bar-container">
|
||||
<div class="b03-file__progress-bar"></div>
|
||||
</div>
|
||||
<div class="b03-file__progress-info">
|
||||
<span class="b03-file__progress-bytes"></span>
|
||||
<span class="b03-file__progress-speed"></span>
|
||||
<span class="b03-file__progress-eta"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="b03-file__error-message" role="alert"></div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
return template;
|
||||
}
|
||||
|
||||
export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
const slots = initializeSlots();
|
||||
const cardMap = new Map<FileSlot, HTMLElement>();
|
||||
@@ -660,29 +535,36 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
const filesGroup = createCardGroup("", ["las_laz", "prj", "tfw", "tif"]); // 타이틀 공백으로 전달
|
||||
|
||||
const cardsContainer = document.createElement("div");
|
||||
cardsContainer.className =
|
||||
"b03-file__main-layout b03-file__control-panel b03-file__cards-container-panel"; // 동일한 양식으로 감싸기
|
||||
cardsContainer.className = "b03-file__control-panel b03-file__cards-container-panel";
|
||||
cardsContainer.append(filesGroup);
|
||||
|
||||
const workflowState = activeProjectId
|
||||
? await fetchWorkflowState(activeProjectId).catch(() => undefined)
|
||||
: undefined;
|
||||
const layout = createWorkflowLayout({
|
||||
title: L("B03_File_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 0,
|
||||
leftPanel: uploadControlPanel,
|
||||
mainContent: cardsContainer,
|
||||
const steps = workflowSteps();
|
||||
const progressContent = createStepBar(steps, 0, {
|
||||
stages: workflowState?.stages,
|
||||
currentStage: workflowState?.current_stage,
|
||||
routes: WORKFLOW_STEP_ROUTES,
|
||||
orientation: "vertical",
|
||||
icons: WORKFLOW_STEP_ICONS,
|
||||
onStepClick: (stepIndex) => {
|
||||
if (activeProjectId) {
|
||||
goToWorkflowStage(activeProjectId, WORKFLOW_STEP_ROUTES[stepIndex]);
|
||||
}
|
||||
},
|
||||
});
|
||||
layout.root.classList.add("b03-file");
|
||||
const layout = createGeneralLayout({
|
||||
pageClass: "b03-file",
|
||||
content: [uploadControlPanel, cardsContainer],
|
||||
});
|
||||
layout.content.classList.add("b03-file__main-layout");
|
||||
const overlays = createWorkflowOverlays({
|
||||
title: L("B03_File_Title"),
|
||||
progressContent,
|
||||
showTitlePanel: false,
|
||||
});
|
||||
layout.root.append(overlays.root);
|
||||
root.replaceChildren(layout.root);
|
||||
|
||||
for (const slot of slots.keys()) renderSlot(slot);
|
||||
|
||||
Reference in New Issue
Block a user