Files
Aislo/B06_Section/B06_Section_UI_Section_View_Menu.ts
T
eomsangdonandClaude Opus 5 11161659f5 feat(b06): 종단 그래프 우클릭으로 구조물·계곡 통과 시설을 넣고 빼게 함
B05 와 B06 은 한 페이지인데 구조물을 넣고 빼는 자리가 B05 뿐이라 화면을 오가야 했음.
종단 그래프 렌더러·알약 레인·좌측 배치 폼은 이미 공용이었으므로, 빠져 있던 조작층만
B05 와 같은 부품(mountStructureMenu)으로 B06 에도 붙임.

관 추가·삭제는 이동과 같은 예약 경로를 씀 — 세션에 쌓고 [저장]·[확정]에서 정본
(pipe_points.json)으로 나가며, 세부 배수유역 재분할은 그때 서버가 처리함. 넣었다 바로
빼거나 뺐다 다시 넣으면 예약만 걷어 내 정본이 흔들리지 않음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fe1QWPTfw11PaKh2LjwXdR
2026-09-12 15:50:02 +09:00

84 lines
3.7 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";
/** 종단 그래프에서 구조물을 넣고 빼는 길 — 페이지가 물려 준다. 없으면 메뉴를 안 붙인다. */
export interface SectionStructureEdit {
/** 관(계곡 통과 시설)을 그 자리에 넣는다. */
addPipe: (chainageM: number) => void;
/** 관을 뺀다. */
removePipe: (chainageM: number) => void;
/** 레지스트리 종류를 골라 구조물을 넣는다(좌측 「구조물 배치」와 같은 2단 메뉴). */
addStructureType: (chainageM: number, typeId: string) => void;
/** 구조물(관 아님)을 뺀다. */
removeStructure: (structureId: string) => void;
}
/** 알약·우클릭이 쓰는 「관인가」 판정 — 관은 정본이 달라 지우는 길도 다르다. */
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),
structureTypes: input.types.map((type) => ({
type_id: type.type_id,
group: type.group,
name: type.name,
})),
onAddStructureType: (chainageM, typeId) => input.edit.addStructureType(chainageM, typeId),
});
}