feat(B03): 고르는 자리 한 줄 · 완료 안내는 좌측 패널 · 결과 목록 제거
사용자 지시 3건(2026-09-03). - 재접속 현황 배너(「…모두 완료된 프로젝트입니다」)를 좌측 안내 패널 「고르는 방법」 안으로 옮김 — 고르기 전에 읽을 것과 같은 자리. - [입력 파일 선택]·[임시 보관함에서 불러오기]·[파일 업로드]를 한 줄(`b03-file__pick-row`)로 묶음. 좁아지면 접히되 선택 영역이 남는 폭을 먹음. - 업로드 완료 항목 목록 삭제 — 같은 내용을 카드가 완료 상태로 이미 보여 줌. 쓰이지 않게 된 `renderUploadResults`와 `.b03-file__results` 규칙도 제거. 곁들여 고친 것 — 카드 격자를 2열 고정에서 `auto-fill minmax(260px, 1fr)` 로 바꿈. 안내 패널을 연 상태에서 카드 폭이 210px 로 눌려 제목이 글자 단위로 접혔음(실측). 제목은 한 줄 말줄임으로 둠. 검증: 한 줄 배치 실측 — 선택 397px · 보관함 331px · 업로드 102px, 높이 76px 로 정렬. 카드 66px(빈 칸)/117px(파일 있음), 제목 줄 높이 21px(한 줄). typecheck·prettier 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,21 +40,29 @@ function section(
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것. */
|
/**
|
||||||
export function createInputGuide(): HTMLElement {
|
* 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것.
|
||||||
|
*
|
||||||
|
* `statusNote`(재접속 현황 배너)는 「고르는 방법」 끝에 붙는다 — 프로젝트가 이미 완료라
|
||||||
|
* 다시 올리면 교체된다는 안내라서, 고르기 전에 읽을 것들과 같은 자리에 둔다
|
||||||
|
* (2026-09-03 사용자 지시).
|
||||||
|
*/
|
||||||
|
export function createInputGuide(statusNote?: HTMLElement): HTMLElement {
|
||||||
const guide = document.createElement("div");
|
const guide = document.createElement("div");
|
||||||
guide.className = "b03-file__guide";
|
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(
|
guide.append(
|
||||||
section("B03_Guide_Files_Title", [
|
section("B03_Guide_Files_Title", [
|
||||||
"B03_Guide_Files_Route",
|
"B03_Guide_Files_Route",
|
||||||
"B03_Guide_Files_Terrain",
|
"B03_Guide_Files_Terrain",
|
||||||
"B03_Guide_Files_Optional",
|
"B03_Guide_Files_Optional",
|
||||||
]),
|
]),
|
||||||
section("B03_Guide_How_Title", [
|
howTo,
|
||||||
"B03_Guide_How_Drop",
|
|
||||||
"B03_Guide_How_Card",
|
|
||||||
"B03_Guide_How_Temp",
|
|
||||||
]),
|
|
||||||
section("B03_Guide_Notes_Title", [
|
section("B03_Guide_Notes_Title", [
|
||||||
"B03_Guide_Notes_Replace",
|
"B03_Guide_Notes_Replace",
|
||||||
"B03_Guide_Notes_Crs",
|
"B03_Guide_Notes_Crs",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createButton, createTag, showToast } from "@ui/ui_template_elements";
|
|||||||
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
import { createGeneralLayout } from "@ui/ui_template_general_layout";
|
||||||
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
||||||
import { createStepBar, WORKFLOW_STEP_ICONS } from "@ui/ui_template_workflow_layout";
|
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 { clearPreloadMark } from "../A00_Common/b_asset_cache";
|
||||||
import { navigateTo } from "../A00_Common/router";
|
import { navigateTo } from "../A00_Common/router";
|
||||||
import { attachTempBatch } from "../B01_Dashboard/B01_Dashboard_Api_Temp";
|
import { attachTempBatch } from "../B01_Dashboard/B01_Dashboard_Api_Temp";
|
||||||
@@ -29,7 +29,6 @@ import {
|
|||||||
confirmReplaceUpload,
|
confirmReplaceUpload,
|
||||||
isInitialPipelineRunning,
|
isInitialPipelineRunning,
|
||||||
pollInitialPipeline,
|
pollInitialPipeline,
|
||||||
renderUploadResults,
|
|
||||||
uploadOneFile,
|
uploadOneFile,
|
||||||
} from "./B03_FileInput_UI_Upload";
|
} from "./B03_FileInput_UI_Upload";
|
||||||
import {
|
import {
|
||||||
@@ -63,8 +62,6 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
// 대시보드 임시 보관함에서 가져올 자료 선택기 — 선택되면 [업로드]가 이동을 수행한다.
|
// 대시보드 임시 보관함에서 가져올 자료 선택기 — 선택되면 [업로드]가 이동을 수행한다.
|
||||||
const tempPicker = createTempPicker(() => updateUploadButton());
|
const tempPicker = createTempPicker(() => updateUploadButton());
|
||||||
const cardMap = new Map<FileSlot, HTMLElement>();
|
const cardMap = new Map<FileSlot, HTMLElement>();
|
||||||
const resultList = document.createElement("ul");
|
|
||||||
resultList.className = "b03-file__results";
|
|
||||||
let uploadButton: HTMLButtonElement;
|
let uploadButton: HTMLButtonElement;
|
||||||
let resumeBanner: HTMLDivElement;
|
let resumeBanner: HTMLDivElement;
|
||||||
let activeProjectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? "";
|
let activeProjectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? "";
|
||||||
@@ -653,22 +650,20 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
|
|
||||||
pageError.textContent = "";
|
pageError.textContent = "";
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
const uploaded: UploadedFileResult[] = [];
|
|
||||||
try {
|
try {
|
||||||
for (let index = 0; index < targetStates.length; index += 1) {
|
for (let index = 0; index < targetStates.length; index += 1) {
|
||||||
const state = targetStates[index];
|
const state = targetStates[index];
|
||||||
uploaded.push(
|
await uploadOneFile(
|
||||||
...(await uploadOneFile(
|
activeProjectId,
|
||||||
activeProjectId,
|
state,
|
||||||
state,
|
index === targetStates.length - 1,
|
||||||
index === targetStates.length - 1,
|
() => renderSlot(state.slot),
|
||||||
() => renderSlot(state.slot),
|
lasFreeDesign,
|
||||||
lasFreeDesign,
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
clearDerivedCaches(activeProjectId);
|
clearDerivedCaches(activeProjectId);
|
||||||
renderUploadResults(resultList, uploaded);
|
// 업로드 결과는 카드가 이미 완료 상태로 보여 준다 — 같은 내용을 목록으로 또 쌓지
|
||||||
|
// 않는다(2026-09-03 사용자 지시).
|
||||||
showToast(L("B03_File_Upload_Success"), "success");
|
showToast(L("B03_File_Upload_Success"), "success");
|
||||||
|
|
||||||
showToast(L("B03_File_Analysis_InProgress"), "info");
|
showToast(L("B03_File_Analysis_InProgress"), "info");
|
||||||
@@ -737,19 +732,15 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
disabled: true,
|
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");
|
const uploadControlPanel = document.createElement("div");
|
||||||
uploadControlPanel.className = "b03-file__control-panel";
|
uploadControlPanel.className = "b03-file__control-panel";
|
||||||
uploadControlPanel.append(
|
uploadControlPanel.append(subtitle, pickRow, resumeBanner, pageError);
|
||||||
subtitle,
|
|
||||||
overviewBanner,
|
|
||||||
dropzone,
|
|
||||||
// 대시보드 임시 보관함에서 자료를 끌어오는 자리 — 업로드 컨테이너 안에 둔다.
|
|
||||||
tempPicker.root,
|
|
||||||
resumeBanner,
|
|
||||||
pageError,
|
|
||||||
uploadButton,
|
|
||||||
resultList,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 자료의 출처가 둘로 갈린다 — 원청이 준 계획노선, 측량이 준 지형(LAS·래스터).
|
// 자료의 출처가 둘로 갈린다 — 원청이 준 계획노선, 측량이 준 지형(LAS·래스터).
|
||||||
// 좌표계 파일(.prj)도 각각 하나씩 오므로 컨테이너를 나눠야 어느 칸에 무엇을 넣는지
|
// 좌표계 파일(.prj)도 각각 하나씩 오므로 컨테이너를 나눠야 어느 칸에 무엇을 넣는지
|
||||||
@@ -848,7 +839,7 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
const overlays = createWorkflowOverlays({
|
const overlays = createWorkflowOverlays({
|
||||||
title: L("B03_File_Title"),
|
title: L("B03_File_Title"),
|
||||||
progressContent,
|
progressContent,
|
||||||
optionsContent: createInputGuide(),
|
optionsContent: createInputGuide(overviewBanner),
|
||||||
// 공용 워크플로 레이아웃(B04·B05)이 하는 일을 여기서도 한다 — 패널이 열리면 본문을
|
// 공용 워크플로 레이아웃(B04·B05)이 하는 일을 여기서도 한다 — 패널이 열리면 본문을
|
||||||
// 그만큼 밀어내지 않으면 안내가 카드 위를 덮는다.
|
// 그만큼 밀어내지 않으면 안내가 카드 위를 덮는다.
|
||||||
onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen),
|
onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen),
|
||||||
|
|||||||
@@ -45,6 +45,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Wiza 스타일 가이드의 Lavender Glow 그라데이션 및 soft purple border hover */
|
/* 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 블록은 안내 문구를 담느라 컸다. 문구는 좌측
|
/* 한 줄 바(2026-09-03) — 종전 160px 블록은 안내 문구를 담느라 컸다. 문구는 좌측
|
||||||
패널로 갔으므로 여기는 「끌어 놓거나 눌러서 고르는 자리」 표시만 한다. */
|
패널로 갔으므로 여기는 「끌어 놓거나 눌러서 고르는 자리」 표시만 한다. */
|
||||||
.b03-file__dropzone {
|
.b03-file__dropzone {
|
||||||
@@ -266,9 +285,11 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 카드가 좁아지면 한 줄에 제목·상태·[선택]이 못 들어가 제목이 글자 단위로 접힌다
|
||||||
|
(2026-09-03 실측: 안내 패널을 연 상태에서 카드 폭 210px). 폭이 모자라면 열을 줄인다. */
|
||||||
.b03-file__group-content {
|
.b03-file__group-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
gap: var(--spacing-12);
|
gap: var(--spacing-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,11 +392,10 @@
|
|||||||
color: var(--color-deep-iris, #26114a);
|
color: var(--color-deep-iris, #26114a);
|
||||||
font-size: var(--text-body-sm, 14px);
|
font-size: var(--text-body-sm, 14px);
|
||||||
font-weight: var(--font-weight-medium, 500);
|
font-weight: var(--font-weight-medium, 500);
|
||||||
/* 한글은 글자 단위로 끊기므로 어절을 지켜 준다 — "계획
|
/* 제목은 한 줄로 두고 모자라면 말줄임 — 접히면 카드가 세로로 길어진다(2026-09-03). */
|
||||||
노선
|
white-space: nowrap;
|
||||||
도형" 방지. */
|
overflow: hidden;
|
||||||
word-break: keep-all;
|
text-overflow: ellipsis;
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.b03-file__card-ext,
|
.b03-file__card-ext,
|
||||||
@@ -525,65 +545,3 @@
|
|||||||
.b03-file__card--error .b03-file__progress-section {
|
.b03-file__card--error .b03-file__progress-section {
|
||||||
display: none;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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건을 청크로 올린다. 중단된 세션이 있으면 그 지점부터 이어 올린다.
|
* 파일 1건을 청크로 올린다. 중단된 세션이 있으면 그 지점부터 이어 올린다.
|
||||||
* 진행 상황은 `onProgress`로만 알린다 — 갱신 주기는 config 값으로 제한한다.
|
* 진행 상황은 `onProgress`로만 알린다 — 갱신 주기는 config 값으로 제한한다.
|
||||||
|
|||||||
Reference in New Issue
Block a user