Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_1
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/* =============================================================================
|
||||
* B03_FileInput_UI_Guide.ts
|
||||
* 파일 입력 좌측 안내 패널 (2026-09-03 사용자 지시).
|
||||
*
|
||||
* 종전에는 고르는 방법·필요한 파일 목록이 **선택 영역과 카드 안에** 길게 들어가 있어
|
||||
* 카드 한 장이 220px씩 차지했다. 안내는 한 번 읽으면 되는 것이라 다른 단계(B04·B05)와
|
||||
* 같은 자리 — 공용 오버레이의 좌측 패널(`createWorkflowOverlays.optionsContent`) — 로
|
||||
* 옮기고, 본문은 고르는 자리만 남긴다.
|
||||
*
|
||||
* 패널 양식은 **B04~B07과 같은 공용 양식**을 그대로 쓴다(2026-09-03 사용자 지시):
|
||||
* 패널 루트 `{page}__form`, 문단은 `ui-collapsible ui-sidebar-section` + 제목에
|
||||
* `ui-collapsible__title`. 제목 행을 누르면 접히는 동작·캐럿·외곽선이 전부 공용 것이다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
/** 안내 문단 하나 — 공용 접기 컨테이너(B06 `buildGroup`과 같은 조립). */
|
||||
function section(
|
||||
titleKey: keyof typeof ui_locales,
|
||||
itemKeys: (keyof typeof ui_locales)[],
|
||||
): HTMLElement {
|
||||
const group = document.createElement("section");
|
||||
group.className = "b03-file__guide-group ui-collapsible ui-sidebar-section";
|
||||
|
||||
const title = document.createElement("h3");
|
||||
title.className = "b03-file__guide-legend ui-collapsible__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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 좌측 패널 본문 — 필요한 파일 · 고르는 방법 · 알아 둘 것.
|
||||
*
|
||||
* `statusNote`(재접속 현황 배너)는 「고르는 방법」 끝에 붙는다 — 프로젝트가 이미 완료라
|
||||
* 다시 올리면 교체된다는 안내라서, 고르기 전에 읽을 것들과 같은 자리에 둔다
|
||||
* (2026-09-03 사용자 지시).
|
||||
*/
|
||||
export function createInputGuide(statusNote?: HTMLElement): HTMLElement {
|
||||
const guide = document.createElement("div");
|
||||
guide.className = "b03-file__form";
|
||||
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",
|
||||
]),
|
||||
howTo,
|
||||
section("B03_Guide_Notes_Title", [
|
||||
"B03_Guide_Notes_Replace",
|
||||
"B03_Guide_Notes_Crs",
|
||||
"B03_Guide_Notes_LasFree",
|
||||
]),
|
||||
);
|
||||
attachCollapsible(guide);
|
||||
return guide;
|
||||
}
|
||||
@@ -10,12 +10,13 @@ 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";
|
||||
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 {
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
confirmReplaceUpload,
|
||||
isInitialPipelineRunning,
|
||||
pollInitialPipeline,
|
||||
renderUploadResults,
|
||||
uploadOneFile,
|
||||
} from "./B03_FileInput_UI_Upload";
|
||||
import {
|
||||
@@ -62,8 +62,6 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
// 대시보드 임시 보관함에서 가져올 자료 선택기 — 선택되면 [업로드]가 이동을 수행한다.
|
||||
const tempPicker = createTempPicker(() => updateUploadButton());
|
||||
const cardMap = new Map<FileSlot, HTMLElement>();
|
||||
const resultList = document.createElement("ul");
|
||||
resultList.className = "b03-file__results";
|
||||
let uploadButton: HTMLButtonElement;
|
||||
let resumeBanner: HTMLDivElement;
|
||||
let activeProjectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY) ?? "";
|
||||
@@ -97,9 +95,8 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
dropzone.tabIndex = 0;
|
||||
const dropzoneLabel = document.createElement("strong");
|
||||
dropzoneLabel.textContent = L("B03_File_Select_Label");
|
||||
const dropzoneHint = document.createElement("span");
|
||||
dropzoneHint.textContent = L("B03_File_Select_Hint");
|
||||
dropzone.append(dropzoneLabel, dropzoneHint, fileInput);
|
||||
// 설명은 좌측 안내 패널 몫이다(2026-09-03 사용자 지시) — 여기는 자리 이름만 남긴다.
|
||||
dropzone.append(dropzoneLabel, fileInput);
|
||||
|
||||
const pageError = document.createElement("p");
|
||||
pageError.className = "b03-file__error";
|
||||
@@ -184,7 +181,10 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
const validation = validateSlots();
|
||||
uploadButton.disabled = validation !== null;
|
||||
pageError.textContent = validation ?? "";
|
||||
// 「업로드할 파일을 선택하세요」는 버튼이 잠긴 것으로 이미 드러난다 — 고르기도 전에
|
||||
// 붉은 경고를 띄우지 않는다(2026-09-03 사용자 지시). 나머지 사유는 그대로 알린다.
|
||||
pageError.textContent =
|
||||
validation === null || validation === L("B03_File_Error_Required") ? "" : validation;
|
||||
}
|
||||
|
||||
/** 확장자 줄 — 지금 필수인지에 따라 "· 선택" 꼬리표가 붙고 떨어진다. */
|
||||
@@ -302,28 +302,31 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
|
||||
function onFileSelected(selection: readonly File[], targetSlot?: FileSlot): void {
|
||||
if (selection.length === 0) return;
|
||||
// LAS 없이 설계를 켜면 포인트클라우드는 아예 받지 않는다 (2026-08-30 사용자 지시) —
|
||||
// 카드를 회색으로 덮어도 파일 선택 영역·드롭으로 들어올 수 있어 여기서 걸러 낸다.
|
||||
const pointCloudExtensions = slots.get("las_laz")?.extensions ?? [];
|
||||
const files = lasFreeDesign
|
||||
? selection.filter((file) => !pointCloudExtensions.includes(getExtension(file.name)))
|
||||
: selection;
|
||||
const blocked = files.length !== selection.length;
|
||||
if (blocked && files.length === 0) {
|
||||
pageError.textContent = L("B03_File_Error_LasFreeBlocked");
|
||||
return;
|
||||
}
|
||||
// 개수는 "고른 파일 수"가 아니라 **최종적으로 차는 슬롯 수**로 센다.
|
||||
// 같은 슬롯을 다시 고르는 것은 교체라 개수가 늘지 않는다 — 더하기로 세면 5개를 고른
|
||||
// 뒤 파일 선택 영역으로 하나만 바꾸려 해도 초과로 막힌다(2026-08-08).
|
||||
// 파일 하나에 카드 하나다. `.prj`만 확장자로 안 갈리므로 노선 도형과 basename이
|
||||
// 같은지로 노선/지형 좌표계 카드를 정한다(2026-08-31 사용자 지시).
|
||||
const routeFile = slots.get("csv")?.file?.name;
|
||||
const assignments = planSlotAssignments(
|
||||
files,
|
||||
const planned = planSlotAssignments(
|
||||
selection,
|
||||
slotConfigs(),
|
||||
routeFile ? routeFile.replace(/\.[^.]*$/, "") : undefined,
|
||||
);
|
||||
// LAS 없이 설계를 켜면 **지형 자료를 통째로** 받지 않는다(2026-09-03 사용자 지시 —
|
||||
// 종전에는 포인트클라우드만 걸렀다). 확장자가 아니라 **배정된 카드**로 거른다: `.prj`는
|
||||
// 노선·지형이 같은 확장자라 확장자로 거르면 노선 좌표계까지 함께 떨어진다.
|
||||
const assignments = lasFreeDesign
|
||||
? planned.filter((item) => {
|
||||
const slot = targetSlot ?? item.slot;
|
||||
return slot === undefined || !TERRAIN_SLOTS.includes(slot);
|
||||
})
|
||||
: planned;
|
||||
const blocked = assignments.length !== planned.length;
|
||||
if (assignments.length === 0) {
|
||||
pageError.textContent = blocked ? L("B03_File_Error_LasFreeBlocked") : "";
|
||||
return;
|
||||
}
|
||||
const occupied = new Set(selectedStates().map((state) => state.slot));
|
||||
for (const item of assignments) {
|
||||
const slot = targetSlot ?? item.slot;
|
||||
@@ -379,7 +382,8 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
*/
|
||||
function isSlotRequired(state: FileSlotState): boolean {
|
||||
if (SHAPEFILE_DEPENDENT_SLOTS.includes(state.slot)) return routeIsShapefile();
|
||||
if (state.slot === "las_laz") return !lasFreeDesign;
|
||||
// LAS 없이 설계면 지형 자료(포인트클라우드·좌표계·래스터)는 통째로 받지 않는다.
|
||||
if (TERRAIN_SLOTS.includes(state.slot)) return lasFreeDesign ? false : state.isRequired;
|
||||
return state.isRequired;
|
||||
}
|
||||
|
||||
@@ -430,10 +434,14 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
? inRouteSet
|
||||
? "route_prj"
|
||||
: "prj"
|
||||
: Array.from(slots.values()).find(
|
||||
(candidate) =>
|
||||
candidate.slot !== "route_prj" && candidate.extensions.includes(extension),
|
||||
)?.slot;
|
||||
: // 옛 프로젝트의 노선은 `.csv`로 올라가 있다 — 이제 받지는 않지만(2026-09-03)
|
||||
// 이미 올라간 것은 계획노선 도형 카드에 그대로 보여야 한다.
|
||||
extension === ".csv"
|
||||
? "csv"
|
||||
: Array.from(slots.values()).find(
|
||||
(candidate) =>
|
||||
candidate.slot !== "route_prj" && candidate.extensions.includes(extension),
|
||||
)?.slot;
|
||||
const state = slot ? slots.get(slot) : undefined;
|
||||
if (state) {
|
||||
state.serverUploaded = {
|
||||
@@ -652,22 +660,20 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
|
||||
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");
|
||||
@@ -736,28 +742,25 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
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)도 각각 하나씩 오므로 컨테이너를 나눠야 어느 칸에 무엇을 넣는지
|
||||
// 화면만 보고 안다(2026-08-31 사용자 지시).
|
||||
// 안내 문구 없음 — CSV 관련 설명이 붙어 있었으나 CSV는 사용자가 넣는 자료가 아니다
|
||||
// (2026-09-03 사용자 지시: 내부 계산 파일).
|
||||
const routeGroup = createCardGroup(
|
||||
L("B03_File_Group_Route"),
|
||||
ROUTE_SLOTS,
|
||||
"b03-file__group--route",
|
||||
L("B03_File_Group_Route_Hint"),
|
||||
);
|
||||
const terrainGroup = createCardGroup(
|
||||
L("B03_File_Group_Terrain"),
|
||||
@@ -777,18 +780,22 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
lasFreeRow.append(lasFreeCheck, lasFreeText);
|
||||
function applyLasFreeState(): void {
|
||||
lasFreeCheck.checked = lasFreeDesign;
|
||||
const card = cardMap.get("las_laz");
|
||||
card?.classList.toggle("b03-file__card--disabled", lasFreeDesign);
|
||||
// 카드를 회색으로 덮는 것만으로는 선택이 막히지 않는다 — 버튼·input을 실제로 잠근다.
|
||||
card
|
||||
?.querySelectorAll<HTMLButtonElement | HTMLInputElement>(
|
||||
".b03-file__card-select, .b03-file__slot-input",
|
||||
)
|
||||
.forEach((element) => {
|
||||
element.disabled = lasFreeDesign;
|
||||
});
|
||||
// 켜기 전에 이미 골라 둔 LAS는 내린다 — 켠 채로 남아 올라가는 사고를 막는다.
|
||||
if (lasFreeDesign && slots.get("las_laz")?.file) removeFile("las_laz");
|
||||
terrainGroup.classList.toggle("b03-file__group--disabled", lasFreeDesign);
|
||||
for (const slot of TERRAIN_SLOTS) {
|
||||
const card = cardMap.get(slot);
|
||||
card?.classList.toggle("b03-file__card--disabled", lasFreeDesign);
|
||||
// 카드를 회색으로 덮는 것만으로는 선택이 막히지 않는다 — 버튼·input을 실제로 잠근다.
|
||||
card
|
||||
?.querySelectorAll<HTMLButtonElement | HTMLInputElement>(
|
||||
".b03-file__card-select, .b03-file__card-remove, .b03-file__slot-input",
|
||||
)
|
||||
.forEach((element) => {
|
||||
element.disabled = lasFreeDesign;
|
||||
});
|
||||
// 켜기 전에 골라 둔 로컬 파일은 내린다 — 켠 채로 남아 올라가는 사고를 막는다.
|
||||
// 이미 서버에 올라간 자료(재입력)는 그대로 두고 조작만 잠근다(2026-09-03 사용자 지시).
|
||||
if (lasFreeDesign && slots.get(slot)?.file) removeFile(slot);
|
||||
}
|
||||
}
|
||||
lasFreeCheck.addEventListener("change", () => {
|
||||
lasFreeDesign = lasFreeCheck.checked;
|
||||
@@ -803,8 +810,9 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
updateUploadButton();
|
||||
});
|
||||
|
||||
// LAS 토글은 지형 컨테이너의 것이다 — 켜면 그 안의 포인트클라우드 카드만 잠긴다.
|
||||
terrainGroup.append(lasFreeRow);
|
||||
// LAS 토글은 **파일 입력 컨테이너**의 것이다(2026-09-03 사용자 지시) — 무엇을 받을지
|
||||
// 정하는 스위치라 고르는 자리 옆에 선다. 켜면 지형 컨테이너가 통째로 잠긴다.
|
||||
uploadControlPanel.insertBefore(lasFreeRow, resumeBanner);
|
||||
|
||||
const routePanel = document.createElement("div");
|
||||
routePanel.className = "b03-file__control-panel b03-file__cards-container-panel";
|
||||
@@ -842,10 +850,15 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
||||
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(overviewBanner),
|
||||
// 공용 워크플로 레이아웃(B04·B05)이 하는 일을 여기서도 한다 — 패널이 열리면 본문을
|
||||
// 그만큼 밀어내지 않으면 안내가 카드 위를 덮는다.
|
||||
onOptionsOpenChange: (isOpen) => layout.root.classList.toggle("is-options-open", isOpen),
|
||||
});
|
||||
layout.root.append(overlays.root);
|
||||
pageRoot = layout.root;
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
.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 → 16 (2026-09-03) — 고르는 자리만 남기고 안내를 좌측 패널로 뺐다.
|
||||
`--spacing-20`은 테마에 없는 토큰이라 선언째 무효가 된다(패딩이 통째로 사라짐). */
|
||||
padding: var(--spacing-16);
|
||||
border-radius: var(--radius-large, 24px); /* Wiza 24px 대형 둥글기 */
|
||||
border: 1px solid var(--color-mist, #e6e2e3);
|
||||
box-shadow: var(--shadow-lg);
|
||||
@@ -43,15 +45,40 @@
|
||||
}
|
||||
|
||||
/* Wiza 스타일 가이드의 Lavender Glow 그라데이션 및 soft purple border hover */
|
||||
/* 고르는 자리 한 줄 — [입력 파일 선택] · [임시 보관함] · [파일 업로드](2026-09-03).
|
||||
좁아지면 아래로 접히되, 선택 영역이 남는 폭을 먹는다. */
|
||||
/* 셋을 같은 폭·같은 높이로 나눈다 — 열 1:1:1(2026-09-03 사용자 지시).
|
||||
flex 로는 버튼·드롭존의 내용 폭이 배분에 섞여 288·254·288 로 어긋났다. 격자는
|
||||
칸을 먼저 나누므로 내용과 무관하게 정확히 1:1:1 이 된다. 좁아지면 칸이 접힌다. */
|
||||
.b03-file__pick-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b03-file__pick-row > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 칸을 꽉 채워야 세 칸의 높이가 같아진다 — 버튼은 기본이 내용 크기다. */
|
||||
.b03-file__pick-row .b03-file__temp-picker > .ui-btn,
|
||||
.b03-file__pick-row > .ui-btn {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 한 줄 바(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 +104,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);
|
||||
}
|
||||
|
||||
@@ -86,11 +113,16 @@
|
||||
color: var(--color-slate, #615e6e);
|
||||
}
|
||||
|
||||
/* 빈 오류 줄이 자리를 잡고 있으면 토글 아래가 통째로 비어 보인다 — 글자가 있을 때만
|
||||
자리를 차지한다(2026-09-03 사용자 지시). */
|
||||
.b03-file__error {
|
||||
min-height: var(--spacing-16);
|
||||
color: var(--color-danger, #dc2626);
|
||||
font-size: var(--text-body-sm, 14px);
|
||||
margin: var(--spacing-8) 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.b03-file__error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.b03-file__resume {
|
||||
@@ -185,14 +217,124 @@
|
||||
.b03-file__columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--spacing-24);
|
||||
gap: var(--spacing-16);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* LAS 없이 설계 토글 — 파일 입력 컨테이너의 스위치(2026-09-03 사용자 지시).
|
||||
B04 좌측 패널의 체크 줄(`b04-surface__check`)과 같은 서식이다. */
|
||||
.b03-file__lasfree {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
align-self: flex-start;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 1px solid var(--color-mist, #e6e2e3);
|
||||
border-radius: var(--radius-buttons, 8px);
|
||||
font-size: var(--text-body-sm, 14px);
|
||||
color: var(--color-deep-iris, #26114a);
|
||||
cursor: pointer;
|
||||
transition: border-color var(--transition-base, 0.2s);
|
||||
}
|
||||
|
||||
.b03-file__lasfree:hover {
|
||||
border-color: var(--color-royal-amethyst, #3e0079);
|
||||
}
|
||||
|
||||
.b03-file__lasfree input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
accent-color: var(--color-royal-amethyst, #3e0079);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 켜져 있으면 스위치 자신도 켜진 티를 낸다 — 지형 컨테이너가 왜 잠겼는지의 근거. */
|
||||
.b03-file__lasfree:has(input:checked) {
|
||||
border-color: var(--color-royal-amethyst, #3e0079);
|
||||
background: var(--color-mist-violet, #edecff);
|
||||
font-weight: var(--font-weight-medium, 500);
|
||||
}
|
||||
|
||||
/* LAS 없이 설계를 켜면 지형 컨테이너는 받지 않는다 — 잠긴 것이 보이게 흐린다. */
|
||||
.b03-file__group--disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.b03-file__card--disabled {
|
||||
background: var(--color-paper, #f6f7fa);
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.b03-file__card--disabled .b03-file__card-select {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 좌측 안내 패널이 열리면 본문을 그만큼 밀어낸다 — 공용 워크플로 레이아웃(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) — 본문에서 뺀 설명이 사는 자리.
|
||||
양식은 B04~B07 좌측 패널과 **같은 공용 양식**이다: 루트 `__form`, 문단은
|
||||
`ui-collapsible ui-sidebar-section`(외곽선·접힘·캐럿은 공용 CSS 몫), 제목은
|
||||
`__group-legend` 크기. 여기서는 값만 맞추고 새로 만들지 않는다. --- */
|
||||
.b03-file__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b03-file__guide-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
margin: 0;
|
||||
padding: var(--spacing-16);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.b03-file__guide-legend {
|
||||
margin: 0;
|
||||
padding: 0 var(--spacing-8);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 글자 크기·색도 공용 양식 그대로 — B04 좌측 패널 본문(`b04-surface__check`)과 같은
|
||||
`--text-body-sm` · `--color-text-body`(2026-09-03 사용자 지시: 양식 = 글자 크기 포함). */
|
||||
.b03-file__guide-list {
|
||||
margin: 0;
|
||||
padding-left: var(--spacing-16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
.b03-file__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-20);
|
||||
gap: var(--spacing-12);
|
||||
}
|
||||
|
||||
.b03-file__group-hint {
|
||||
@@ -201,17 +343,20 @@
|
||||
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;
|
||||
}
|
||||
|
||||
/* 카드가 좁아지면 한 줄에 제목·상태·[선택]이 못 들어가 제목이 글자 단위로 접힌다
|
||||
(2026-09-03 실측: 안내 패널을 연 상태에서 카드 폭 210px). 폭이 모자라면 열을 줄인다. */
|
||||
.b03-file__group-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--spacing-24);
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--spacing-12);
|
||||
}
|
||||
|
||||
/* 업로드 중에는 화면 조작을 막는다 — 중복 업로드·단계 이동 방지(2026-08-08 사용자 지시). */
|
||||
@@ -235,16 +380,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 +423,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,22 +448,20 @@
|
||||
|
||||
.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 {
|
||||
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,
|
||||
@@ -357,25 +503,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 +545,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;
|
||||
@@ -455,65 +611,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;
|
||||
}
|
||||
|
||||
@@ -4,25 +4,11 @@
|
||||
* 모달 껍데기(.b03-file__modal / -backdrop)는 기존 확인 모달 스타일을 그대로 쓴다.
|
||||
* ========================================================================== */
|
||||
|
||||
/* 버튼 하나만 담는 자리 — 테두리를 또 두르면 버튼 테두리와 이중이 된다
|
||||
(2026-09-03 사용자 지시). */
|
||||
.b03-file__temp-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-12);
|
||||
flex-wrap: wrap;
|
||||
padding: var(--spacing-12);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
}
|
||||
|
||||
.b03-file__temp-summary {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.b03-file__temp-summary.is-active {
|
||||
color: var(--color-success);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.b03-file__temp-modal {
|
||||
|
||||
@@ -60,7 +60,9 @@ const SLOT_CONFIGS: readonly SlotConfig[] = [
|
||||
slot: "csv",
|
||||
labelKey: "B03_File_Slot_PlannedRoute",
|
||||
icon: "⌁",
|
||||
extensions: [".csv", ".shp"],
|
||||
// `.csv`는 받지 않는다 — 내부 계산이 만드는 파일이라 사용자가 넣는 자료가 아니다
|
||||
// (2026-09-03 사용자 지시). 슬롯 키 `csv`는 저장·서버 규약이라 그대로 둔다.
|
||||
extensions: [".shp"],
|
||||
isRequired: true,
|
||||
},
|
||||
{
|
||||
@@ -208,10 +210,10 @@ export function createFileCardTemplate(): HTMLTemplateElement {
|
||||
<span class="b03-file__card-ext"></span>
|
||||
</div>
|
||||
<div class="b03-file__card-badge-container"></div>
|
||||
<button class="b03-file__card-select" type="button"></button>
|
||||
<button class="b03-file__card-remove" type="button"></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>
|
||||
|
||||
@@ -46,19 +46,15 @@ export function createTempPicker(
|
||||
variant: "ghost",
|
||||
onClick: () => void openModal(),
|
||||
});
|
||||
const summary = document.createElement("span");
|
||||
summary.className = "b03-file__temp-summary";
|
||||
summary.textContent = L("B03_Temp_None");
|
||||
root.append(openButton, summary);
|
||||
// 선택 요약(「선택된 보관 자료 없음」·묶음 이름)은 두지 않는다 — 불러온 자료는 카드가
|
||||
// 곧바로 보여 주고, 고르기 전 상태는 [파일 업로드]가 잠긴 것으로 이미 드러난다
|
||||
// (2026-09-03 사용자 지시).
|
||||
root.append(openButton);
|
||||
|
||||
let selectedBatch: TempBatchItem | null = null;
|
||||
|
||||
function applySelection(batch: TempBatchItem | null): void {
|
||||
selectedBatch = batch;
|
||||
summary.textContent = batch
|
||||
? `${L("B03_Temp_Selected")} ${batch.name} (${batch.files.length}${L("B03_Temp_FileCount")})`
|
||||
: L("B03_Temp_None");
|
||||
summary.classList.toggle("is-active", Boolean(batch));
|
||||
onSelected(batch);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 값으로 제한한다.
|
||||
|
||||
@@ -224,9 +224,45 @@ export const ui_locales_b1 = {
|
||||
"Upload the required planned route, terrain, and point cloud files.",
|
||||
],
|
||||
B03_File_Select_Label: ["입력 파일 선택", "Select input files"],
|
||||
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.",
|
||||
/* --- 좌측 안내 패널 (2026-09-03) --- */
|
||||
B03_Guide_Files_Title: ["필요한 파일", "Files you need"],
|
||||
B03_Guide_Files_Route: [
|
||||
"계획노선 — shapefile 한 벌(.shp·.shx·.dbf·.cpg·.prj) 5개.",
|
||||
"Planned route — 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: [
|
||||
"[임시 보관함]은 대시보드에 미리 올려 둔 자료를 가져옵니다.",
|
||||
"[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."],
|
||||
@@ -268,10 +304,6 @@ export const ui_locales_b1 = {
|
||||
B03_File_Group_Inputs: ["입력 자료", "Input files"],
|
||||
B03_File_Group_Route: ["계획노선 자료", "Planned route files"],
|
||||
B03_File_Group_Terrain: ["지형 자료 (LAS)", "Terrain files (LAS)"],
|
||||
B03_File_Group_Route_Hint: [
|
||||
"shapefile은 파일 5개가 한 벌입니다. CSV 한 장으로 넣어도 됩니다.",
|
||||
"A shapefile is a set of five files. A single CSV also works.",
|
||||
],
|
||||
B03_File_Group_Terrain_Hint: [
|
||||
"여기의 PRJ·TFW는 지형 자료의 좌표계입니다 — 노선 PRJ와 별개입니다.",
|
||||
"The PRJ and TFW here describe the terrain data, separate from the route PRJ.",
|
||||
@@ -295,9 +327,7 @@ export const ui_locales_b1 = {
|
||||
B03_File_Slot_TerrainDem: ["지형 래스터", "Terrain DEM"],
|
||||
B03_File_Slot_CadDrawing: ["CAD 도면", "CAD Drawing"],
|
||||
/* --- B03 임시 보관함 불러오기 (2026-08-08) --- */
|
||||
B03_Temp_Btn_Open: ["임시 보관함에서 불러오기", "Load from temporary storage"],
|
||||
B03_Temp_None: ["선택된 보관 자료 없음", "No stored set selected"],
|
||||
B03_Temp_Selected: ["선택됨:", "Selected:"],
|
||||
B03_Temp_Btn_Open: ["임시 보관함", "Temporary storage"],
|
||||
B03_Temp_FileCount: ["개 파일", " files"],
|
||||
B03_Temp_Modal_Title: ["보관 자료 선택", "Select stored files"],
|
||||
B03_Temp_Modal_Empty: [
|
||||
|
||||
Reference in New Issue
Block a user