Files
Aislo/B06_Section/B06_Section_UI_Section_View_Menu.ts
T
eomsangdonandClaude Opus 5.5 bbd6254892 feat(구조물표): 322-1 부품 상자 · 표 읽기 — 목록 · 측점 · 카드 이름 · 형상 고르기 · 3D 몸체 · 알약 띠가 종류 키 · 군 글자 · 자리꼴 · managed_by 대신 구조물 표(catalog) 한 줄을 읽음 · 코드 숨김 목록 걷음 · ★ 넷 + 관 공식 한 벌
- 부품 상자 common_util_structure_parts.json(갈래마다 부품 · 읽는 칸 · 자리꼴 · 정본 · 42종 처음값) · 표 읽기 common_util_structure_catalog.ts / .py 짝(정의 반영 ⊕ 처음값 ⊕ 부품 끔) · 거울 시험 test_322_1_catalog_mirror
- 목록: pickGroups 군 차례 = 구조물군 열쇠 · 페이지 거르기 listPick(B05 both · B06 both + b06) · HIDDEN_PICK_GROUPS · HIDDEN_PICK_TYPES · pickable 걷음(옛 대피 셋 asRoad 그대로)
- 측점: structureStationMarks · pipeStationMarks · is_station_planting_type · resolve_extra_stations = 표 횡단도(anchor · span3 · none)
- 카드 이름: cardTitle / card_title 한 공식(빈칸 = 그 종류 정의 기본값) · 표시 이름이 정의 관종도 앎
- 형상: 벽 = shape wall(형태 = 형상칸.form · 물빼기 = 형상칸.weep) · 포장 = shape pavement(「사용 중」 거르기 없앰) · 맹암거 · 사토 · 관 지점 시설 세트 = shape
- 3D 몸체 = 그림 자료 주인 종류의 표 3D 부품 · 알약 띠 = 표 자리꼴 구간(관 지점 시설은 알약만)
- ★1 노출형 · 개거 횡단도 none · ★2 쇄석 화면에서도 포장층 · ★3 기슭막이 표 자리꼴 interval(정의 파일 그대로) · ★4 코드 숨김 10종 list hidden · ★5 관 빈칸 D800 → 정의 기본 D1000
- 옛 시험: 도움 파일 로더 27개가 표 모듈 공용 한 벌(helper_322_catalog_module) 씀 · 기대값 고침(272_1 · b05_extra_stations · roundtrip · 314 · 52_7 · walls 거울 · wall_drain_pipe)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 15:09:45 +09:00

