merge: main_laptop_1 665ccf777 — 52-7 충돌 = 양쪽 다 살림
- Panel_Options = 메인 hidesTypeSummary 세 조건(명세 · 정의 그룹 · 종단배수) + 가드레일 solveSplit · 옛 hasSpecContainers 걷음 - 구조물정의.json = 대피로 · 가드레일 · 맹암거 그룹 다 · 판 9 - 이력 판_6 = 메인 것(바탕 판 6 그대로) · 네 판_6 은 판_8 로 - 52-8 숨김(군 단위)과 대피로 목록 빼기(종류 단위)는 자리가 달라 둘 다 둠 - 시험 수 = B05 선택지 28 · 정의 종류 42 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -53,6 +53,8 @@ import {
|
||||
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
||||
import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form";
|
||||
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||
import { fordHeightHint, fordHeightM } from "../A00_Common/design/design_structure_ford_calc";
|
||||
import { guardHeightHint, pipeLengthM } from "../A00_Common/design/design_structure_pipe_calc";
|
||||
import {
|
||||
hasLinks,
|
||||
@@ -250,45 +252,31 @@ export function createFacilityOptionsForm(
|
||||
root.className = "b05-drainage__facility";
|
||||
root.hidden = true;
|
||||
|
||||
// ── 관종 · 관경 한 행 — 세월교 몫(배관은 아래 명세 폼 · PLAN 52-1) ─────────
|
||||
const pipeMaterial = optionalSelect("— 안 정함 —", ["흄관", "VR관", "파형강관"]);
|
||||
const pipeDiameter = document.createElement("select");
|
||||
pipeDiameter.replaceChildren(
|
||||
new Option("— 안 정함 —", ""),
|
||||
...["800", "1000", "1200", "1500"].map((size) => new Option(`Ø${size}`, size)),
|
||||
);
|
||||
const pipeRow = grid(
|
||||
labeled("관종", pipeMaterial, "pipe_kind"),
|
||||
labeled("관경", pipeDiameter, "pipe_diameter_mm"),
|
||||
);
|
||||
|
||||
// ── 배관 = 사용자 구조물 표 01 명세(PLAN 52-1) — 컨테이너 다섯 · 옛 관종 · 날개벽 칸은 뺌(지난 정의 값) ──
|
||||
let linkSource: SpecLinks | null = null;
|
||||
let chainage: number | null = null;
|
||||
const pipeForm = createSpecForm(
|
||||
{
|
||||
get: () => linkSource?.get() ?? {},
|
||||
pick: (key) => linkSource?.pick(key),
|
||||
clear: (key) => linkSource?.clear(key),
|
||||
set: (key, item) => linkSource?.set(key, item),
|
||||
const specLinks: SpecLinks = {
|
||||
get: () => linkSource?.get() ?? {},
|
||||
pick: (key) => linkSource?.pick(key),
|
||||
clear: (key) => linkSource?.clear(key),
|
||||
set: (key, item) => linkSource?.set(key, item),
|
||||
};
|
||||
const pipeForm = createSpecForm(specLinks, {
|
||||
auto: (_c, item) => {
|
||||
if (item.id !== "length") return "";
|
||||
const length = pipeLengthM(chainage);
|
||||
return length === null ? "횡단 설계 뒤 셈" : length.toFixed(2);
|
||||
},
|
||||
{
|
||||
auto: (_c, item) => {
|
||||
if (item.id !== "length") return "";
|
||||
const length = pipeLengthM(chainage);
|
||||
return length === null ? "횡단 설계 뒤 셈" : length.toFixed(2);
|
||||
},
|
||||
hint: (c, item, state) => {
|
||||
const soil = c.id !== "inlet" || state.inlet?.[0]?.structure === "기슭막이";
|
||||
return item.id === "height" && soil ? guardHeightHint(state.pipe?.[0]?.diameter ?? "") : "";
|
||||
},
|
||||
onChange: (c, item) => {
|
||||
if (c.id === "inlet" && (item.id === "structure" || item.id === "form"))
|
||||
inletStructureHandler?.(inletStructureOf());
|
||||
emit();
|
||||
},
|
||||
hint: (c, item, state) => {
|
||||
const soil = c.id !== "inlet" || state.inlet?.[0]?.structure === "기슭막이";
|
||||
return item.id === "height" && soil ? guardHeightHint(state.pipe?.[0]?.diameter ?? "") : "";
|
||||
},
|
||||
);
|
||||
onChange: (c, item) => {
|
||||
if (c.id === "inlet" && (item.id === "structure" || item.id === "form"))
|
||||
inletStructureHandler?.(inletStructureOf());
|
||||
emit();
|
||||
},
|
||||
});
|
||||
// 조정창에서 옮겨 오는 행(단 수·옵션)이 붙을 자리 — 유입 · 유출 컨테이너 끝(2026-08-29 지시 4)
|
||||
const inletSlot = pipeForm.slot("inlet");
|
||||
const outletSlot = pipeForm.slot("outlet");
|
||||
@@ -301,6 +289,26 @@ export function createFacilityOptionsForm(
|
||||
: "auto";
|
||||
};
|
||||
|
||||
// ── 세월교 = 사용자 구조물 표 04 명세(PLAN 52-4) — 세월교 · 유입 구조 · 유출 구조 ──
|
||||
// 월류 높이 = 월류 폭과 연동(폭을 바꾸면 필요 수심으로 채움 · 그 아래는 안 받음 · `design_structure_ford_calc`)
|
||||
let fordFlow: number | null = null;
|
||||
const fordNeed = (): number | null =>
|
||||
fordHeightM(fordFlow, Number(fordForm.state().ford?.[0]?.width ?? ""));
|
||||
const fordForm = createSpecForm(specLinks, {
|
||||
hint: (c, item, state) =>
|
||||
c.id === "ford" && item.id === "height"
|
||||
? fordHeightHint(fordFlow, Number(state.ford?.[0]?.width ?? ""))
|
||||
: "",
|
||||
onChange: (c, item) => {
|
||||
const need = c.id === "ford" ? fordNeed() : null;
|
||||
const height = fordForm.state().ford?.[0]?.height ?? "";
|
||||
const low = height !== "" && !(Number(height) >= (need ?? 0));
|
||||
if (need !== null && (item.id === "width" || (item.id === "height" && low)))
|
||||
fordForm.setValue("ford", "height", need.toFixed(2));
|
||||
emit();
|
||||
},
|
||||
});
|
||||
|
||||
// ── 추가 기슭막이(다단) — 횡단도에서 다단 벽을 고를 때만 뜬다(2026-08-30 사용자
|
||||
// 지시 4). 유입구·유출구와 같은 양식이되, 값은 조정창 사본의 행이 그대로 옮겨 온다
|
||||
// (다단 벽은 폼에 자기 칸이 없다 — 높이·형태·구간값이 전부 조정 채널에 있다).
|
||||
@@ -370,6 +378,8 @@ export function createFacilityOptionsForm(
|
||||
);
|
||||
const wingInFields = createWingFields("날개벽(유입)", WING_IN_KEYS);
|
||||
const wingOutFields = createWingFields("날개벽(유출)", WING_OUT_KEYS);
|
||||
const wingInHome = wingInFields.slot.parentElement!;
|
||||
const wingOutHome = wingOutFields.slot.parentElement!;
|
||||
|
||||
const hiddenOptions = createHiddenOptions(root);
|
||||
|
||||
@@ -399,8 +409,8 @@ export function createFacilityOptionsForm(
|
||||
// 월류 폭·높이 → 바닥 경사 → 수량 → 개략 단면 결과. 다른 시설에서는 전부 숨는다.
|
||||
root.append(
|
||||
saveNote,
|
||||
pipeRow,
|
||||
pipeForm.root,
|
||||
fordForm.root,
|
||||
ford.widthRow,
|
||||
ford.slopeRow,
|
||||
ford.countRow,
|
||||
@@ -421,15 +431,18 @@ export function createFacilityOptionsForm(
|
||||
if (current === null) return;
|
||||
const isPipe = current === "pipe";
|
||||
const isBox = current === "box_culvert";
|
||||
pipeRow.hidden = current !== "ford_bridge";
|
||||
const isBridge = current === "ford_bridge";
|
||||
pipeForm.root.hidden = !isPipe;
|
||||
fordForm.root.hidden = !isBridge;
|
||||
boxWrap.hidden = !isBox;
|
||||
// 날개벽은 BOX암거와 세월교가 같은 옵션 한 벌을 쓴다(2026-08-25 사용자 확정).
|
||||
const hasWing = isBox || current === "ford_bridge";
|
||||
// 날개벽 칸 = BOX암거 · 세월교는 유입 · 유출 구조 컨테이너(52-4) — B06 조정 행 자리만 그 컨테이너 끝으로
|
||||
const hasWing = isBox;
|
||||
wingInFields.root.hidden = !hasWing;
|
||||
wingOutFields.root.hidden = !hasWing;
|
||||
const isFord = current === "ford_pavement" || current === "ford_bridge";
|
||||
ford.countRow.hidden = current !== "ford_bridge";
|
||||
(isBridge ? fordForm.slot("inlet") : wingInHome).append(wingInFields.slot);
|
||||
(isBridge ? fordForm.slot("outlet") : wingOutHome).append(wingOutFields.slot);
|
||||
const isFord = current === "ford_pavement";
|
||||
ford.countRow.hidden = true;
|
||||
ford.widthRow.hidden = !isFord;
|
||||
// 바닥 경사는 물넘이포장만 쓴다 — 세월교는 구체 위 노면이라 파임이 없다.
|
||||
ford.slopeRow.hidden = current !== "ford_pavement";
|
||||
@@ -462,8 +475,6 @@ export function createFacilityOptionsForm(
|
||||
revetOutlet.fields.onChange(emit);
|
||||
revetSide.addEventListener("change", emit);
|
||||
[
|
||||
pipeMaterial,
|
||||
pipeDiameter,
|
||||
boxSize,
|
||||
boxWidth,
|
||||
boxHeight,
|
||||
@@ -500,8 +511,12 @@ export function createFacilityOptionsForm(
|
||||
bindLinks(links) {
|
||||
linkSource = links;
|
||||
pipeForm.paintLinks();
|
||||
fordForm.paintLinks();
|
||||
},
|
||||
paintLinks: () => {
|
||||
pipeForm.paintLinks();
|
||||
fordForm.paintLinks();
|
||||
},
|
||||
paintLinks: () => pipeForm.paintLinks(),
|
||||
setChainage(chainageM) {
|
||||
chainage = chainageM;
|
||||
if (current === "pipe") pipeForm.refresh();
|
||||
@@ -516,6 +531,7 @@ export function createFacilityOptionsForm(
|
||||
current = facility;
|
||||
hiddenOptions.remember(options);
|
||||
ford.setDesignFlow(designFlow ?? null);
|
||||
fordFlow = designFlow ?? null;
|
||||
if (facility === null) {
|
||||
syncVisibility();
|
||||
return;
|
||||
@@ -523,12 +539,11 @@ export function createFacilityOptionsForm(
|
||||
const text = (key: string): string =>
|
||||
options[key] !== undefined ? String(options[key]) : "";
|
||||
// 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
||||
pipeMaterial.value = text("pipe_kind");
|
||||
pipeDiameter.value = text("pipe_diameter_mm");
|
||||
if (facility === "pipe") {
|
||||
const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name]));
|
||||
const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name]));
|
||||
if (facility === "pipe")
|
||||
pipeForm.write(PIPE_SPEC, options, definition?.spec_names ?? {}, groups);
|
||||
}
|
||||
if (facility === "ford_bridge")
|
||||
fordForm.write(FORD_SPEC, withLegacyGuards(options), definition?.spec_names ?? {}, groups);
|
||||
// 저장된 폭·높이가 프리셋과 맞으면 그 프리셋을, 아니면 "사용자 지정"을 고른다.
|
||||
// 값이 없으면 「안 정함」.
|
||||
const savedWidth = Number.parseFloat(text("body_width_m"));
|
||||
@@ -549,7 +564,7 @@ export function createFacilityOptionsForm(
|
||||
boxHeight.value = hasSaved ? String(savedHeight) : "";
|
||||
wingInFields.write(options);
|
||||
wingOutFields.write(options);
|
||||
if (facility === "ford_pavement" || facility === "ford_bridge") ford.write(facility, options);
|
||||
if (facility === "ford_pavement") ford.write(facility, options);
|
||||
revetSide.value = text("side");
|
||||
const spread = legacyRevetOptions(options);
|
||||
revetInlet.fields.write(spread);
|
||||
@@ -558,15 +573,13 @@ export function createFacilityOptionsForm(
|
||||
},
|
||||
setDesignFlow(designFlow) {
|
||||
ford.setDesignFlow(designFlow ?? null);
|
||||
fordFlow = designFlow ?? null;
|
||||
if (current === "ford_bridge") fordForm.refresh(); // 높이 안내
|
||||
syncVisibility();
|
||||
},
|
||||
readOptions() {
|
||||
const options: Record<string, string | number> = {};
|
||||
// 고른 칸만 싣는다 — 「안 정함」은 빈 값(기본값을 몰래 확정으로 안 바꿈).
|
||||
const putPipe = (): void => {
|
||||
if (pipeDiameter.value) options.pipe_diameter_mm = Number(pipeDiameter.value);
|
||||
if (pipeMaterial.value) options.pipe_kind = pipeMaterial.value;
|
||||
};
|
||||
if (current === "pipe") {
|
||||
Object.assign(options, pipeForm.read()); // 명세 컨테이너 값 = 옛 키 그대로(PLAN 52-1)
|
||||
} else if (current === "box_culvert") {
|
||||
@@ -578,10 +591,7 @@ export function createFacilityOptionsForm(
|
||||
} else if (current === "ford_pavement") {
|
||||
ford.read(current, options);
|
||||
} else if (current === "ford_bridge") {
|
||||
putPipe();
|
||||
ford.read(current, options);
|
||||
wingInFields.read(options);
|
||||
wingOutFields.read(options);
|
||||
Object.assign(options, fordForm.read()); // 표 04 명세 값 · 옛 관종 · 날개벽 치수는 병합이 이어 붙임(52-4)
|
||||
} else if (current === "revetment") {
|
||||
if (revetSide.value) options.side = revetSide.value;
|
||||
// 단 수는 폼이 갖지 않는다 — 횡단 설계 patch(extra_wall_counts)가 정본이고
|
||||
|
||||
@@ -42,14 +42,16 @@ export const FORM_OPTION_KEYS: Readonly<Record<string, readonly string[]>> = {
|
||||
],
|
||||
box_culvert: ["body_width_m", "body_height_m", ...wingKeys("in"), ...wingKeys("out")],
|
||||
ford_pavement: ["ford_width_m", "ford_height_m", "ford_slope_pct", "thickness_cm", "length_m"],
|
||||
// 세월교 = 표 04 명세 키(PLAN 52-4 · `design_structure_ford`) — 옛 관종 · 날개벽 치수는 폼에서 빠져 이어 붙임
|
||||
ford_bridge: [
|
||||
"pipe_kind",
|
||||
"pipe_diameter_mm",
|
||||
"pipe_count",
|
||||
"ford_width_m",
|
||||
"ford_height_m",
|
||||
...wingKeys("in"),
|
||||
...wingKeys("out"),
|
||||
"inlet_type",
|
||||
"outlet_type",
|
||||
"wing_in",
|
||||
"wing_out",
|
||||
],
|
||||
revetment: ["side", ...revetKeys("inlet"), ...revetKeys("outlet")],
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "./B05_Profile_UI_IrregularStations";
|
||||
import { structureAnchorM, type StructureInstance } from "./B05_Profile_Api_Structures";
|
||||
import { GROUP_LABELS } from "./B05_Profile_UI_Structures_Panel";
|
||||
import { HIDDEN_PICK_GROUPS } from "./B05_Profile_UI_Structures_Panel_Types";
|
||||
|
||||
/** 우클릭 메뉴용 최소 타입 정보 — 사이드 「구조물 배치」 종류 목록과 같은 원천. */
|
||||
export interface StructureMenuType {
|
||||
@@ -32,7 +33,9 @@ export function structureGroupMenuItems(
|
||||
types: ReadonlyArray<StructureMenuType>,
|
||||
onPick: (typeId: string) => void,
|
||||
): MapContextMenuItem[] {
|
||||
const groups = [...new Set(types.map((type) => type.group))];
|
||||
const groups = [...new Set(types.map((type) => type.group))].filter(
|
||||
(group) => !HIDDEN_PICK_GROUPS.has(group),
|
||||
);
|
||||
return groups.map((group) => ({
|
||||
label: GROUP_LABELS[group] ?? group,
|
||||
children: types
|
||||
|
||||
@@ -78,7 +78,9 @@ export function createFacilityDefinition(root: HTMLElement): FacilityDefinition
|
||||
root
|
||||
.querySelectorAll(".is-rule-hidden")
|
||||
.forEach((node) => node.classList.remove("is-rule-hidden"));
|
||||
root.querySelectorAll<HTMLOptionElement>("option[hidden]").forEach((o) => (o.hidden = false));
|
||||
root
|
||||
.querySelectorAll<HTMLOptionElement>("option[hidden]:not([data-pick-hidden])")
|
||||
.forEach((o) => (o.hidden = false));
|
||||
paint();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { PipeFacility } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import { type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility";
|
||||
import { field, optionDefaults } from "./B05_Profile_UI_Structures_Fields";
|
||||
import {
|
||||
hasSpecContainers,
|
||||
hidesTypeSummary,
|
||||
paintStale,
|
||||
renderOptionFields as renderOptionFieldsInto,
|
||||
} from "./B05_Profile_UI_Structures_Panel_Options";
|
||||
@@ -54,6 +54,7 @@ export type { PipeFacilityItem, StructuresSection } from "./B05_Profile_UI_Struc
|
||||
import {
|
||||
DEFAULT_INTERVAL_LENGTH_M,
|
||||
GROUP_LABELS,
|
||||
groupOption,
|
||||
hasComputedRange,
|
||||
readBeforeOption,
|
||||
readLengthOption,
|
||||
@@ -227,7 +228,7 @@ export function createStructuresSection(
|
||||
!!type && !managed && type.placement === "interval" && !hasComputedRange(type);
|
||||
positionRow.hidden = !type;
|
||||
positionDivider.hidden = !type;
|
||||
summary.root.hidden = !type || hasSpecContainers(type); // 컨테이너 종류는 컨테이너마다 잇기(52-1)
|
||||
summary.root.hidden = !type || hidesTypeSummary(type); // 컨테이너 종류는 컨테이너마다 잇기(52-1)
|
||||
startFields.wrap.hidden = !isInterval;
|
||||
endFields.wrap.hidden = !isInterval;
|
||||
primary.disabled = !type;
|
||||
@@ -544,7 +545,7 @@ export function createStructuresSection(
|
||||
// 첫 항목은 빈칸 — 진입·리셋 상태에서 군·종류를 강제로 고르게 두지 않는다. 이름은 정의(48) 먼저.
|
||||
groupSelect.replaceChildren(
|
||||
new Option("— 선택 —", ""),
|
||||
...[...groups].map(([g, name]) => new Option(name ?? GROUP_LABELS[g] ?? g, g)),
|
||||
...[...groups].map(([g, name]) => groupOption(g, name ?? GROUP_LABELS[g] ?? g)),
|
||||
);
|
||||
groupSelect.value = "";
|
||||
syncTypeOptions();
|
||||
|
||||
@@ -23,9 +23,10 @@ import { FORM_OPTION_KEYS } from "./B05_Profile_UI_Drainage_Facility_Merge";
|
||||
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
|
||||
import { solveSplit, specKeys, type SplitRole } from "../A00_Common/design/design_structure_spec";
|
||||
|
||||
/** 컨테이너로 그리는 종류인지(명세 · 정의 그룹 상자) — 그때 종류 단위 「구조물 조합」 줄은 가림(PLAN 52-1 · 52-7). */
|
||||
export const hasSpecContainers = (type: StructureType): boolean =>
|
||||
!!STRUCTURE_SPECS[type.type_id]?.containers.length || !!type.groups?.length;
|
||||
/** 종류 단위 「구조물 조합」 줄을 가릴 종류인지 — 구조물 명세 컨테이너 · 정의 그룹 상자(그룹마다 잇기 줄)로
|
||||
* 그리는 종류(PLAN 52-1 · 52-6b · 52-7) · 종단배수(B군 · 조합 잇기 없음 · 표 07). */
|
||||
export const hidesTypeSummary = (type: StructureType): boolean =>
|
||||
!!STRUCTURE_SPECS[type.type_id]?.containers.length || !!type.groups?.length || type.group === "B";
|
||||
|
||||
/** 길이 = 전 + 후 를 배관 맞물림(`solveSplit` · 마지막에 고친 둘을 지킴)으로 푸는 종류(표 07 가드레일). */
|
||||
const SOLVE_SPLIT = new Set(["guardrail"]);
|
||||
|
||||
@@ -34,6 +34,18 @@ export const GROUP_LABELS: Record<string, string> = {
|
||||
호환: "기타",
|
||||
};
|
||||
|
||||
/** 새로 놓을 때 고르기 목록(군 드롭다운 · 우클릭 메뉴)에서 뺄 군 — 종단배수·계류사방(표 07).
|
||||
* 옛 값 · 도면 · 집계는 그대로이고 데이터도 지우지 않는다. */
|
||||
export const HIDDEN_PICK_GROUPS: ReadonlySet<string> = new Set(["B", "D"]);
|
||||
|
||||
/** 군 드롭다운 항목 — 뺄 군은 가림(옛 항목을 열 때는 값으로 그대로 고를 수 있음). */
|
||||
export function groupOption(group: string, name: string): HTMLOptionElement {
|
||||
const option = new Option(name, group);
|
||||
option.hidden = HIDDEN_PICK_GROUPS.has(group);
|
||||
if (option.hidden) option.dataset.pickHidden = ""; // 정의 폼의 「가림 풀기」가 건드리지 않게
|
||||
return option;
|
||||
}
|
||||
|
||||
/** 구간형 신규 추가 시 기본 구간 길이(m). 사용자가 종점을 바로 고칠 수 있게 짧게 잡는다. */
|
||||
export const DEFAULT_INTERVAL_LENGTH_M = 15;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user