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 (
{value === index && {children}}
);
}
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 (
<>
}
onClick={handleClick}
disabled={disabled}
fullWidth
>
Export
>
);
}
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") && (
}
onClick={() => {
if (selectedModelId === "custom") openCustomDialog();
if (selectedModelId === "url") openUrlDialog();
if (selectedModelId === "upload" && onEditModel) onEditModel();
}}
>
{selectedModelId === "upload" ? "Upload" : "Edit"}
)}
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} />
}
onClick={onDeletePoint}
disabled={selectedIndex === null}
fullWidth
>
Delete
{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" },
},
}}
>
))}
}
onClick={() => onStartDrawing('polyline')}
disabled={drawingState.mode !== 'none'}
>
Create Polyline
onExportGeometries('polyline', format)} disabled={polylines.length === 0} />
}
onClick={onDeleteGeometry}
disabled={selectedGeometry?.type !== 'polyline'}
fullWidth
>
Delete
{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" },
},
}}
>
))}
}
onClick={() => onStartDrawing('polygon')}
disabled={drawingState.mode !== 'none'}
>
Create Polygon
onExportGeometries('polygon', format)} disabled={polygons.length === 0} />
}
onClick={onDeleteGeometry}
disabled={selectedGeometry?.type !== 'polygon'}
fullWidth
>
Delete
);
}