fix(B03): LAS 없이 설계를 켜면 LAS/LAZ를 아예 받지 않는다

사용자 보고(2026-08-30): 체크한 상태에서 실수로 넣은 LAS가 그대로 업로드됐다.
카드에 --disabled 클래스만 걸려 있어 회색으로 보일 뿐 선택 버튼과 input이
살아 있었고, 서버도 las_free일 때 LAS 1개를 정상 경로로 받아 줬다.

- 화면: 선택 버튼·file input을 실제로 잠그고, 켤 때 이미 고른 LAS는 내린다.
  파일 선택 영역·드롭으로 들어오는 것은 onFileSelected에서 걸러 낸다.
- 서버(신뢰 경계): /files는 las_free면 LAS 0개만 받는다. 큰 LAS가 타는
  청크 업로드 세션 생성도 같은 검사를 건다 — 여기서 막지 않으면 /files 검사를
  통째로 비켜 간다.
This commit is contained in:
2026-08-30 18:11:19 +09:00
parent b59d0e4f2c
commit 3ea6aa5b13
3 changed files with 56 additions and 8 deletions
+31 -6
View File
@@ -332,8 +332,24 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
renderSlot(state.slot);
}
function onFileSelected(files: readonly File[], targetSlot?: FileSlot): void {
if (files.length === 0) return;
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).
@@ -351,7 +367,7 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
pageError.textContent = L("B03_File_Error_Count");
return;
}
pageError.textContent = "";
pageError.textContent = blocked ? L("B03_File_Error_LasFreeBlocked") : "";
void (async () => {
for (const file of files) await assignFileToSlot(file, targetSlot);
await detectPausedUploads();
@@ -773,9 +789,18 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
lasFreeRow.append(lasFreeCheck, lasFreeText);
function applyLasFreeState(): void {
lasFreeCheck.checked = lasFreeDesign;
cardMap
.get("las_laz")
?.classList.toggle("b03-file__card--disabled", 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");
}
lasFreeCheck.addEventListener("change", () => {
lasFreeDesign = lasFreeCheck.checked;