feat(B03): LAS 없이 설계 토글을 입력 컨테이너로 · 켜면 지형 자료 전체 차단
사용자 지시(2026-09-03). - 토글을 지형 컨테이너에서 **파일 입력 컨테이너**로 옮김 — 무엇을 받을지 정하는 스위치라 고르는 자리 옆에 선다. 서식도 입혔음(공용 체크 줄 + 켜짐 강조). - 켜면 지형 자료를 **통째로** 받지 않음: 포인트클라우드·좌표계·래스터 카드를 모두 잠그고 컨테이너를 흐림. 필수 판정에서도 지형 슬롯 전체가 빠진다. - 파일 거르기를 확장자가 아니라 **배정된 카드** 기준으로 바꿈 — `.prj` 는 노선·지형이 같은 확장자라 확장자로 거르면 노선 좌표계까지 함께 떨어졌다. - 이미 올라간 자료(재입력)는 지우지 않고 **조작만 잠금** — 로컬로 고른 파일만 내린다. 좌측 안내 패널의 글자 크기도 공용 양식에 맞춤 — 본문 `--text-body-sm`·`--color-text-body` (B04 `b04-surface__check` 와 같음), 제목 `--text-caption`. 검증: 실측 — 토글이 입력 컨테이너 안에 있고, 켜면 지형 카드 4장이 모두 disabled + [파일 선택] 잠김, 기존 업로드분은 「완료」·파일명 유지. 끄면 원복. 패널 글자 14px/12px. typecheck·prettier 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -303,28 +303,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;
|
||||
@@ -380,7 +383,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;
|
||||
}
|
||||
|
||||
@@ -772,18 +776,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;
|
||||
@@ -798,8 +806,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";
|
||||
|
||||
@@ -212,6 +212,55 @@
|
||||
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 {
|
||||
@@ -261,15 +310,16 @@
|
||||
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-caption, 12px);
|
||||
line-height: 1.55;
|
||||
color: var(--color-text-secondary, #615e6e);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
.b03-file__group {
|
||||
|
||||
Reference in New Issue
Block a user