diff --git a/B03_FileInput/B03_FileInput_UI_Guide.ts b/B03_FileInput/B03_FileInput_UI_Guide.ts index fce2ccd7..dfa389d3 100644 --- a/B03_FileInput/B03_FileInput_UI_Guide.ts +++ b/B03_FileInput/B03_FileInput_UI_Guide.ts @@ -40,21 +40,29 @@ function section( return group; } -/** 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것. */ -export function createInputGuide(): HTMLElement { +/** + * 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것. + * + * `statusNote`(재접속 현황 배너)는 「고르는 방법」 끝에 붙는다 — 프로젝트가 이미 완료라 + * 다시 올리면 교체된다는 안내라서, 고르기 전에 읽을 것들과 같은 자리에 둔다 + * (2026-09-03 사용자 지시). + */ +export function createInputGuide(statusNote?: HTMLElement): HTMLElement { const guide = document.createElement("div"); guide.className = "b03-file__guide"; + const howTo = section("B03_Guide_How_Title", [ + "B03_Guide_How_Drop", + "B03_Guide_How_Card", + "B03_Guide_How_Temp", + ]); + if (statusNote) howTo.append(statusNote); guide.append( section("B03_Guide_Files_Title", [ "B03_Guide_Files_Route", "B03_Guide_Files_Terrain", "B03_Guide_Files_Optional", ]), - section("B03_Guide_How_Title", [ - "B03_Guide_How_Drop", - "B03_Guide_How_Card", - "B03_Guide_How_Temp", - ]), + howTo, section("B03_Guide_Notes_Title", [ "B03_Guide_Notes_Replace", "B03_Guide_Notes_Crs", diff --git a/B03_FileInput/B03_FileInput_UI_Page.ts b/B03_FileInput/B03_FileInput_UI_Page.ts index 1cb44dd8..300589f4 100644 --- a/B03_FileInput/B03_FileInput_UI_Page.ts +++ b/B03_FileInput/B03_FileInput_UI_Page.ts @@ -10,7 +10,7 @@ import { createButton, createTag, showToast } from "@ui/ui_template_elements"; 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 { fetchUploadOverview, type UploadedFileResult } from "./B03_FileInput_Api_Fetch"; +import { fetchUploadOverview } from "./B03_FileInput_Api_Fetch"; import { clearPreloadMark } from "../A00_Common/b_asset_cache"; import { navigateTo } from "../A00_Common/router"; import { attachTempBatch } from "../B01_Dashboard/B01_Dashboard_Api_Temp"; @@ -29,7 +29,6 @@ import { confirmReplaceUpload, isInitialPipelineRunning, pollInitialPipeline, - renderUploadResults, uploadOneFile, } from "./B03_FileInput_UI_Upload"; import { @@ -63,8 +62,6 @@ export async function renderB03FileInput(root: HTMLElement): Promise { // 대시보드 임시 보관함에서 가져올 자료 선택기 — 선택되면 [업로드]가 이동을 수행한다. const tempPicker = createTempPicker(() => updateUploadButton()); const cardMap = new Map(); - const resultList = document.createElement("ul"); - resultList.className = "b03-file__results"; let uploadButton: HTMLButtonElement; let resumeBanner: HTMLDivElement; let activeProjectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? ""; @@ -653,22 +650,20 @@ export async function renderB03FileInput(root: HTMLElement): Promise { pageError.textContent = ""; setUploading(true); - const uploaded: UploadedFileResult[] = []; try { for (let index = 0; index < targetStates.length; index += 1) { const state = targetStates[index]; - uploaded.push( - ...(await uploadOneFile( - activeProjectId, - state, - index === targetStates.length - 1, - () => renderSlot(state.slot), - lasFreeDesign, - )), + await uploadOneFile( + activeProjectId, + state, + index === targetStates.length - 1, + () => renderSlot(state.slot), + lasFreeDesign, ); } clearDerivedCaches(activeProjectId); - renderUploadResults(resultList, uploaded); + // 업로드 결과는 카드가 이미 완료 상태로 보여 준다 — 같은 내용을 목록으로 또 쌓지 + // 않는다(2026-09-03 사용자 지시). showToast(L("B03_File_Upload_Success"), "success"); showToast(L("B03_File_Analysis_InProgress"), "info"); @@ -737,19 +732,15 @@ export async function renderB03FileInput(root: HTMLElement): Promise { disabled: true, }); + // 고르는 자리 한 줄 — [입력 파일 선택] · [임시 보관함에서 불러오기] · [파일 업로드] + // (2026-09-03 사용자 지시). 셋이 한 동작의 앞뒤라 흩어 놓을 이유가 없다. + const pickRow = document.createElement("div"); + pickRow.className = "b03-file__pick-row"; + pickRow.append(dropzone, tempPicker.root, uploadButton); + const uploadControlPanel = document.createElement("div"); uploadControlPanel.className = "b03-file__control-panel"; - uploadControlPanel.append( - subtitle, - overviewBanner, - dropzone, - // 대시보드 임시 보관함에서 자료를 끌어오는 자리 — 업로드 컨테이너 안에 둔다. - tempPicker.root, - resumeBanner, - pageError, - uploadButton, - resultList, - ); + uploadControlPanel.append(subtitle, pickRow, resumeBanner, pageError); // 자료의 출처가 둘로 갈린다 — 원청이 준 계획노선, 측량이 준 지형(LAS·래스터). // 좌표계 파일(.prj)도 각각 하나씩 오므로 컨테이너를 나눠야 어느 칸에 무엇을 넣는지 @@ -848,7 +839,7 @@ export async function renderB03FileInput(root: HTMLElement): Promise { const overlays = createWorkflowOverlays({ title: L("B03_File_Title"), progressContent, - optionsContent: createInputGuide(), + optionsContent: createInputGuide(overviewBanner), // 공용 워크플로 레이아웃(B04·B05)이 하는 일을 여기서도 한다 — 패널이 열리면 본문을 // 그만큼 밀어내지 않으면 안내가 카드 위를 덮는다. onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen), diff --git a/B03_FileInput/B03_FileInput_UI_Style.css b/B03_FileInput/B03_FileInput_UI_Style.css index d53a46a4..e654bc45 100644 --- a/B03_FileInput/B03_FileInput_UI_Style.css +++ b/B03_FileInput/B03_FileInput_UI_Style.css @@ -45,6 +45,25 @@ } /* Wiza 스타일 가이드의 Lavender Glow 그라데이션 및 soft purple border hover */ +/* 고르는 자리 한 줄 — [입력 파일 선택] · [임시 보관함] · [파일 업로드](2026-09-03). + 좁아지면 아래로 접히되, 선택 영역이 남는 폭을 먹는다. */ +.b03-file__pick-row { + display: flex; + flex-wrap: wrap; + align-items: stretch; + gap: var(--spacing-8); +} + +.b03-file__pick-row .b03-file__dropzone { + flex: 1 1 320px; +} + +.b03-file__pick-row .b03-file__temp-picker, +.b03-file__pick-row .ui-btn { + flex: 0 0 auto; + align-self: stretch; +} + /* 한 줄 바(2026-09-03) — 종전 160px 블록은 안내 문구를 담느라 컸다. 문구는 좌측 패널로 갔으므로 여기는 「끌어 놓거나 눌러서 고르는 자리」 표시만 한다. */ .b03-file__dropzone { @@ -266,9 +285,11 @@ margin: 0; } +/* 카드가 좁아지면 한 줄에 제목·상태·[선택]이 못 들어가 제목이 글자 단위로 접힌다 + (2026-09-03 실측: 안내 패널을 연 상태에서 카드 폭 210px). 폭이 모자라면 열을 줄인다. */ .b03-file__group-content { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--spacing-12); } @@ -371,11 +392,10 @@ color: var(--color-deep-iris, #26114a); font-size: var(--text-body-sm, 14px); font-weight: var(--font-weight-medium, 500); - /* 한글은 글자 단위로 끊기므로 어절을 지켜 준다 — "계획 -노선 -도형" 방지. */ - word-break: keep-all; - overflow-wrap: break-word; + /* 제목은 한 줄로 두고 모자라면 말줄임 — 접히면 카드가 세로로 길어진다(2026-09-03). */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .b03-file__card-ext, @@ -525,65 +545,3 @@ .b03-file__card--error .b03-file__progress-section { display: none; } - -.b03-file__results { - margin: var(--spacing-8) 0 0 0; - padding: 0; - list-style: none; - border: 1px solid var(--color-mist, #e6e2e3); - border-radius: var(--radius-cards, 8px); - overflow: hidden; - background: var(--color-canvas, #ffffff); -} - -.b03-file__results:empty { - display: none; -} - -.b03-file__results li { - padding: var(--spacing-12) var(--spacing-16); - display: flex; - flex-direction: column; - gap: 2px; - border-bottom: 1px solid var(--color-mist, #e6e2e3); - color: var(--color-deep-iris, #26114a); - font-size: var(--text-body-sm, 14px); -} - -.b03-file__results li:last-child { - border-bottom: 0; -} - -@media (max-width: 1440px) { - /* 좁아지면 두 컨테이너를 위아래로 쌓는다 — 카드가 눌려 글자가 접히는 것을 막는다. */ - .b03-file__columns { - grid-template-columns: 1fr; - } -} - -@media (max-width: 720px) { - .b03-file { - padding: var(--spacing-16) var(--spacing-12); - } - - .b03-file__group-content { - grid-template-columns: 1fr; /* 모바일에서는 1행 1열 구조 */ - } -} - -/* LAS 없는 설계(도엽등고선 기반) 토글 — 2026-08-30 */ -.b03-file__lasfree { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 2px; - font-size: var(--text-body); - color: var(--color-text-body); - cursor: pointer; - user-select: none; -} - -.b03-file__card--disabled { - opacity: 0.45; - pointer-events: none; -} diff --git a/B03_FileInput/B03_FileInput_UI_Upload.ts b/B03_FileInput/B03_FileInput_UI_Upload.ts index dafe030b..3f69e449 100644 --- a/B03_FileInput/B03_FileInput_UI_Upload.ts +++ b/B03_FileInput/B03_FileInput_UI_Upload.ts @@ -73,22 +73,6 @@ export function confirmReplaceUpload(slotLabel: string, fileName: string): Promi } /** 업로드 결과 목록(파일명 + 저장 경로)을 다시 그린다. */ -export function renderUploadResults( - list: HTMLElement, - results: readonly UploadedFileResult[], -): void { - list.replaceChildren(); - for (const result of results) { - const item = document.createElement("li"); - const filename = document.createElement("strong"); - filename.textContent = result.original_filename; - const path = document.createElement("span"); - path.textContent = `${L("B03_File_Result_Path")}: ${result.relative_path}`; - item.append(filename, path); - list.append(item); - } -} - /** * 파일 1건을 청크로 올린다. 중단된 세션이 있으면 그 지점부터 이어 올린다. * 진행 상황은 `onProgress`로만 알린다 — 갱신 주기는 config 값으로 제한한다.