- SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음) - README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리 - 패치내역을 docs/로 이동, _worklog.md 추가 - 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외
112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
import React from 'react';
|
|
import { ContextMenu } from './ContextMenu';
|
|
import { type MapContextMenuEvent, type EditMode, type SimulationResult, type SelectionTarget } from './types';
|
|
import { type NetworkModel, type InpNode } from './inpModel';
|
|
import { type CRS } from './crs';
|
|
import { type MapFocusRequest } from './Map';
|
|
|
|
export interface MapContextMenuProps {
|
|
contextMenu: MapContextMenuEvent;
|
|
setContextMenu: React.Dispatch<React.SetStateAction<MapContextMenuEvent | null>>;
|
|
appMode: 'explore' | 'edit' | 'result';
|
|
setAppMode: React.Dispatch<React.SetStateAction<'explore' | 'edit' | 'result'>>;
|
|
setEditMode: React.Dispatch<React.SetStateAction<EditMode>>;
|
|
networkModel: NetworkModel | null;
|
|
wasmResult: SimulationResult | null;
|
|
crs: CRS;
|
|
setSelectedTarget: React.Dispatch<React.SetStateAction<SelectionTarget>>;
|
|
setActiveTab: React.Dispatch<React.SetStateAction<import('./types').RightTab | null>>;
|
|
requestMapFocus: (request: Omit<MapFocusRequest, 'id'>) => void;
|
|
showToast: (msg: string) => void;
|
|
handleAddNodeAtCoords: (x: number, y: number, nodeType?: InpNode['type']) => void;
|
|
handleCopyText: (text: string, label: string) => void;
|
|
handleZoomToLayer: (layerId: string) => void;
|
|
handleDeleteSelected: () => void;
|
|
handleRecalculateLength: (pipeId: string) => void;
|
|
handleReversePipe: (pipeId: string) => void;
|
|
handleSplitPipe: (pipeId: string) => void;
|
|
}
|
|
|
|
/** Right-click menu on the map (node/link/empty-space actions). */
|
|
export const MapContextMenu: React.FC<MapContextMenuProps> = ({
|
|
contextMenu,
|
|
setContextMenu,
|
|
appMode,
|
|
setAppMode,
|
|
setEditMode,
|
|
networkModel,
|
|
wasmResult,
|
|
crs,
|
|
setSelectedTarget,
|
|
setActiveTab,
|
|
requestMapFocus,
|
|
showToast,
|
|
handleAddNodeAtCoords,
|
|
handleCopyText,
|
|
handleZoomToLayer,
|
|
handleDeleteSelected,
|
|
handleRecalculateLength,
|
|
handleReversePipe,
|
|
handleSplitPipe,
|
|
}) => (
|
|
<ContextMenu
|
|
crs={crs}
|
|
event={contextMenu}
|
|
onClose={() => setContextMenu(null)}
|
|
onCopyText={handleCopyText}
|
|
onAddNodeAtCoords={(x, y) => {
|
|
handleAddNodeAtCoords(x, y, 'JUNCTION');
|
|
setContextMenu(null);
|
|
}}
|
|
onFitBounds={() => {
|
|
showToast('전체 화면을 표시합니다.');
|
|
}}
|
|
onPanTo={(lngLat) => {
|
|
requestMapFocus({ lngLat, zoom: 17 });
|
|
showToast(`좌표 (${lngLat[0].toFixed(4)}, ${lngLat[1].toFixed(4)})로 이동했습니다.`);
|
|
}}
|
|
onInspectTarget={(target) => {
|
|
if (target.type === 'node' || target.type === 'link') {
|
|
setSelectedTarget(target);
|
|
}
|
|
setActiveTab('inspector');
|
|
}}
|
|
onViewResults={(target) => {
|
|
setSelectedTarget(target);
|
|
setActiveTab('results');
|
|
}}
|
|
onStartPipeFromNode={(nodeId) => {
|
|
if (appMode !== 'edit') setAppMode('edit');
|
|
setEditMode('add-pipe');
|
|
setSelectedTarget({ type: 'node', id: nodeId });
|
|
}}
|
|
onSplitPipe={handleSplitPipe}
|
|
onSelectConnectedLinks={(nodeId) => {
|
|
if (!networkModel) return;
|
|
const connectedPipes = Object.values(networkModel.pipes).filter(
|
|
(p) => p.node1 === nodeId || p.node2 === nodeId
|
|
);
|
|
if (connectedPipes.length > 0) {
|
|
setSelectedTarget({ type: 'link', id: connectedPipes[0].id });
|
|
setActiveTab('inspector');
|
|
showToast(`연결 관로 [${connectedPipes[0].id}] 선택됨 (총 ${connectedPipes.length}개 관로)`);
|
|
}
|
|
}}
|
|
onSelectConnectedNodes={(pipeId) => {
|
|
if (!networkModel) return;
|
|
const pipe = networkModel.pipes[pipeId];
|
|
if (pipe) {
|
|
setSelectedTarget({ type: 'node', id: pipe.node1 });
|
|
setActiveTab('inspector');
|
|
showToast(`시작 절점 [${pipe.node1}] 선택됨 (끝 절점: ${pipe.node2})`);
|
|
}
|
|
}}
|
|
onCalculatePipeLength={handleRecalculateLength}
|
|
onReversePipe={handleReversePipe}
|
|
onDeleteNode={handleDeleteSelected}
|
|
onDeletePipe={handleDeleteSelected}
|
|
onZoomToLayer={handleZoomToLayer}
|
|
canRunResults={!!wasmResult}
|
|
/>
|
|
);
|