From a059c5f1527f07c9431ef910793582b475c6b091 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 3 Sep 2026 20:31:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(B03):=20=ED=8C=8C=EC=9D=BC=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=96=91=EC=8B=9D=20=EC=A0=95=EB=A6=AC=20=E2=80=94?= =?UTF-8?q?=20=EC=95=88=EB=82=B4=EB=8A=94=20=EC=A2=8C=EC=B8=A1=20=ED=8C=A8?= =?UTF-8?q?=EB=84=90,=20=EB=B3=B8=EB=AC=B8=EC=9D=80=20=EA=B3=A0=EB=A5=B4?= =?UTF-8?q?=EB=8A=94=20=EC=9E=90=EB=A6=AC=EB=A7=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 지적(2026-09-03) — 선택 항목이 너무 크고 대중없이 놓임. 안내는 B04·B05 와 같은 공용 좌측 패널로 빼고 본문은 고르는 자리만 남김. - `B03_FileInput_UI_Guide.ts` 신설 — 필요한 파일 / 고르는 방법 / 알아 둘 것 3묶음을 공용 오버레이 `optionsContent` 에 실음. 문단 구획은 공용 `ui-sidebar-section`. - 선택 영역 160px 블록 → 한 줄 바. 긴 문구는 패널로 옮기고 힌트는 한 줄로 교체. - 카드 `min-height: 220px` 폐지, [선택]을 제목 줄 오른쪽 끝 작은 버튼으로 이동, 빈 칸은 제목 줄만 남김. 진행바·속도·예상완료는 올라가는 동안만 표시. - 여백 정리 — 컨테이너 패딩 32 → 20, 그룹 제목 24 → 16px, 격자 간격 24 → 12. - 좌측 패널이 열리면 본문을 밀어내는 규칙 추가 — B03 은 일반 레이아웃이라 공용 워크플로 레이아웃의 padding 규칙이 없어 패널이 카드를 덮었음. 검증: 카드 높이 232 → 66px(빈 칸)·117px(파일 있음), 선택 영역 160 → 47px, 문서 높이 1408px 로 한 화면 남짓. typecheck·prettier 통과. Co-Authored-By: Claude Opus 5 (1M context) --- B03_FileInput/B03_FileInput_UI_Guide.ts | 65 +++++++++++ B03_FileInput/B03_FileInput_UI_Page.ts | 8 +- B03_FileInput/B03_FileInput_UI_Style.css | 129 +++++++++++++++++----- B03_FileInput/B03_FileInput_UI_Support.ts | 2 +- ui_template/ui_template_locale_b1.ts | 46 +++++++- 5 files changed, 216 insertions(+), 34 deletions(-) create mode 100644 B03_FileInput/B03_FileInput_UI_Guide.ts diff --git a/B03_FileInput/B03_FileInput_UI_Guide.ts b/B03_FileInput/B03_FileInput_UI_Guide.ts new file mode 100644 index 00000000..fce2ccd7 --- /dev/null +++ b/B03_FileInput/B03_FileInput_UI_Guide.ts @@ -0,0 +1,65 @@ +/* ============================================================================= + * B03_FileInput_UI_Guide.ts + * 파일 입력 좌측 안내 패널 (2026-09-03 사용자 지시). + * + * 종전에는 고르는 방법·필요한 파일 목록이 **선택 영역과 카드 안에** 길게 들어가 있어 + * 카드 한 장이 220px씩 차지했다. 안내는 한 번 읽으면 되는 것이라 다른 단계(B04·B05)와 + * 같은 자리 — 공용 오버레이의 좌측 패널(`createWorkflowOverlays.optionsContent`) — 로 + * 옮기고, 본문은 고르는 자리만 남긴다. + * + * 문단 구획은 공용 `ui-sidebar-section`을 쓴다(B04·B05 좌측 패널과 같은 테두리·간격). + * ========================================================================== */ + +import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale"; + +function L(key: keyof typeof ui_locales): string { + return ui_locales[key][currentLanguageIndex]; +} + +/** 안내 문단 하나 — 제목 + 항목 목록. */ +function section( + titleKey: keyof typeof ui_locales, + itemKeys: (keyof typeof ui_locales)[], +): HTMLElement { + const group = document.createElement("section"); + group.className = "ui-sidebar-section b03-file__guide-group"; + + const title = document.createElement("h3"); + title.className = "b03-file__guide-title"; + title.textContent = L(titleKey); + + const list = document.createElement("ul"); + list.className = "b03-file__guide-list"; + for (const key of itemKeys) { + const item = document.createElement("li"); + item.textContent = L(key); + list.append(item); + } + + group.append(title, list); + return group; +} + +/** 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것. */ +export function createInputGuide(): HTMLElement { + const guide = document.createElement("div"); + guide.className = "b03-file__guide"; + 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", + ]), + section("B03_Guide_Notes_Title", [ + "B03_Guide_Notes_Replace", + "B03_Guide_Notes_Crs", + "B03_Guide_Notes_LasFree", + ]), + ); + return guide; +} diff --git a/B03_FileInput/B03_FileInput_UI_Page.ts b/B03_FileInput/B03_FileInput_UI_Page.ts index d00be854..1cb44dd8 100644 --- a/B03_FileInput/B03_FileInput_UI_Page.ts +++ b/B03_FileInput/B03_FileInput_UI_Page.ts @@ -16,6 +16,7 @@ import { navigateTo } from "../A00_Common/router"; import { attachTempBatch } from "../B01_Dashboard/B01_Dashboard_Api_Temp"; import { clearRouteLatestCache } from "../B05_Profile/B05_Profile_Api_Fetch"; import { invalidateSectionDetail } from "../B06_Section/B06_Section_Section_Store"; +import { createInputGuide } from "./B03_FileInput_UI_Guide"; import { createTempPicker } from "./B03_FileInput_UI_TempPicker"; import { workflowSteps } from "../A00_Common/b_page_scaffold"; import { @@ -842,10 +843,15 @@ export async function renderB03FileInput(root: HTMLElement): Promise { content: [uploadControlPanel, cardsContainer], }); layout.content.classList.add("b03-file__main-layout"); + // 고르는 방법·필요한 파일 안내는 좌측 패널로 뺐다 — 본문은 고르는 자리만 남긴다 + // (2026-09-03 사용자 지시). 자리·여닫기는 B04·B05 좌측 패널과 같은 공용 오버레이다. const overlays = createWorkflowOverlays({ title: L("B03_File_Title"), progressContent, - showTitlePanel: false, + optionsContent: createInputGuide(), + // 공용 워크플로 레이아웃(B04·B05)이 하는 일을 여기서도 한다 — 패널이 열리면 본문을 + // 그만큼 밀어내지 않으면 안내가 카드 위를 덮는다. + onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen), }); layout.root.append(overlays.root); pageRoot = layout.root; diff --git a/B03_FileInput/B03_FileInput_UI_Style.css b/B03_FileInput/B03_FileInput_UI_Style.css index c0f8768f..e77e94c2 100644 --- a/B03_FileInput/B03_FileInput_UI_Style.css +++ b/B03_FileInput/B03_FileInput_UI_Style.css @@ -19,9 +19,10 @@ .b03-file__control-panel { display: flex; flex-direction: column; - gap: var(--spacing-20); + gap: var(--spacing-16); background: var(--color-canvas, #ffffff); - padding: var(--spacing-32); + /* 여백 32 → 20 (2026-09-03) — 고르는 자리만 남기고 안내를 좌측 패널로 뺐다. */ + padding: var(--spacing-20); border-radius: var(--radius-large, 24px); /* Wiza 24px 대형 둥글기 */ border: 1px solid var(--color-mist, #e6e2e3); box-shadow: var(--shadow-lg); @@ -43,15 +44,17 @@ } /* Wiza 스타일 가이드의 Lavender Glow 그라데이션 및 soft purple border hover */ +/* 한 줄 바(2026-09-03) — 종전 160px 블록은 안내 문구를 담느라 컸다. 문구는 좌측 + 패널로 갔으므로 여기는 「끌어 놓거나 눌러서 고르는 자리」 표시만 한다. */ .b03-file__dropzone { - min-height: 160px; - padding: var(--spacing-32) var(--spacing-24); - border: 2px dashed var(--color-mist, #e6e2e3); - border-radius: var(--radius-large, 24px); + padding: var(--spacing-12) var(--spacing-16); + border: 1px dashed var(--color-mist, #e6e2e3); + border-radius: var(--radius-cards, 8px); background: var(--color-paper, #f6f7fa); display: flex; - flex-direction: column; - align-items: center; + flex-direction: row; + flex-wrap: wrap; + align-items: baseline; justify-content: center; gap: var(--spacing-8); cursor: pointer; @@ -77,7 +80,7 @@ .b03-file__dropzone strong { color: var(--color-deep-iris, #26114a); - font-size: var(--text-body, 16px); + font-size: var(--text-body-sm, 14px); font-weight: var(--font-weight-medium, 500); } @@ -185,14 +188,67 @@ .b03-file__columns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: var(--spacing-24); + gap: var(--spacing-16); align-items: start; } +/* 좌측 안내 패널이 열리면 본문을 그만큼 밀어낸다 — 공용 워크플로 레이아웃(B04·B05)의 + `.ui-workflow-layout__body` 규칙과 같은 값. 접히면 손잡이 폭만 남긴다. */ +.b03-file__main-layout { + transition: padding-left var(--transition-base); +} + +.b03-file.is-options-open .b03-file__main-layout { + padding-left: min(var(--wf-left-panel-width), calc(100vw - var(--spacing-48))); +} + +.b03-file:not(.is-options-open) .b03-file__main-layout { + padding-left: var(--spacing-24); +} + +@media (max-width: 860px) { + .b03-file.is-options-open .b03-file__main-layout { + padding-left: 0; + } +} + +/* --- 좌측 안내 패널 (2026-09-03) — 본문에서 뺀 설명이 사는 자리. --- */ +.b03-file__guide { + display: flex; + flex-direction: column; + gap: var(--spacing-12); + padding: var(--spacing-12); +} + +.b03-file__guide-group { + display: flex; + flex-direction: column; + gap: var(--spacing-8); + padding: var(--spacing-12); +} + +.b03-file__guide-title { + margin: 0; + font-size: var(--text-body-sm, 14px); + font-weight: var(--font-weight-medium, 500); + color: var(--color-text, #26114a); +} + +.b03-file__guide-list { + margin: 0; + padding-left: var(--spacing-16); + display: flex; + flex-direction: column; + gap: var(--spacing-4); + font-size: var(--text-caption, 12px); + line-height: 1.55; + color: var(--color-text-secondary, #615e6e); +} + .b03-file__group { display: flex; flex-direction: column; - gap: var(--spacing-20); + gap: var(--spacing-12); } .b03-file__group-hint { @@ -201,17 +257,18 @@ margin: calc(var(--spacing-8) * -1) 0 0 0; } +/* 제목 24px → 본문 크기(2026-09-03) — 컨테이너가 둘뿐이라 큰 제목이 자리만 먹었다. */ .b03-file__group-title { - font-size: var(--text-subheading, 24px); + font-size: var(--text-body, 16px); color: var(--color-deep-iris, #26114a); font-family: var(--font-britti-sans, inherit); - margin: 0 0 var(--spacing-8) 0; + margin: 0; } .b03-file__group-content { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: var(--spacing-24); + gap: var(--spacing-12); } /* 업로드 중에는 화면 조작을 막는다 — 중복 업로드·단계 이동 방지(2026-08-08 사용자 지시). */ @@ -235,16 +292,17 @@ } /* Wiza 8px radius 카드 */ +/* 카드 한 장 = 파일 한 칸. 빈 칸은 **한 줄**로 끝난다(2026-09-03 사용자 지시) — + 파일이 붙거나 올라가는 중에만 아래 내용(파일명·진행바)이 펼쳐진다. */ .b03-file__card { - min-height: 220px; - padding: var(--spacing-24); /* 내부 전체 패딩 확대 */ + padding: var(--spacing-12) var(--spacing-16); border: 1px solid var(--color-mist, #e6e2e3); border-radius: var(--radius-cards, 8px); background: var(--color-canvas, #ffffff); box-shadow: var(--shadow-sm); display: flex; flex-direction: column; - gap: var(--spacing-20); + gap: var(--spacing-8); transition: transform var(--transition-base, 0.2s), border-color var(--transition-base, 0.2s), @@ -277,9 +335,11 @@ /* 카드가 좁아졌다(컨테이너 2열 × 카드 2열, 2026-08-31). 격자로 고정하면 제목이 글자 단위로 접히므로, 자리가 모자라면 배지·삭제 버튼이 다음 줄로 내려가게 한다. */ +/* 제목·상태·[선택]이 **한 줄**에 선다(2026-09-03) — 줄바꿈을 허용하면 [선택]이 아래로 + 내려가 카드가 두 줄이 됐다. 자리가 모자라면 제목이 줄어든다(min-width: 0). */ .b03-file__card-header { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; gap: var(--spacing-8); align-items: center; } @@ -300,11 +360,10 @@ .b03-file__card-heading { min-width: 0; - flex: 1 1 55%; + flex: 1 1 auto; display: flex; flex-direction: column; - gap: 2px; - margin-top: 2px; /* 제목 상단 여유 추가 */ + gap: 1px; } .b03-file__card-label { @@ -357,25 +416,29 @@ } .b03-file__card-content { - flex: 1; display: flex; flex-direction: column; - justify-content: flex-end; - gap: var(--spacing-16); /* 버튼 하단 및 내부 간격 확대 */ - padding-bottom: var(--spacing-8); /* 파일 선택 버튼 하단 패딩 확보 */ + gap: var(--spacing-8); } +/* 빈 칸은 제목 줄만 남긴다 — 파일이 붙으면 이름·크기가, 올라가는 중에는 진행바가 선다. */ +.b03-file__card--empty .b03-file__card-content { + display: none; +} + +/* [선택]은 제목 줄 오른쪽 끝의 작은 버튼 — 종전 전폭 40px 버튼이 카드 높이를 키웠다. */ .b03-file__card-select { - width: 100%; - min-height: 40px; /* 버튼 높이 증가 */ + flex: 0 0 auto; + min-height: 26px; + padding: 0 var(--spacing-8); border: 1px solid var(--color-mist, #e6e2e3); border-radius: var(--radius-buttons, 8px); color: var(--color-deep-iris, #26114a); background: var(--color-canvas, #ffffff); font-weight: var(--font-weight-medium, 500); - font-size: var(--text-body-sm, 14px); + font-size: var(--text-caption, 12px); + white-space: nowrap; cursor: pointer; - margin-bottom: var(--spacing-8); /* 파일 선택 버튼 하단 마진 추가 */ transition: background var(--transition-base, 0.2s), border-color var(--transition-base, 0.2s); @@ -395,12 +458,18 @@ overflow-wrap: anywhere; } +/* 진행바·속도·예상완료는 **올라가는 동안만** 보인다(2026-09-03) — 끝난 카드에 남겨 두면 + 읽을 일 없는 세 줄이 카드 높이를 그대로 차지했다. */ .b03-file__progress-section { - display: flex; + display: none; flex-direction: column; gap: var(--spacing-8); } +.b03-file__card--uploading .b03-file__progress-section { + display: flex; +} + .b03-file__progress-bar-container { width: 100%; height: 6px; diff --git a/B03_FileInput/B03_FileInput_UI_Support.ts b/B03_FileInput/B03_FileInput_UI_Support.ts index 4ee67cc8..93f17894 100644 --- a/B03_FileInput/B03_FileInput_UI_Support.ts +++ b/B03_FileInput/B03_FileInput_UI_Support.ts @@ -208,10 +208,10 @@ export function createFileCardTemplate(): HTMLTemplateElement {
+
-
diff --git a/ui_template/ui_template_locale_b1.ts b/ui_template/ui_template_locale_b1.ts index 97b8a443..6f54fd60 100644 --- a/ui_template/ui_template_locale_b1.ts +++ b/ui_template/ui_template_locale_b1.ts @@ -224,9 +224,51 @@ export const ui_locales_b1 = { "Upload the required planned route, terrain, and point cloud files.", ], B03_File_Select_Label: ["입력 파일 선택", "Select input files"], + /* 긴 설명은 좌측 안내 패널(B03_Guide_*)로 옮겼다 — 선택 영역은 한 줄만 남긴다 + (2026-09-03 사용자 지시). */ B03_File_Select_Hint: [ - "계획노선(CSV 또는 shapefile 5개)과 LAS/LAZ 1개, 지형 PRJ·TFW를 함께 고르면 카드에 나뉩니다. TIF는 선택 사항입니다.", - "Pick the planned route (a CSV or the five shapefile files), one LAS/LAZ, and the terrain PRJ and TFW together — they are sorted into cards. TIF is optional.", + "끌어 놓거나 눌러서 고르면 확장자대로 카드에 나뉩니다.", + "Drop files here or click to pick — they are sorted into cards by extension.", + ], + /* --- 좌측 안내 패널 (2026-09-03) --- */ + B03_Guide_Files_Title: ["필요한 파일", "Files you need"], + B03_Guide_Files_Route: [ + "계획노선 — CSV 한 장, 또는 shapefile 한 벌(.shp·.shx·.dbf·.cpg·.prj) 5개.", + "Planned route — one CSV, or the five shapefile parts (.shp, .shx, .dbf, .cpg, .prj).", + ], + B03_Guide_Files_Terrain: [ + "지형 — 포인트클라우드 LAS/LAZ 1개와 좌표계 PRJ, 래스터 기준 TFW.", + "Terrain — one LAS/LAZ point cloud plus the PRJ projection and TFW world file.", + ], + B03_Guide_Files_Optional: [ + "지형 TIF(DEM)는 선택 사항 — 없어도 지표면 분석이 진행됩니다.", + "The terrain TIF (DEM) is optional — surface analysis runs without it.", + ], + B03_Guide_How_Title: ["고르는 방법", "How to pick"], + B03_Guide_How_Drop: [ + "여러 개를 한 번에 끌어 놓으면 확장자로 카드에 자동 배정됩니다.", + "Drop several files at once — each is assigned to a card by its extension.", + ], + B03_Guide_How_Card: [ + "카드의 [선택]으로 한 칸씩 지정하거나 ✕로 되돌릴 수 있습니다.", + "Use a card's [Select] to set one slot, or ✕ to clear it.", + ], + B03_Guide_How_Temp: [ + "[임시 보관함에서 불러오기]는 대시보드에 미리 올려 둔 자료를 가져옵니다.", + "[Load from temporary storage] pulls files you staged on the dashboard.", + ], + B03_Guide_Notes_Title: ["알아 둘 것", "Before you upload"], + B03_Guide_Notes_Replace: [ + "이미 완료된 프로젝트에 다시 올리면 기존 분석 결과가 교체됩니다.", + "Uploading again to a finished project replaces the existing analysis.", + ], + B03_Guide_Notes_Crs: [ + "작업 좌표계는 라이다 PRJ가 기준 — 노선 좌표계와 다르면 업로드가 막힙니다.", + "The LiDAR PRJ sets the working CRS — a mismatched route CRS blocks the upload.", + ], + B03_Guide_Notes_LasFree: [ + "라이다가 없으면 [LAS 없는 설계]를 켜고 도엽 등고선으로 진행합니다.", + "With no LiDAR, turn on [Design without LAS] and work from sheet contours.", ], B03_File_Selected_Title: ["선택한 파일", "Selected files"], B03_File_Selected_Empty: ["선택한 파일이 없습니다.", "No files selected."],