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 {
|
function onFileSelected(selection: readonly File[], targetSlot?: FileSlot): void {
|
||||||
if (selection.length === 0) return;
|
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개를 고른
|
// 같은 슬롯을 다시 고르는 것은 교체라 개수가 늘지 않는다 — 더하기로 세면 5개를 고른
|
||||||
// 뒤 파일 선택 영역으로 하나만 바꾸려 해도 초과로 막힌다(2026-08-08).
|
// 뒤 파일 선택 영역으로 하나만 바꾸려 해도 초과로 막힌다(2026-08-08).
|
||||||
// 파일 하나에 카드 하나다. `.prj`만 확장자로 안 갈리므로 노선 도형과 basename이
|
// 파일 하나에 카드 하나다. `.prj`만 확장자로 안 갈리므로 노선 도형과 basename이
|
||||||
// 같은지로 노선/지형 좌표계 카드를 정한다(2026-08-31 사용자 지시).
|
// 같은지로 노선/지형 좌표계 카드를 정한다(2026-08-31 사용자 지시).
|
||||||
const routeFile = slots.get("csv")?.file?.name;
|
const routeFile = slots.get("csv")?.file?.name;
|
||||||
const assignments = planSlotAssignments(
|
const planned = planSlotAssignments(
|
||||||
files,
|
selection,
|
||||||
slotConfigs(),
|
slotConfigs(),
|
||||||
routeFile ? routeFile.replace(/\.[^.]*$/, "") : undefined,
|
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));
|
const occupied = new Set(selectedStates().map((state) => state.slot));
|
||||||
for (const item of assignments) {
|
for (const item of assignments) {
|
||||||
const slot = targetSlot ?? item.slot;
|
const slot = targetSlot ?? item.slot;
|
||||||
@@ -380,7 +383,8 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
*/
|
*/
|
||||||
function isSlotRequired(state: FileSlotState): boolean {
|
function isSlotRequired(state: FileSlotState): boolean {
|
||||||
if (SHAPEFILE_DEPENDENT_SLOTS.includes(state.slot)) return routeIsShapefile();
|
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;
|
return state.isRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,18 +776,22 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
lasFreeRow.append(lasFreeCheck, lasFreeText);
|
lasFreeRow.append(lasFreeCheck, lasFreeText);
|
||||||
function applyLasFreeState(): void {
|
function applyLasFreeState(): void {
|
||||||
lasFreeCheck.checked = lasFreeDesign;
|
lasFreeCheck.checked = lasFreeDesign;
|
||||||
const card = cardMap.get("las_laz");
|
terrainGroup.classList.toggle("b03-file__group--disabled", lasFreeDesign);
|
||||||
card?.classList.toggle("b03-file__card--disabled", lasFreeDesign);
|
for (const slot of TERRAIN_SLOTS) {
|
||||||
// 카드를 회색으로 덮는 것만으로는 선택이 막히지 않는다 — 버튼·input을 실제로 잠근다.
|
const card = cardMap.get(slot);
|
||||||
card
|
card?.classList.toggle("b03-file__card--disabled", lasFreeDesign);
|
||||||
?.querySelectorAll<HTMLButtonElement | HTMLInputElement>(
|
// 카드를 회색으로 덮는 것만으로는 선택이 막히지 않는다 — 버튼·input을 실제로 잠근다.
|
||||||
".b03-file__card-select, .b03-file__slot-input",
|
card
|
||||||
)
|
?.querySelectorAll<HTMLButtonElement | HTMLInputElement>(
|
||||||
.forEach((element) => {
|
".b03-file__card-select, .b03-file__card-remove, .b03-file__slot-input",
|
||||||
element.disabled = lasFreeDesign;
|
)
|
||||||
});
|
.forEach((element) => {
|
||||||
// 켜기 전에 이미 골라 둔 LAS는 내린다 — 켠 채로 남아 올라가는 사고를 막는다.
|
element.disabled = lasFreeDesign;
|
||||||
if (lasFreeDesign && slots.get("las_laz")?.file) removeFile("las_laz");
|
});
|
||||||
|
// 켜기 전에 골라 둔 로컬 파일은 내린다 — 켠 채로 남아 올라가는 사고를 막는다.
|
||||||
|
// 이미 서버에 올라간 자료(재입력)는 그대로 두고 조작만 잠근다(2026-09-03 사용자 지시).
|
||||||
|
if (lasFreeDesign && slots.get(slot)?.file) removeFile(slot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
lasFreeCheck.addEventListener("change", () => {
|
lasFreeCheck.addEventListener("change", () => {
|
||||||
lasFreeDesign = lasFreeCheck.checked;
|
lasFreeDesign = lasFreeCheck.checked;
|
||||||
@@ -798,8 +806,9 @@ export async function renderB03FileInput(root: HTMLElement): Promise<void> {
|
|||||||
updateUploadButton();
|
updateUploadButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
// LAS 토글은 지형 컨테이너의 것이다 — 켜면 그 안의 포인트클라우드 카드만 잠긴다.
|
// LAS 토글은 **파일 입력 컨테이너**의 것이다(2026-09-03 사용자 지시) — 무엇을 받을지
|
||||||
terrainGroup.append(lasFreeRow);
|
// 정하는 스위치라 고르는 자리 옆에 선다. 켜면 지형 컨테이너가 통째로 잠긴다.
|
||||||
|
uploadControlPanel.insertBefore(lasFreeRow, resumeBanner);
|
||||||
|
|
||||||
const routePanel = document.createElement("div");
|
const routePanel = document.createElement("div");
|
||||||
routePanel.className = "b03-file__control-panel b03-file__cards-container-panel";
|
routePanel.className = "b03-file__control-panel b03-file__cards-container-panel";
|
||||||
|
|||||||
@@ -212,6 +212,55 @@
|
|||||||
align-items: start;
|
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)의
|
/* 좌측 안내 패널이 열리면 본문을 그만큼 밀어낸다 — 공용 워크플로 레이아웃(B04·B05)의
|
||||||
`.ui-workflow-layout__body` 규칙과 같은 값. 접히면 손잡이 폭만 남긴다. */
|
`.ui-workflow-layout__body` 규칙과 같은 값. 접히면 손잡이 폭만 남긴다. */
|
||||||
.b03-file__main-layout {
|
.b03-file__main-layout {
|
||||||
@@ -261,15 +310,16 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 글자 크기·색도 공용 양식 그대로 — B04 좌측 패널 본문(`b04-surface__check`)과 같은
|
||||||
|
`--text-body-sm` · `--color-text-body`(2026-09-03 사용자 지시: 양식 = 글자 크기 포함). */
|
||||||
.b03-file__guide-list {
|
.b03-file__guide-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: var(--spacing-16);
|
padding-left: var(--spacing-16);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-4);
|
gap: var(--spacing-4);
|
||||||
font-size: var(--text-caption, 12px);
|
font-size: var(--text-body-sm);
|
||||||
line-height: 1.55;
|
color: var(--color-text-body);
|
||||||
color: var(--color-text-secondary, #615e6e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.b03-file__group {
|
.b03-file__group {
|
||||||
|
|||||||
Reference in New Issue
Block a user