136 lines
6.4 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Section_View_Menu.ts
* B06 종단 그래프 위 **우클릭 메뉴** — B05 와 같은 부품(`mountStructureMenu`)을 그대로 쓴다.
*
* 왜 여기서도 여나(2026-09-12 사용자) — B05·B06 은 한 페이지다. 같은 그래프를 보면서
* 한쪽에서만 넣고 뺄 수 있으면 사용자가 화면을 오가야 하고 직관도 깨진다.
*
* 정본에 바로 쓰지 않는다 — 추가·삭제·이동은 **세션에 예약**하고 [저장]·[확정]에서
* 관 정본(`pipe_points.json`)으로 나간다(CLAUDE.md 5장). 세부 배수유역 재분할은 그때
* 서버가 관 목록을 다시 받아 처리한다.
* ========================================================================== */
import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures";
import { structureAnchorM } from "../B05_Profile/B05_Profile_Api_Structures";
import type { IrregularStation } from "../B05_Profile/B05_Profile_UI_IrregularStations";
import { mountStructureMenu } from "../B05_Profile/B05_Profile_UI_Profile_Structures";
import { pickGroups } from "../B05_Profile/B05_Profile_UI_Structures_Panel_Types";
import { listPick } from "../common_util/common_util_structure_catalog";
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
/** 종단 그래프에서 구조물을 넣고 빼는 길 — 페이지가 물려 준다. 없으면 메뉴를 안 붙인다. */
export interface SectionStructureEdit {
/** 관(계곡 통과 시설)을 그 자리에 넣는다. */
addPipe: (chainageM: number) => void;
/** 관을 뺀다. */
removePipe: (chainageM: number) => void;
/** 레지스트리 종류를 골라 구조물을 넣는다(좌측 「구조물 배치」와 같은 2단 메뉴). */
addStructureType: (chainageM: number, typeId: string) => void;
/** 구조물(관 아님)을 뺀다. */
removeStructure: (structureId: string) => void;
/** 관을 다른 측점으로 옮긴다(측점선·알약 끌기). */
movePipe: (fromChainageM: number, toChainageM: number) => void;
/** 구조물(관 아님)을 다른 측점으로 옮긴다. */
moveStructure: (structureId: string, toChainageM: number) => void;
/** 구조물(관 아님) 하나를 폼에 올리고 그 카드로 간다 — 같은 측점 관보다 먼저(52-15). */
selectStructure: (structureId: string) => void;
/** 측점 없는 관(물넘이포장) 하나를 폼에 올림 — 카드 안 고름 · 그 자리로 스크롤만(요구 178). */
selectPipe?: (chainageM: number) => void;
/** 배수 재계산이 관 종류 · 관경을 바꿈 — 카드(미리 뜬 새 관 그림 포함)를 다시 얹음(273-2). */
pipesChanged?: () => void;
}
/**
* 측점선·알약을 끌어 놓았다 — 그래프 위 id 로 구조물을 찾아 같은 예약 경로로 보낸다.
* 종단 측점선의 id 는 **종단 정본 측점 id** 라 알약(구조물 id)과 다를 수 있다. 둘 다 받아
* 같은 자리(누가거리)로 맞춘다.
*/
export function moveMarkById(
markId: string,
toChainageM: number,
input: {
structures: ReadonlyArray<StructureInstance>;
stations: ReadonlyArray<{ station_id: string; chainage_m: number }>;
edit: SectionStructureEdit;
},
): void {
const direct = input.structures.find((entry) => String(entry.structure_id) === markId);
if (direct) {
moveStructureMark(input.structures, input.edit, markId, toChainageM);
return;
}
const station = input.stations.find((entry) => String(entry.station_id) === markId);
if (!station) return;
// 측점선 id 로 온 경우 — 그 측점 자리에 선 구조물을 찾는다(관 매칭과 같은 0.51m).
const near = input.structures.find(
(entry) => Math.abs(structureAnchorM(entry) - station.chainage_m) < PIPE_MATCH_M,
);
if (near) moveStructureMark(input.structures, input.edit, String(near.structure_id), toChainageM);
}
/** 측점선·알약을 끌었을 때 — 관인지 구조물인지 갈라 같은 예약 경로로 보낸다. */
function moveStructureMark(
structures: ReadonlyArray<StructureInstance>,
edit: SectionStructureEdit,
structureId: string,
toChainageM: number,
): void {
const hit = structures.find((entry) => String(entry.structure_id) === structureId);
if (!hit) return;
const from = structureAnchorM(hit);
if (Math.abs(from - toChainageM) < 0.005) return;
if (isPipeMark(hit)) edit.movePipe(from, toChainageM);
else edit.moveStructure(structureId, toChainageM);
}
/** 알약·우클릭이 쓰는 「관인가」 판정 — 관은 정본이 달라 지우는 길도 다르다. */
export function isPipeMark(structure: StructureInstance): boolean {
return String(structure.structure_id ?? "").startsWith("pipe-");
}
/**
* 종단 그래프 칸에 우클릭 메뉴를 붙인다. 그래프는 그릴 때마다 새로 만들어지므로 이
* 함수도 그때마다 다시 부른다 — 상태를 밖에 남기지 않는다(B05 와 같은 규칙).
*/
export function mountSectionStructureMenu(
host: HTMLElement,
input: {
structures: ReadonlyArray<StructureInstance>;
types: ReadonlyArray<StructureType>;
x: (chainageM: number) => number;
chainageAt: (px: number) => number;
maxChainageM: number;
edit: SectionStructureEdit;
},
): void {
// 메뉴가 쓰는 최소 정보만 옮겨 담는다 — B06 에는 B05 의 비정규 측점 목록이 없다.
const stations: IrregularStation[] = input.structures.map((structure) => {
const chainage = structureAnchorM(structure);
const pipe = isPipeMark(structure);
return {
id: String(structure.structure_id),
station: 0,
remainder: 0,
chainage_m: chainage,
structure:
input.types.find((type) => type.type_id === structure.type_id)?.name ??
String(structure.type_id),
origin: pipe ? "pipe" : "user",
} as IrregularStation;
});
mountStructureMenu(host, {
stations,
x: input.x,
chainageAt: input.chainageAt,
maxChainageM: input.maxChainageM,
onRemove: (station) => {
if (station.origin === "pipe") input.edit.removePipe(station.chainage_m);
else input.edit.removeStructure(station.id);
},
onAddPipe: (chainageM) => input.edit.addPipe(chainageM),
structureGroups: () => pickGroups(input.types, listPick("b06")), // B06 패널 거르기와 같음(129-1 · 322-1)
onAddStructureType: (chainageM, typeId) => input.edit.addStructureType(chainageM, typeId),
});
}