Loading src/components/container/App.jsx +2 −2 Original line number Original line Diff line number Diff line Loading @@ -35,10 +35,10 @@ export default function App() { let jsonPromise = {}; let jsonPromise = {}; // Fetched Maps // Fetched Maps var mapsJson = {}; let mapsJson = {}; // Combined Data // Combined Data var aggregateMapList = {}; let aggregateMapList = {}; // Init // Init fetchStatus[astroWebMaps] = "Not Started"; fetchStatus[astroWebMaps] = "Not Started"; Loading src/components/container/GeoStacApp.jsx +2 −2 Original line number Original line Diff line number Diff line Loading @@ -29,7 +29,7 @@ let css = { * @component * @component */ */ export default function GeoStacApp(props) { export default function GeoStacApp(props) { const [targetPlanet, setTargetPlanet] = React.useState("Mars"); const [targetPlanet, setTargetPlanet] = React.useState(props.mapList.systems[4].bodies[0]); const [footprintData, setFootprintData] = React.useState([]); const [footprintData, setFootprintData] = React.useState([]); Loading Loading @@ -68,7 +68,7 @@ export default function GeoStacApp(props) { bodyChange={handleTargetBodyChange} bodyChange={handleTargetBodyChange} /> /> <div id="map-area"> <div id="map-area"> <MapContainer target={targetPlanet} mapList={props.mapList} /> <MapContainer target={targetPlanet.name} mapList={props.mapList}/> </div> </div> <QueryConsole /> <QueryConsole /> </div> </div> Loading src/components/presentational/ConsoleTargetInfo.jsx +6 −9 Original line number Original line Diff line number Diff line Loading @@ -120,18 +120,15 @@ function PlanetDialog(props) { onClose(value); onClose(value); }; }; console.log(props.mapList) return ( return ( <Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}> <Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}> <DialogTitle sx={{ minWidth: 225 }}>Select Target Body</DialogTitle> <DialogTitle sx={{ minWidth: 225 }}>Select Target Body</DialogTitle> <List sx={{ pt: 0 }}> <List sx={{ pt: 0 }}> <ListSubheader value="None">Systems</ListSubheader> <ListSubheader value="None">Systems</ListSubheader> {props.mapList.systems.map((system, sysIndex) => ( {props.mapList.systems.map((system, sysIndex) => ( <> <React.Fragment key={system.name}> <ListItemButton <ListItemButton onClick={() => handleSysOpen(sysIndex)} onClick={() => handleSysOpen(sysIndex)} key={system.name} > > <ListItemAvatar> <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <Avatar sx={{ bgcolor: blue[100] }}> Loading @@ -147,7 +144,7 @@ function PlanetDialog(props) { {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( <ListItemButton <ListItemButton sx={{ pl: 4 }} sx={{ pl: 4 }} onClick={() => handleListItemClick(body.name)} onClick={() => handleListItemClick(body)} key={body.name} key={body.name} > > <ListItemAvatar> <ListItemAvatar> Loading @@ -161,7 +158,7 @@ function PlanetDialog(props) { ))} ))} </List> </List> </Collapse> </Collapse> </> </React.Fragment> ))} ))} </List> </List> </Dialog> </Dialog> Loading @@ -171,7 +168,7 @@ function PlanetDialog(props) { PlanetDialog.propTypes = { PlanetDialog.propTypes = { onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, open: PropTypes.bool.isRequired, selectedValue: PropTypes.string.isRequired, selectedValue: PropTypes.object.isRequired, }; }; /** /** Loading @@ -187,7 +184,7 @@ PlanetDialog.propTypes = { */ */ export default function ConsoleTargetInfo(props) { export default function ConsoleTargetInfo(props) { const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false); const [selectedValue, setSelectedValue] = React.useState(planets[3][0]); const [selectedValue, setSelectedValue] = React.useState(props.mapList.systems[4].bodies[0]); const handleClickOpen = () => { const handleClickOpen = () => { setOpen(true); setOpen(true); Loading Loading @@ -215,7 +212,7 @@ export default function ConsoleTargetInfo(props) { variant="h4" variant="h4" onClick={handleClickOpen} onClick={handleClickOpen} > > {props.target.toUpperCase()} <ArrowDropDownIcon fontSize="large" /> {props.target.name.toUpperCase()} <ArrowDropDownIcon fontSize="large" /> </Typography> </Typography> </Grid> </Grid> <PlanetDialog <PlanetDialog Loading src/components/presentational/FootprintResults.jsx +207 −48 Original line number Original line Diff line number Diff line import React, { useEffect } from "react"; import React, { useEffect } from "react"; import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox"; import {Card, CardContent, CardActions} from "@mui/material"; // result action links // result action links import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip"; Loading @@ -10,6 +11,7 @@ import PreviewIcon from "@mui/icons-material/Preview"; import LaunchIcon from "@mui/icons-material/Launch"; import LaunchIcon from "@mui/icons-material/Launch"; import OpenInFullIcon from "@mui/icons-material/OpenInFull"; import OpenInFullIcon from "@mui/icons-material/OpenInFull"; import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; import TravelExploreIcon from '@mui/icons-material/TravelExplore'; // Footprints.] // object with results // object with results import { getFeatures } from "../../js/ApiJsonCollection"; import { getFeatures } from "../../js/ApiJsonCollection"; Loading @@ -17,6 +19,61 @@ import { getFeatures } from "../../js/ApiJsonCollection"; // geotiff thumbnail viewer // geotiff thumbnail viewer import DisplayGeoTiff from "../presentational/DisplayGeoTiff.jsx"; import DisplayGeoTiff from "../presentational/DisplayGeoTiff.jsx"; import GeoTiffViewer from "../../js/geoTiffViewer.js"; import GeoTiffViewer from "../../js/geoTiffViewer.js"; import { Skeleton } from "@mui/material"; /** * Skeleton to show when footprints are loading */ function LoadingFootprints() { return ( <div className="resultsList"> { Array(5).fill(null).map((_, i) => ( <Card sx={{ width: 250, margin: 1}} key={i}> <CardContent sx={{padding: 0.9, paddingBottom: 0}}> <div className="resultContainer"> <div className="resultImgDiv"> <Skeleton variant="rectangular" width={32} height={32}/> </div> <div className="resultData"> <Skeleton/> <Skeleton/> <Skeleton/> <Skeleton/> <Skeleton/> </div> </div> </CardContent> <CardActions> <div className="resultLinks"> <Stack direction="row" spacing={1} sx={{marginTop:1}}> <Skeleton variant="rounded" width={100} height={20} sx={{borderRadius:5}}/> <Skeleton variant="rounded" width={100} height={20} sx={{borderRadius:5}}/> </Stack> </div> </CardActions> </Card> ))} </div> ); } function NoFootprints(){ return( <div style={{padding: 10, maxWidth: 268}}> <p> This target has no footprints. To see footprints, go to the dropdown menu in the upper left and pick a target body with the <TravelExploreIcon sx={{fontSize: 16, verticalAlign: "middle"}}/> icon next to it. </p> </div> ); } /** /** * Controls css styling for this component using js to css * Controls css styling for this component using js to css Loading @@ -42,10 +99,14 @@ let css = { * * */ */ export default function FootprintResults(props) { export default function FootprintResults(props) { const [features, setFeatures] = React.useState([]); const [features, setFeatures] = React.useState([]); const geoTiffViewer = new GeoTiffViewer("GeoTiffAsset"); const [isLoading, setIsLoading] = React.useState(true); const [hasFootprints, setHasFootprints] = React.useState(true); // Metadata Popup const geoTiffViewer = new GeoTiffViewer("GeoTiffAsset"); const showMetadata = (value) => () => { const showMetadata = (value) => () => { geoTiffViewer.displayGeoTiff(value.assets.thumbnail.href); geoTiffViewer.displayGeoTiff(value.assets.thumbnail.href); geoTiffViewer.changeMetaData( geoTiffViewer.changeMetaData( Loading @@ -58,10 +119,97 @@ export default function FootprintResults(props) { }; }; useEffect(() => { useEffect(() => { setTimeout(() => { setFeatures(getFeatures); // If target has collections (of footprints) }, 1000); if (props.target.collections.length > 0) { // Set Loading setIsLoading(true); setHasFootprints(true); // Promise tracking let fetchPromise = {}; let jsonPromise = {}; // Result let jsonRes = {}; let itemCollectionUrls = []; for(const collection of props.target.collections) { // Get "items" link for each collection let newItemCollectionUrl = collection.links.find(obj => obj.rel === "items").href + props.queryString; itemCollectionUrls.push(newItemCollectionUrl); } for(const itemCollectionUrl of itemCollectionUrls) { fetchPromise[itemCollectionUrl] = "Not Started"; jsonPromise[itemCollectionUrl] = "Not Started"; jsonRes[itemCollectionUrl] = []; } // Fetch JSON and read into object async function startFetch(targetUrl) { fetchPromise[targetUrl] = fetch( targetUrl ).then((res)=>{ jsonPromise[targetUrl] = res.json().then((jsonData)=>{ jsonRes[targetUrl] = jsonData; }).catch((err)=>{ console.log(err); }); }); }).catch((err) => { console.log(err); }); } async function awaitFetch(targetUrl) { await fetchPromise[targetUrl]; await jsonPromise[targetUrl]; } async function fetchAndWait() { // Start fetching for(const itemCollectionUrl of itemCollectionUrls) { startFetch(itemCollectionUrl); } // Wait for completion for(const itemCollectionUrl of itemCollectionUrls) { await awaitFetch(itemCollectionUrl); } // Extract footprints into array let resultsArr = []; let myFeatures = []; for(const itemCollectionUrl of itemCollectionUrls) { myFeatures.push(jsonRes[itemCollectionUrl]); } for(const featCollection of myFeatures) { resultsArr.push(...featCollection.features) } return resultsArr; } (async () => { // Wait let myFeatures = await fetchAndWait() setFeatures(myFeatures); setHasFootprints(myFeatures.length > 0); setIsLoading(false); })(); } else { setIsLoading(false); setHasFootprints(false); } // setTimeout(() => { // setFeatures(getFeatures); // }, 1000); }, [props.target.name, props.queryString]); return ( return ( <div style={css.root} className="scroll-parent"> <div style={css.root} className="scroll-parent"> Loading @@ -81,9 +229,14 @@ export default function FootprintResults(props) { /> /> </span> </span> </div> </div> {isLoading ? <LoadingFootprints/> : hasFootprints ? <div className="resultsList"> <div className="resultsList"> {features.map((feature) => ( {features.map((feature) => ( <div className="resultContainer" key={feature.id}> <Card sx={{ width: 250, margin: 1}} key={feature.id}> <CardContent sx={{padding: 1.2, paddingBottom: 0}}> <div className="resultContainer" > <div className="resultImgDiv"> <div className="resultImgDiv"> <img className="resultImg" src={feature.assets.thumbnail.href} /> <img className="resultImg" src={feature.assets.thumbnail.href} /> </div> </div> Loading @@ -98,6 +251,9 @@ export default function FootprintResults(props) { <strong>Date:</strong> {feature.properties.datetime} <strong>Date:</strong> {feature.properties.datetime} </div> </div> </div> </div> </div> </CardContent> <CardActions> <div className="resultLinks"> <div className="resultLinks"> <Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}> <Chip <Chip Loading @@ -115,15 +271,18 @@ export default function FootprintResults(props) { component="a" component="a" href={`https://stac.astrogeology.usgs.gov/browser-dev/#/collections/${feature.collection}/items/${feature.id}`} href={`https://stac.astrogeology.usgs.gov/browser-dev/#/collections/${feature.collection}/items/${feature.id}`} target="_blank" target="_blank" //href="https://stac.astrogeology.usgs.gov/browser-dev/" variant="outlined" variant="outlined" clickable clickable /> /> </Stack> </Stack> </div> </div> </div> </CardActions> </Card> ))} ))} </div> </div> : <NoFootprints/> } </div> </div> ); ); } } src/components/presentational/SearchAndFilterInput.jsx +100 −24 Original line number Original line Diff line number Diff line Loading @@ -95,32 +95,41 @@ let css = { * * */ */ export default function SearchAndFilterInput(props) { export default function SearchAndFilterInput(props) { // Allows showing/hiding of fields const keywordDetails = React.useRef(null); const keywordDetails = React.useRef(null); const dateDetails = React.useRef(null); const dateDetails = React.useRef(null); // React States // Sort By const [sortVal, setSortVal] = React.useState(""); const [sortVal, setSortVal] = React.useState(""); // Sort By What? const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false); const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false); // Sort Ascending or Descending const [areaCheckVal, setAreaCheckVal] = React.useState(false); // Filter by X checkboxes const [areaCheckVal, setAreaCheckVal] = React.useState(false); // Area const [keywordCheckVal, setKeywordCheckVal] = React.useState(false); // Keyword const [dateCheckVal, setDateCheckVal] = React.useState(false); // Date // Filter by X values const [areaTextVal, setAreaTextVal] = React.useState(""); // Area (received by window message from AstroDrawFilterControl) const [keywordTextVal, setKeywordTextVal] = React.useState(""); // Keyword const [dateFromVal, setDateFromVal] = React.useState(null); // From Date const [dateToVal, setDateToVal] = React.useState(null); // To Date const [keywordCheckVal, setKeywordCheckVal] = React.useState(false); // Page Number const [keywordTextVal, setKeywordTextVal] = React.useState(""); const [pageNumber, setPageNumber] = React.useState(1); const [dateCheckVal, setDateCheckVal] = React.useState(false); // Pagination const [dateFromVal, setDateFromVal] = React.useState(null); const [dateToVal, setDateToVal] = React.useState(null); const [maxPages, setMaxPages] = React.useState(10); const [maxPages, setMaxPages] = React.useState(10); const [maxNumberFootprints, setMaxNumberFootprints] = React.useState(10); const [maxNumberFootprints, setMaxNumberFootprints] = React.useState(10); const [numberReturned, setNumberReturned] = React.useState(10); const [numberReturned, setNumberReturned] = React.useState(10); const [limitVal, setLimitVal] = React.useState(10); const [limitVal, setLimitVal] = React.useState(10); // Max Number of footprints requested per collection const [applyChipVisStyle, setApplyChipVisStyle] = React.useState( // Apply/Alert Chip css.chipHidden const [applyChipVisStyle, setApplyChipVisStyle] = React.useState(css.chipHidden); ); const [chipMessage, setChipMessage] = React.useState("Apply to Show Footprints on Map"); const [gotoPage, setGotopage] = React.useState("Apply to go to page 2"); const setApplyChip = (value) => { const setApplyChip = (value) => { setGotopage(value); setChipMessage("Apply to Show Footprints on Map"); setApplyChipVisStyle(css.chipShown); setApplyChipVisStyle(css.chipShown); }; }; Loading Loading @@ -148,12 +157,56 @@ export default function SearchAndFilterInput(props) { setMaxPages(1); setMaxPages(1); setMaxNumberFootprints(0); setMaxNumberFootprints(0); setNumberReturned(0); setNumberReturned(0); setApplyChip("Apply to show Footprints"); setApplyChip("Apply to Show Footprints on Map"); //// Uncomment to close details on clear //// Uncomment to close details on clear // keywordDetails.current.open = false; // keywordDetails.current.open = false; // dateDetails.current.open = false; // dateDetails.current.open = false; }; }; const buildQueryString = () => { let myQueryString = "?"; // Page Number if (pageNumber != 1) myQueryString += "page=" + pageNumber + "&"; // Number of footprints requested per request if (limitVal != 10) myQueryString += "limit=" + limitVal + "&" // Date if (dateCheckVal) { let d = new Date(); let fromDate = "1970-01-01T00:00:00Z"; // From start of 1970 by default let toDate = d.getFullYear() + "-12-31T23:59:59Z"; // To end of current year by default // From if(dateFromVal instanceof Date && !isNaN(dateFromVal.valueOf())) { fromDate = dateFromVal.toISOString(); } // To if(dateToVal instanceof Date && !isNaN(dateToVal.valueOf())) { toDate = dateToVal.toISOString(); } myQueryString += "datetime=" + fromDate + "/" + toDate + "&"; } // Keyword if(keywordCheckVal) myQueryString += "keywords=[" + keywordTextVal.split(" ") + "]&"; // Area if(areaCheckVal && areaTextVal !== "") myQueryString += areaTextVal; // Sorting... Not supported by the API? const sortAscDesc = sortAscCheckVal ? "asc" : "desc"; if (sortVal === "date" || sortVal === "location") { console.log("Warning: Sorting not Supported!"); // myQueryString += 'sort=[{field:datetime,direction:' + sortAscDesc + '}]&' } props.setQueryString(myQueryString); } // Sorting // Sorting const handleSortChange = (event) => { const handleSortChange = (event) => { setSortVal(event.target.value); setSortVal(event.target.value); Loading Loading @@ -211,9 +264,18 @@ export default function SearchAndFilterInput(props) { setApplyChip("Apply to show " + value + " footprints"); setApplyChip("Apply to show " + value + " footprints"); }; }; // Pagination const handlePageChange = (event, value) => { setPageNumber(value); setCurrentPage(value); setApplyChip("Apply to go to page " + value); }; // resets pagination and limit when switching targets // resets pagination and limit when switching targets useEffect(() => { useEffect(() => { setTimeout(() => { setTimeout(() => { setCurrentPage(1); setPageNumber(1); setMaxNumberFootprints(getNumberMatched); setMaxNumberFootprints(getNumberMatched); setNumberReturned(getNumberReturned); setNumberReturned(getNumberReturned); setLimitVal(10); setLimitVal(10); Loading @@ -221,13 +283,27 @@ export default function SearchAndFilterInput(props) { setMaxPages(getMaxNumberPages); setMaxPages(getMaxNumberPages); props.footprintNavClick(); props.footprintNavClick(); }, 2000); }, 2000); }, [props.target]); }, [props.target.name]); // Pagination // Listen for any state change (input) and update the query string based on it const handlePageChange = (event, value) => { useEffect(() => { setCurrentPage(value); buildQueryString(); setApplyChip("Apply to go to page " + value); }, [sortVal, sortAscCheckVal, areaCheckVal, areaTextVal, keywordCheckVal, keywordTextVal, dateCheckVal, dateFromVal, dateToVal, limitVal, pageNumber]); }; const onBoxDraw = event => { if(typeof event.data == "string" && event.data.includes("bbox")){ setAreaTextVal(event.data); setAreaCheckVal(true); } } useEffect(() => { window.addEventListener("message", onBoxDraw); return () => { window.removeEventListener("message", onBoxDraw); } }, []); /* Control IDs for reference: /* Control IDs for reference: applyButton applyButton Loading Loading @@ -429,7 +505,7 @@ export default function SearchAndFilterInput(props) { <div style={applyChipVisStyle}> <div style={applyChipVisStyle}> <Chip <Chip id="applyChip" id="applyChip" label={gotoPage} label={chipMessage} icon={<FlagIcon />} icon={<FlagIcon />} onClick={handleApply} onClick={handleApply} variant="outlined" variant="outlined" Loading Loading
src/components/container/App.jsx +2 −2 Original line number Original line Diff line number Diff line Loading @@ -35,10 +35,10 @@ export default function App() { let jsonPromise = {}; let jsonPromise = {}; // Fetched Maps // Fetched Maps var mapsJson = {}; let mapsJson = {}; // Combined Data // Combined Data var aggregateMapList = {}; let aggregateMapList = {}; // Init // Init fetchStatus[astroWebMaps] = "Not Started"; fetchStatus[astroWebMaps] = "Not Started"; Loading
src/components/container/GeoStacApp.jsx +2 −2 Original line number Original line Diff line number Diff line Loading @@ -29,7 +29,7 @@ let css = { * @component * @component */ */ export default function GeoStacApp(props) { export default function GeoStacApp(props) { const [targetPlanet, setTargetPlanet] = React.useState("Mars"); const [targetPlanet, setTargetPlanet] = React.useState(props.mapList.systems[4].bodies[0]); const [footprintData, setFootprintData] = React.useState([]); const [footprintData, setFootprintData] = React.useState([]); Loading Loading @@ -68,7 +68,7 @@ export default function GeoStacApp(props) { bodyChange={handleTargetBodyChange} bodyChange={handleTargetBodyChange} /> /> <div id="map-area"> <div id="map-area"> <MapContainer target={targetPlanet} mapList={props.mapList} /> <MapContainer target={targetPlanet.name} mapList={props.mapList}/> </div> </div> <QueryConsole /> <QueryConsole /> </div> </div> Loading
src/components/presentational/ConsoleTargetInfo.jsx +6 −9 Original line number Original line Diff line number Diff line Loading @@ -120,18 +120,15 @@ function PlanetDialog(props) { onClose(value); onClose(value); }; }; console.log(props.mapList) return ( return ( <Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}> <Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}> <DialogTitle sx={{ minWidth: 225 }}>Select Target Body</DialogTitle> <DialogTitle sx={{ minWidth: 225 }}>Select Target Body</DialogTitle> <List sx={{ pt: 0 }}> <List sx={{ pt: 0 }}> <ListSubheader value="None">Systems</ListSubheader> <ListSubheader value="None">Systems</ListSubheader> {props.mapList.systems.map((system, sysIndex) => ( {props.mapList.systems.map((system, sysIndex) => ( <> <React.Fragment key={system.name}> <ListItemButton <ListItemButton onClick={() => handleSysOpen(sysIndex)} onClick={() => handleSysOpen(sysIndex)} key={system.name} > > <ListItemAvatar> <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <Avatar sx={{ bgcolor: blue[100] }}> Loading @@ -147,7 +144,7 @@ function PlanetDialog(props) { {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( <ListItemButton <ListItemButton sx={{ pl: 4 }} sx={{ pl: 4 }} onClick={() => handleListItemClick(body.name)} onClick={() => handleListItemClick(body)} key={body.name} key={body.name} > > <ListItemAvatar> <ListItemAvatar> Loading @@ -161,7 +158,7 @@ function PlanetDialog(props) { ))} ))} </List> </List> </Collapse> </Collapse> </> </React.Fragment> ))} ))} </List> </List> </Dialog> </Dialog> Loading @@ -171,7 +168,7 @@ function PlanetDialog(props) { PlanetDialog.propTypes = { PlanetDialog.propTypes = { onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, open: PropTypes.bool.isRequired, open: PropTypes.bool.isRequired, selectedValue: PropTypes.string.isRequired, selectedValue: PropTypes.object.isRequired, }; }; /** /** Loading @@ -187,7 +184,7 @@ PlanetDialog.propTypes = { */ */ export default function ConsoleTargetInfo(props) { export default function ConsoleTargetInfo(props) { const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false); const [selectedValue, setSelectedValue] = React.useState(planets[3][0]); const [selectedValue, setSelectedValue] = React.useState(props.mapList.systems[4].bodies[0]); const handleClickOpen = () => { const handleClickOpen = () => { setOpen(true); setOpen(true); Loading Loading @@ -215,7 +212,7 @@ export default function ConsoleTargetInfo(props) { variant="h4" variant="h4" onClick={handleClickOpen} onClick={handleClickOpen} > > {props.target.toUpperCase()} <ArrowDropDownIcon fontSize="large" /> {props.target.name.toUpperCase()} <ArrowDropDownIcon fontSize="large" /> </Typography> </Typography> </Grid> </Grid> <PlanetDialog <PlanetDialog Loading
src/components/presentational/FootprintResults.jsx +207 −48 Original line number Original line Diff line number Diff line import React, { useEffect } from "react"; import React, { useEffect } from "react"; import Checkbox from "@mui/material/Checkbox"; import Checkbox from "@mui/material/Checkbox"; import {Card, CardContent, CardActions} from "@mui/material"; // result action links // result action links import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip"; Loading @@ -10,6 +11,7 @@ import PreviewIcon from "@mui/icons-material/Preview"; import LaunchIcon from "@mui/icons-material/Launch"; import LaunchIcon from "@mui/icons-material/Launch"; import OpenInFullIcon from "@mui/icons-material/OpenInFull"; import OpenInFullIcon from "@mui/icons-material/OpenInFull"; import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen"; import TravelExploreIcon from '@mui/icons-material/TravelExplore'; // Footprints.] // object with results // object with results import { getFeatures } from "../../js/ApiJsonCollection"; import { getFeatures } from "../../js/ApiJsonCollection"; Loading @@ -17,6 +19,61 @@ import { getFeatures } from "../../js/ApiJsonCollection"; // geotiff thumbnail viewer // geotiff thumbnail viewer import DisplayGeoTiff from "../presentational/DisplayGeoTiff.jsx"; import DisplayGeoTiff from "../presentational/DisplayGeoTiff.jsx"; import GeoTiffViewer from "../../js/geoTiffViewer.js"; import GeoTiffViewer from "../../js/geoTiffViewer.js"; import { Skeleton } from "@mui/material"; /** * Skeleton to show when footprints are loading */ function LoadingFootprints() { return ( <div className="resultsList"> { Array(5).fill(null).map((_, i) => ( <Card sx={{ width: 250, margin: 1}} key={i}> <CardContent sx={{padding: 0.9, paddingBottom: 0}}> <div className="resultContainer"> <div className="resultImgDiv"> <Skeleton variant="rectangular" width={32} height={32}/> </div> <div className="resultData"> <Skeleton/> <Skeleton/> <Skeleton/> <Skeleton/> <Skeleton/> </div> </div> </CardContent> <CardActions> <div className="resultLinks"> <Stack direction="row" spacing={1} sx={{marginTop:1}}> <Skeleton variant="rounded" width={100} height={20} sx={{borderRadius:5}}/> <Skeleton variant="rounded" width={100} height={20} sx={{borderRadius:5}}/> </Stack> </div> </CardActions> </Card> ))} </div> ); } function NoFootprints(){ return( <div style={{padding: 10, maxWidth: 268}}> <p> This target has no footprints. To see footprints, go to the dropdown menu in the upper left and pick a target body with the <TravelExploreIcon sx={{fontSize: 16, verticalAlign: "middle"}}/> icon next to it. </p> </div> ); } /** /** * Controls css styling for this component using js to css * Controls css styling for this component using js to css Loading @@ -42,10 +99,14 @@ let css = { * * */ */ export default function FootprintResults(props) { export default function FootprintResults(props) { const [features, setFeatures] = React.useState([]); const [features, setFeatures] = React.useState([]); const geoTiffViewer = new GeoTiffViewer("GeoTiffAsset"); const [isLoading, setIsLoading] = React.useState(true); const [hasFootprints, setHasFootprints] = React.useState(true); // Metadata Popup const geoTiffViewer = new GeoTiffViewer("GeoTiffAsset"); const showMetadata = (value) => () => { const showMetadata = (value) => () => { geoTiffViewer.displayGeoTiff(value.assets.thumbnail.href); geoTiffViewer.displayGeoTiff(value.assets.thumbnail.href); geoTiffViewer.changeMetaData( geoTiffViewer.changeMetaData( Loading @@ -58,10 +119,97 @@ export default function FootprintResults(props) { }; }; useEffect(() => { useEffect(() => { setTimeout(() => { setFeatures(getFeatures); // If target has collections (of footprints) }, 1000); if (props.target.collections.length > 0) { // Set Loading setIsLoading(true); setHasFootprints(true); // Promise tracking let fetchPromise = {}; let jsonPromise = {}; // Result let jsonRes = {}; let itemCollectionUrls = []; for(const collection of props.target.collections) { // Get "items" link for each collection let newItemCollectionUrl = collection.links.find(obj => obj.rel === "items").href + props.queryString; itemCollectionUrls.push(newItemCollectionUrl); } for(const itemCollectionUrl of itemCollectionUrls) { fetchPromise[itemCollectionUrl] = "Not Started"; jsonPromise[itemCollectionUrl] = "Not Started"; jsonRes[itemCollectionUrl] = []; } // Fetch JSON and read into object async function startFetch(targetUrl) { fetchPromise[targetUrl] = fetch( targetUrl ).then((res)=>{ jsonPromise[targetUrl] = res.json().then((jsonData)=>{ jsonRes[targetUrl] = jsonData; }).catch((err)=>{ console.log(err); }); }); }).catch((err) => { console.log(err); }); } async function awaitFetch(targetUrl) { await fetchPromise[targetUrl]; await jsonPromise[targetUrl]; } async function fetchAndWait() { // Start fetching for(const itemCollectionUrl of itemCollectionUrls) { startFetch(itemCollectionUrl); } // Wait for completion for(const itemCollectionUrl of itemCollectionUrls) { await awaitFetch(itemCollectionUrl); } // Extract footprints into array let resultsArr = []; let myFeatures = []; for(const itemCollectionUrl of itemCollectionUrls) { myFeatures.push(jsonRes[itemCollectionUrl]); } for(const featCollection of myFeatures) { resultsArr.push(...featCollection.features) } return resultsArr; } (async () => { // Wait let myFeatures = await fetchAndWait() setFeatures(myFeatures); setHasFootprints(myFeatures.length > 0); setIsLoading(false); })(); } else { setIsLoading(false); setHasFootprints(false); } // setTimeout(() => { // setFeatures(getFeatures); // }, 1000); }, [props.target.name, props.queryString]); return ( return ( <div style={css.root} className="scroll-parent"> <div style={css.root} className="scroll-parent"> Loading @@ -81,9 +229,14 @@ export default function FootprintResults(props) { /> /> </span> </span> </div> </div> {isLoading ? <LoadingFootprints/> : hasFootprints ? <div className="resultsList"> <div className="resultsList"> {features.map((feature) => ( {features.map((feature) => ( <div className="resultContainer" key={feature.id}> <Card sx={{ width: 250, margin: 1}} key={feature.id}> <CardContent sx={{padding: 1.2, paddingBottom: 0}}> <div className="resultContainer" > <div className="resultImgDiv"> <div className="resultImgDiv"> <img className="resultImg" src={feature.assets.thumbnail.href} /> <img className="resultImg" src={feature.assets.thumbnail.href} /> </div> </div> Loading @@ -98,6 +251,9 @@ export default function FootprintResults(props) { <strong>Date:</strong> {feature.properties.datetime} <strong>Date:</strong> {feature.properties.datetime} </div> </div> </div> </div> </div> </CardContent> <CardActions> <div className="resultLinks"> <div className="resultLinks"> <Stack direction="row" spacing={1}> <Stack direction="row" spacing={1}> <Chip <Chip Loading @@ -115,15 +271,18 @@ export default function FootprintResults(props) { component="a" component="a" href={`https://stac.astrogeology.usgs.gov/browser-dev/#/collections/${feature.collection}/items/${feature.id}`} href={`https://stac.astrogeology.usgs.gov/browser-dev/#/collections/${feature.collection}/items/${feature.id}`} target="_blank" target="_blank" //href="https://stac.astrogeology.usgs.gov/browser-dev/" variant="outlined" variant="outlined" clickable clickable /> /> </Stack> </Stack> </div> </div> </div> </CardActions> </Card> ))} ))} </div> </div> : <NoFootprints/> } </div> </div> ); ); } }
src/components/presentational/SearchAndFilterInput.jsx +100 −24 Original line number Original line Diff line number Diff line Loading @@ -95,32 +95,41 @@ let css = { * * */ */ export default function SearchAndFilterInput(props) { export default function SearchAndFilterInput(props) { // Allows showing/hiding of fields const keywordDetails = React.useRef(null); const keywordDetails = React.useRef(null); const dateDetails = React.useRef(null); const dateDetails = React.useRef(null); // React States // Sort By const [sortVal, setSortVal] = React.useState(""); const [sortVal, setSortVal] = React.useState(""); // Sort By What? const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false); const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false); // Sort Ascending or Descending const [areaCheckVal, setAreaCheckVal] = React.useState(false); // Filter by X checkboxes const [areaCheckVal, setAreaCheckVal] = React.useState(false); // Area const [keywordCheckVal, setKeywordCheckVal] = React.useState(false); // Keyword const [dateCheckVal, setDateCheckVal] = React.useState(false); // Date // Filter by X values const [areaTextVal, setAreaTextVal] = React.useState(""); // Area (received by window message from AstroDrawFilterControl) const [keywordTextVal, setKeywordTextVal] = React.useState(""); // Keyword const [dateFromVal, setDateFromVal] = React.useState(null); // From Date const [dateToVal, setDateToVal] = React.useState(null); // To Date const [keywordCheckVal, setKeywordCheckVal] = React.useState(false); // Page Number const [keywordTextVal, setKeywordTextVal] = React.useState(""); const [pageNumber, setPageNumber] = React.useState(1); const [dateCheckVal, setDateCheckVal] = React.useState(false); // Pagination const [dateFromVal, setDateFromVal] = React.useState(null); const [dateToVal, setDateToVal] = React.useState(null); const [maxPages, setMaxPages] = React.useState(10); const [maxPages, setMaxPages] = React.useState(10); const [maxNumberFootprints, setMaxNumberFootprints] = React.useState(10); const [maxNumberFootprints, setMaxNumberFootprints] = React.useState(10); const [numberReturned, setNumberReturned] = React.useState(10); const [numberReturned, setNumberReturned] = React.useState(10); const [limitVal, setLimitVal] = React.useState(10); const [limitVal, setLimitVal] = React.useState(10); // Max Number of footprints requested per collection const [applyChipVisStyle, setApplyChipVisStyle] = React.useState( // Apply/Alert Chip css.chipHidden const [applyChipVisStyle, setApplyChipVisStyle] = React.useState(css.chipHidden); ); const [chipMessage, setChipMessage] = React.useState("Apply to Show Footprints on Map"); const [gotoPage, setGotopage] = React.useState("Apply to go to page 2"); const setApplyChip = (value) => { const setApplyChip = (value) => { setGotopage(value); setChipMessage("Apply to Show Footprints on Map"); setApplyChipVisStyle(css.chipShown); setApplyChipVisStyle(css.chipShown); }; }; Loading Loading @@ -148,12 +157,56 @@ export default function SearchAndFilterInput(props) { setMaxPages(1); setMaxPages(1); setMaxNumberFootprints(0); setMaxNumberFootprints(0); setNumberReturned(0); setNumberReturned(0); setApplyChip("Apply to show Footprints"); setApplyChip("Apply to Show Footprints on Map"); //// Uncomment to close details on clear //// Uncomment to close details on clear // keywordDetails.current.open = false; // keywordDetails.current.open = false; // dateDetails.current.open = false; // dateDetails.current.open = false; }; }; const buildQueryString = () => { let myQueryString = "?"; // Page Number if (pageNumber != 1) myQueryString += "page=" + pageNumber + "&"; // Number of footprints requested per request if (limitVal != 10) myQueryString += "limit=" + limitVal + "&" // Date if (dateCheckVal) { let d = new Date(); let fromDate = "1970-01-01T00:00:00Z"; // From start of 1970 by default let toDate = d.getFullYear() + "-12-31T23:59:59Z"; // To end of current year by default // From if(dateFromVal instanceof Date && !isNaN(dateFromVal.valueOf())) { fromDate = dateFromVal.toISOString(); } // To if(dateToVal instanceof Date && !isNaN(dateToVal.valueOf())) { toDate = dateToVal.toISOString(); } myQueryString += "datetime=" + fromDate + "/" + toDate + "&"; } // Keyword if(keywordCheckVal) myQueryString += "keywords=[" + keywordTextVal.split(" ") + "]&"; // Area if(areaCheckVal && areaTextVal !== "") myQueryString += areaTextVal; // Sorting... Not supported by the API? const sortAscDesc = sortAscCheckVal ? "asc" : "desc"; if (sortVal === "date" || sortVal === "location") { console.log("Warning: Sorting not Supported!"); // myQueryString += 'sort=[{field:datetime,direction:' + sortAscDesc + '}]&' } props.setQueryString(myQueryString); } // Sorting // Sorting const handleSortChange = (event) => { const handleSortChange = (event) => { setSortVal(event.target.value); setSortVal(event.target.value); Loading Loading @@ -211,9 +264,18 @@ export default function SearchAndFilterInput(props) { setApplyChip("Apply to show " + value + " footprints"); setApplyChip("Apply to show " + value + " footprints"); }; }; // Pagination const handlePageChange = (event, value) => { setPageNumber(value); setCurrentPage(value); setApplyChip("Apply to go to page " + value); }; // resets pagination and limit when switching targets // resets pagination and limit when switching targets useEffect(() => { useEffect(() => { setTimeout(() => { setTimeout(() => { setCurrentPage(1); setPageNumber(1); setMaxNumberFootprints(getNumberMatched); setMaxNumberFootprints(getNumberMatched); setNumberReturned(getNumberReturned); setNumberReturned(getNumberReturned); setLimitVal(10); setLimitVal(10); Loading @@ -221,13 +283,27 @@ export default function SearchAndFilterInput(props) { setMaxPages(getMaxNumberPages); setMaxPages(getMaxNumberPages); props.footprintNavClick(); props.footprintNavClick(); }, 2000); }, 2000); }, [props.target]); }, [props.target.name]); // Pagination // Listen for any state change (input) and update the query string based on it const handlePageChange = (event, value) => { useEffect(() => { setCurrentPage(value); buildQueryString(); setApplyChip("Apply to go to page " + value); }, [sortVal, sortAscCheckVal, areaCheckVal, areaTextVal, keywordCheckVal, keywordTextVal, dateCheckVal, dateFromVal, dateToVal, limitVal, pageNumber]); }; const onBoxDraw = event => { if(typeof event.data == "string" && event.data.includes("bbox")){ setAreaTextVal(event.data); setAreaCheckVal(true); } } useEffect(() => { window.addEventListener("message", onBoxDraw); return () => { window.removeEventListener("message", onBoxDraw); } }, []); /* Control IDs for reference: /* Control IDs for reference: applyButton applyButton Loading Loading @@ -429,7 +505,7 @@ export default function SearchAndFilterInput(props) { <div style={applyChipVisStyle}> <div style={applyChipVisStyle}> <Chip <Chip id="applyChip" id="applyChip" label={gotoPage} label={chipMessage} icon={<FlagIcon />} icon={<FlagIcon />} onClick={handleApply} onClick={handleApply} variant="outlined" variant="outlined" Loading