import React, { useState, useEffect } from 'react'; import { Box, Paper, Typography, Tooltip } from '@mui/material'; import { styled } from '@mui/system'; import axios from 'axios'; const ImageContainer = styled(Paper)({ width: '40vw', height: '40vw', display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative', border: '3px dashed #ccc', margin: '10px', }); const ScrollableList = styled('div')({ width: '20vw', height: '17vw', overflowY: 'scroll', display: 'flex', flexDirection: 'column', // alignItems: 'center', border: '2px solid #ccc', margin: '10px', }); const TextItem = styled(Typography)({ margin: '5px 0', cursor: 'pointer', transition: 'color 0.3s ease', '&:hover': { color: '#1976d2', }, }); const TextItemPlain = styled(Typography)({ margin: '5px 0', }); const DotContainer = styled('div')({ display: 'flex', flexDirection: 'column', justifyContent: 'center', // Center vertically // alignItems: 'center', // Center horizontally margin: '0 10px', // height: '100%', // Ensure it takes up the full height of the parent container height: '40vw', }); const DotWrapper = styled('div')({ display: 'flex', alignItems: 'center', margin: '10px 0', }); const Dot = styled('div')(({ selected }) => ({ width: '20px', height: '20px', borderRadius: '50%', backgroundColor: selected ? '#1976d2' : '#ccc', marginRight: '10px', cursor: 'pointer', transition: 'background-color 0.3s ease', })); const LevelLabel = styled(Typography)({ fontSize: '14px', color: '#666', fontWeight: '500', }); const ImageGrid = styled('div')({ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', marginTop: '20px', width: '80vw', overflowX: 'scroll', whiteSpace: 'nowrap', }); const Thumbnail = styled('img')({ width: '10vw', height: '10vw', margin: '5px', cursor: 'pointer', objectFit: 'cover', border: '2px solid transparent', transition: 'border-color 0.3s ease', '&:hover': { borderColor: '#1976d2', }, }); const CloseButton = styled('div')({ position: 'absolute', top: '10px', right: '10px', cursor: 'pointer', backgroundColor: '#f5f5f5', borderRadius: '50%', padding: '5px', display: 'flex', justifyContent: 'center', alignItems: 'center', '&:hover': { backgroundColor: '#e0e0e0', }, }); const App = () => { const [mainImage, setMainImage] = useState(null); const [selectedIdx, setSelectedIdx] = useState(-1); const [thumbnails, setThumbnails] = useState([]); const [selectedDot, setSelectedDot] = useState(-1); const [selectedValidIndices, setSelectedValidIndices] = useState([]); const [validItems, setValidItems] = useState([]); const [invalidItems, setInvalidItems] = useState([]); // Fetch thumbnails from backend useEffect(() => { const fetchThumbnails = async () => { try { const response = await axios.get('/api/return_thumbnails'); const { thumbnails } = response.data; setThumbnails(thumbnails.map((base64) => `data:image/png;base64,${base64}`)); // Decode Base64 } catch (error) { console.error('Error fetching thumbnails:', error); // retry in 1 second console.log('Retrying in 1 second...'); setTimeout(fetchThumbnails, 1000); } }; fetchThumbnails(); }, []); const fetchStateData = async () => { try { console.log('Fetching state data:', selectedIdx, selectedDot, selectedValidIndices); // join the valid indices with commas const validIndicesStr = selectedValidIndices.length === 0 ? 'None' : selectedValidIndices.join(','); const response = await axios.get('/api/return_state_data', { params: { image_index: selectedIdx, detail_level: selectedDot, object_list: validIndicesStr, }, }); const { mask_overlayed_image, valid_object_color_tuples, invalid_objects } = response.data; setMainImage(`data:image/png;base64,${mask_overlayed_image}`); setValidItems(valid_object_color_tuples); setInvalidItems(invalid_objects); } catch (error) { console.error('Error fetching state data:', error); } }; // UseEffect to fetch data after state changes useEffect(() => { if (selectedIdx !== -1 && selectedDot !== -1) { fetchStateData(); } }, [selectedIdx, selectedDot, selectedValidIndices]); const handleThumbnailClick = (index) => { console.log('Selected image:', index); setSelectedIdx(index); // Updates state setSelectedDot(2); // Updates state setSelectedValidIndices([]); // Updates state // fetchStateData will run automatically after state updates }; const handleCloseImage = () => { setMainImage(null); setSelectedIdx(-1); setSelectedDot(-1); setSelectedValidIndices([]); setValidItems([]); setInvalidItems([]); // No need to fetch state data since the image is closed }; const handleDotClick = (index) => { if (selectedIdx === -1) { console.log('Please select an image first'); return; } setSelectedDot(index); setSelectedValidIndices([]); // fetchStateData will run automatically after state updates }; const handleItemClick = (index) => { setSelectedValidIndices((prevSelected) => prevSelected.includes(index) ? prevSelected.filter((item) => item !== index) // Remove if already selected : [...prevSelected, index] // Add to selected items ); console.log('Toggled item:', index); // fetchStateData will run automatically after state updates }; const levels = ['Coarse', 'Mid', 'Fine']; return ( {mainImage ? ( <> Selected X ) : ( Please Select an Image Below )} Segmented {validItems.map((tuple, index) => ( handleItemClick(index)} style={{ color: selectedValidIndices.includes(index) ? '#1976d2' : '#000', // Change text color if selected borderLeft: `10px solid ${tuple[1]}`, // Add colored line paddingLeft: '10px', // Adjust padding to make space for the line // make the text bold if selected fontWeight: selectedValidIndices.includes(index) ? 'bold' : 'normal', // add margin to the left marginLeft: '1vw', marginTop: '0.75vh', }} > {tuple[0]} ))} Detected (but not Segmented) {invalidItems.map((text, index) => ( {text} ))} {levels.map((label, index) => ( handleDotClick(index)} /> {label} ))} {thumbnails.map((image, index) => ( handleThumbnailClick(index)} /> ))} ); }; export default App;