import React, { useEffect, useState } from "react"; import { Card, CardContent, Typography, Button, List, ListItemButton, ListItemText, Divider, Stack, FormControl, InputLabel, Select, MenuItem, Menu, TextField, Dialog, DialogTitle, DialogContent, ToggleButton, ToggleButtonGroup, DialogActions, Tabs, Tab, Box } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import AddIcon from "@mui/icons-material/Add"; import { getDefaultBucket } from "../utils/awsConfig"; const DEFAULT_CUSTOM_MODEL = { bucket: getDefaultBucket(), prefix: "data/3dtiles", tileset: "custom/tileset.json", offsetHeight: 0, }; const DEFAULT_URL_MODEL = { tilesetUrl: "", offsetHeight: 0, }; function TabPanel(props) { const { children, value, index, ...other } = props; return ( ); } function a11yProps(index) { return { id: `simple-tab-${index}`, "aria-controls": `simple-tabpanel-${index}`, }; } function ExportMenu({ onExport, disabled }) { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const handleSelect = (format) => { onExport(format); handleClose(); }; return ( <> handleSelect('geojson')}>As GeoJSON (.json) handleSelect('csv')}>As CSV (.csv) handleSelect('kml')}>As KML (.kml) ); } export default function MeasurePanel({ points, onSelectPoint, onDeletePoint, onToggleMeasure, measuring, offsetHeight, models, selectedModelId, customModel, urlModel, onModelChange, onSaveCustomModel, onSaveUrlModel, onOffsetHeightChange, onEditModel, pointAppearance, onPointAppearanceChange, // New geometry props polylines, polygons, drawingState, selectedGeometry, onStartDrawing, onFinishDrawing, onCancelDrawing, onSelectGeometry, onDeleteGeometry, onExportGeometries, }) { const [selectedIndex, setSelectedIndex] = useState(null); const [customDialogOpen, setCustomDialogOpen] = useState(false); const [draftCustomModel, setDraftCustomModel] = useState( customModel || DEFAULT_CUSTOM_MODEL ); const [urlDialogOpen, setUrlDialogOpen] = useState(false); const [draftUrlModel, setDraftUrlModel] = useState( urlModel || DEFAULT_URL_MODEL ); const [tabIndex, setTabIndex] = useState(0); const handleTabChange = (event, newValue) => { setTabIndex(newValue); }; useEffect(() => { if (!customDialogOpen) { setDraftCustomModel(customModel || DEFAULT_CUSTOM_MODEL); } }, [customDialogOpen, customModel]); useEffect(() => { if (!urlDialogOpen) { setDraftUrlModel(urlModel || DEFAULT_URL_MODEL); } }, [urlDialogOpen, urlModel]); const handleSelect = (index) => { // Allow deselecting const nextIndex = selectedIndex === index ? null : index; setSelectedIndex(nextIndex); if (onSelectPoint) onSelectPoint(points[index]); }; const openCustomDialog = () => { setDraftCustomModel(customModel || DEFAULT_CUSTOM_MODEL); setCustomDialogOpen(true); }; const openUrlDialog = () => { setDraftUrlModel(urlModel || DEFAULT_URL_MODEL); setUrlDialogOpen(true); }; const handleModelChange = (event) => { const nextModelId = event.target.value; if (onModelChange) onModelChange(nextModelId); if (nextModelId === "custom") { openCustomDialog(); } if (nextModelId === "url") { openUrlDialog(); } }; const handleCustomFieldChange = (field) => (event) => { setDraftCustomModel((prev) => ({ ...prev, [field]: event.target.value, })); }; const handleCustomSave = () => { const nextOffset = Number(draftCustomModel.offsetHeight); const payload = { ...draftCustomModel, offsetHeight: Number.isNaN(nextOffset) ? 0 : nextOffset, }; if (onSaveCustomModel) onSaveCustomModel(payload); setCustomDialogOpen(false); }; const handleUrlFieldChange = (field) => (event) => { setDraftUrlModel((prev) => ({ ...prev, [field]: event.target.value, })); }; const handleUrlSave = () => { const nextOffset = Number(draftUrlModel.offsetHeight); const payload = { ...draftUrlModel, offsetHeight: Number.isNaN(nextOffset) ? 0 : nextOffset, }; if (onSaveUrlModel) onSaveUrlModel(payload); setUrlDialogOpen(false); }; const handleCustomClose = () => { setCustomDialogOpen(false); }; const handleUrlClose = () => { setUrlDialogOpen(false); }; return ( 3D Measurement Model {(selectedModelId === "custom" || selectedModelId === "url" || selectedModelId === "upload") && ( )} onOffsetHeightChange && onOffsetHeightChange(event.target.value) } inputProps={{ step: 1 }} /> {points.length === 0 && ( No points measured yet. )} {points.map((p, index) => ( handleSelect(index)} sx={{ borderRadius: 2, "&.Mui-selected": { bgcolor: "primary.light", color: "white", "&:hover": { bgcolor: "primary.main" }, }, }} > {index < points.length - 1 && } ))} { // Prevent unselecting all buttons if (newAppearance !== null) { onPointAppearanceChange(newAppearance); } }} aria-label="point appearance" > Error Ellipsoid Simple Point onExportGeometries('point', format)} disabled={points.length === 0} /> {polylines.length === 0 && ( No polylines created yet. )} {polylines.map((line) => ( onSelectGeometry('polyline', line.id)} sx={{ borderRadius: 2, "&.Mui-selected": { bgcolor: "primary.light", color: "white", "&:hover": { bgcolor: "primary.dark" }, }, }} > ))} onExportGeometries('polyline', format)} disabled={polylines.length === 0} /> {polygons.length === 0 && ( No polygons created yet. )} {polygons.map((poly) => ( onSelectGeometry('polygon', poly.id)} sx={{ borderRadius: 2, "&.Mui-selected": { bgcolor: "primary.light", color: "white", "&:hover": { bgcolor: "primary.dark" }, }, }} > ))} onExportGeometries('polygon', format)} disabled={polygons.length === 0} /> Custom S3 Model URL Model ); }