Files
Aislo/B05_Profile/B05_Profile_UI_Panel_Fields.ts
T
eomsangdonandClaude Opus 5.5 4668642d2c refactor(B05): 좌측 패널 · 곡선 이름표 날 칸을 부품 입력 · 고르기로 (PLAN 553-2)
- 구조물 배치 · 비정규 측점 · 배수 시설 · 설정 칸 17 자리 = ui-input · ui-select(design_structure_form 포함 — M02 도 같은 꼴)
- 페이지 칸 꼴(테두리 · 바탕 · 둥글기 · 여백) 지움 · 높이 = 좌측 패널 양식(26) 그대로 · 고르기 화살표 자리 = 패널 양식 값
- 구조물 조합 이름 칸 = 부품 칸 · 곡선 반지름 · 길이 칸 = 단 24
- 칸 자리 전후 다름 0 · 그림 색 다름 0(밝음 · 어두움)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:41:58 +09:00

117 lines
4.7 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Panel_Fields.ts
* 좌측 패널이 함께 쓰는 **값 · 상수 · 입력 칸 만들기** — `B05_Profile_UI_Panel.ts` 가 714줄이라
* 떼어낸 조각이다(계획서 24-2b). 본문 로직과 수치는 그대로다.
* ========================================================================== */
import { type ButtonVariant, createButton } from "@ui/ui_template_elements";
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
// 법정 기준표 = logic(511-B B-3 · X5) — 부르는 쪽은 그대로 여기서 받음
export { PROFILE_CRITERIA, SPEED_CHOICES, gradeLimitOf } from "./logic/B05_Profile_Grade_Criteria";
export interface RoutePanelValues {
contourInterval: number;
/** 임도 종류 — 현행 3종(간선·산불진화·작업). branch(지선)는 폐지됐으나 기존
* 저장분 호환을 위해 값으로는 받는다(2026-08-19). 값의 원천은 프로젝트 등록
* 정보(projects.road_type)이며 화면에서는 고르지 않는다. */
gradeClass: "trunk" | "fire" | "branch" | "work";
/** 설계속도(km/h) — 임도는 속도를 낼 수 없는 노선이라 기본 20이다. 간선·산불진화만
* 20~40에서 고를 수 있고 작업임도는 20 고정(별표2, 2026-08-19 사용자 확정). */
designSpeed: 20 | 30 | 40;
stationInterval: number | null;
crossSampleInterval: number | null;
longSampleInterval: number | null;
terrainType: "normal" | "special";
maxGradePct: number | null;
minVerticalRadius: number | null;
minTangentLength: number | null;
startElevationOffset: number | null;
endElevationOffset: number | null;
/** 횡단배수 최소 계획고를 계획선·편집에 강제할지 — 기본 해제(2026-09-01 사용자 지시).
* 해제해도 부족분 경고는 그대로 뜬다. 강제만 풀고 판단은 사용자에게 맡긴다. */
enforcePipeClearance: boolean;
/** 시작 측점 · 누가거리 표시(448 저장) — B05 라벨만 · 측점 열쇠 · 다른 페이지에 안 씀. */
stationDisplay: { station: number; cumulative: number };
}
/** 측점 간격(m) — 20m 고정(2026-08-18 사용자 확정, 입력 UI 삭제). */
export const STATION_INTERVAL_M = 20;
/** 임도 종류별 표시 이름 — 프로젝트 등록(B02)과 같은 말을 쓴다(2026-08-19 확정). */
export const ROAD_TYPE_LABELS: Record<string, string> = {
trunk: "간선임도",
fire: "산불진화임도",
work: "작업임도",
branch: "작업임도(구 지선)",
};
export type WrappedInput = HTMLInputElement & { wrapper: HTMLLabelElement };
export function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
export function button(
label: string,
onClick: () => void,
variant: ButtonVariant = "ghost",
): HTMLButtonElement {
return createButton({ label, variant, onClick: () => onClick() });
}
export function numberField(label: string, value = ""): WrappedInput {
const wrapper = document.createElement("label");
wrapper.className = "b05-route__field";
const caption = document.createElement("span");
caption.textContent = label;
const input = document.createElement("input");
input.className = "ui-input";
input.type = "number";
input.step = "0.01";
input.value = value;
wrapper.append(caption, input);
return Object.assign(input, { wrapper });
}
export function checkbox(label: string, checked: boolean): WrappedInput {
// 부품 「체크」 클래스(553-2) — 시험이 별칭 없이 이 파일을 돌려 클래스로 붙임
const wrapper = document.createElement("label");
wrapper.className = "ui-checkbox b05-route__check";
const input = document.createElement("input");
input.type = "checkbox";
input.className = "ui-checkbox__input";
input.checked = checked;
const caption = document.createElement("span");
caption.className = "ui-checkbox__text";
caption.textContent = label;
wrapper.append(input, caption);
return Object.assign(input, { wrapper });
}
export function toggleButton(
label: string,
checked: boolean,
onChange: (checked: boolean) => void,
): HTMLButtonElement {
const element = button(
label,
() => {
const active = !element.classList.contains("is-active");
element.classList.toggle("is-active", active);
element.setAttribute("aria-pressed", String(active));
onChange(active);
},
"glass",
);
element.classList.toggle("is-active", checked);
element.setAttribute("aria-pressed", String(checked));
return element;
}
export function parseOptional(input: HTMLInputElement): number | null {
if (!input.value.trim()) return null;
const value = Number(input.value);
return Number.isFinite(value) ? value : null;
}