Skip to content
Snippets Groups Projects
Commit ff573cfe authored by Clark's avatar Clark
Browse files

resolved merge conflicts in GeoStacApp.jsx

parents 57a3e9cc d6d2b822
No related tags found
No related merge requests found
......@@ -35,10 +35,10 @@ export default function App() {
let jsonPromise = {};
// Fetched Maps
var mapsJson = {};
let mapsJson = {};
// Combined Data
var aggregateMapList = {};
let aggregateMapList = {};
// Init
fetchStatus[astroWebMaps] = "Not Started";
......
......@@ -29,7 +29,7 @@ let css = {
* @component
*/
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([]);
......@@ -68,7 +68,7 @@ export default function GeoStacApp(props) {
bodyChange={handleTargetBodyChange}
/>
<div id="map-area">
<MapContainer target={targetPlanet} mapList={props.mapList} />
<MapContainer target={targetPlanet.name} mapList={props.mapList}/>
</div>
<QueryConsole />
</div>
......
......@@ -120,18 +120,15 @@ function PlanetDialog(props) {
onClose(value);
};
console.log(props.mapList)
return (
<Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}>
<DialogTitle sx={{ minWidth: 225 }}>Select Target Body</DialogTitle>
<List sx={{ pt: 0 }}>
<ListSubheader value="None">Systems</ListSubheader>
{props.mapList.systems.map((system, sysIndex) => (
<>
<React.Fragment key={system.name}>
<ListItemButton
onClick={() => handleSysOpen(sysIndex)}
key={system.name}
>
<ListItemAvatar>
<Avatar sx={{ bgcolor: blue[100] }}>
......@@ -147,7 +144,7 @@ function PlanetDialog(props) {
{props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => (
<ListItemButton
sx={{ pl: 4 }}
onClick={() => handleListItemClick(body.name)}
onClick={() => handleListItemClick(body)}
key={body.name}
>
<ListItemAvatar>
......@@ -161,7 +158,7 @@ function PlanetDialog(props) {
))}
</List>
</Collapse>
</>
</React.Fragment>
))}
</List>
</Dialog>
......@@ -171,7 +168,7 @@ function PlanetDialog(props) {
PlanetDialog.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
selectedValue: PropTypes.string.isRequired,
selectedValue: PropTypes.object.isRequired,
};
/**
......@@ -187,7 +184,7 @@ PlanetDialog.propTypes = {
*/
export default function ConsoleTargetInfo(props) {
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 = () => {
setOpen(true);
......@@ -215,7 +212,7 @@ export default function ConsoleTargetInfo(props) {
variant="h4"
onClick={handleClickOpen}
>
{props.target.toUpperCase()} <ArrowDropDownIcon fontSize="large" />
{props.target.name.toUpperCase()} <ArrowDropDownIcon fontSize="large" />
</Typography>
</Grid>
<PlanetDialog
......
import React, { useEffect } from "react";
import Checkbox from "@mui/material/Checkbox";
import {Card, CardContent, CardActions} from "@mui/material";
// result action links
import Chip from "@mui/material/Chip";
......@@ -10,6 +11,7 @@ import PreviewIcon from "@mui/icons-material/Preview";
import LaunchIcon from "@mui/icons-material/Launch";
import OpenInFullIcon from "@mui/icons-material/OpenInFull";
import CloseFullscreenIcon from "@mui/icons-material/CloseFullscreen";
import TravelExploreIcon from '@mui/icons-material/TravelExplore'; // Footprints.]
// object with results
import { getFeatures } from "../../js/ApiJsonCollection";
......@@ -17,6 +19,61 @@ import { getFeatures } from "../../js/ApiJsonCollection";
// geotiff thumbnail viewer
import DisplayGeoTiff from "../presentational/DisplayGeoTiff.jsx";
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
......@@ -42,10 +99,14 @@ let css = {
*
*/
export default function FootprintResults(props) {
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) => () => {
geoTiffViewer.displayGeoTiff(value.assets.thumbnail.href);
geoTiffViewer.changeMetaData(
......@@ -58,10 +119,97 @@ export default function FootprintResults(props) {
};
useEffect(() => {
setTimeout(() => {
setFeatures(getFeatures);
}, 1000);
// If target has collections (of footprints)
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 (
<div style={css.root} className="scroll-parent">
......@@ -81,9 +229,14 @@ export default function FootprintResults(props) {
/>
</span>
</div>
{isLoading ?
<LoadingFootprints/>
: hasFootprints ?
<div className="resultsList">
{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">
<img className="resultImg" src={feature.assets.thumbnail.href} />
</div>
......@@ -98,6 +251,9 @@ export default function FootprintResults(props) {
<strong>Date:</strong>&nbsp;{feature.properties.datetime}
</div>
</div>
</div>
</CardContent>
<CardActions>
<div className="resultLinks">
<Stack direction="row" spacing={1}>
<Chip
......@@ -115,15 +271,18 @@ export default function FootprintResults(props) {
component="a"
href={`https://stac.astrogeology.usgs.gov/browser-dev/#/collections/${feature.collection}/items/${feature.id}`}
target="_blank"
//href="https://stac.astrogeology.usgs.gov/browser-dev/"
variant="outlined"
clickable
/>
</Stack>
</div>
</div>
</CardActions>
</Card>
))}
</div>
:
<NoFootprints/>
}
</div>
);
}
......@@ -95,32 +95,41 @@ let css = {
*
*/
export default function SearchAndFilterInput(props) {
// Allows showing/hiding of fields
const keywordDetails = React.useRef(null);
const dateDetails = React.useRef(null);
// React States
const [sortVal, setSortVal] = React.useState("");
const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false);
const [areaCheckVal, setAreaCheckVal] = React.useState(false);
// Sort By
const [sortVal, setSortVal] = React.useState(""); // Sort By What?
const [sortAscCheckVal, setSortAscCheckVal] = React.useState(false); // Sort Ascending or Descending
// 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);
const [keywordTextVal, setKeywordTextVal] = React.useState("");
// Page Number
const [pageNumber, setPageNumber] = React.useState(1);
const [dateCheckVal, setDateCheckVal] = React.useState(false);
const [dateFromVal, setDateFromVal] = React.useState(null);
const [dateToVal, setDateToVal] = React.useState(null);
// Pagination
const [maxPages, setMaxPages] = React.useState(10);
const [maxNumberFootprints, setMaxNumberFootprints] = 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(
css.chipHidden
);
const [gotoPage, setGotopage] = React.useState("Apply to go to page 2");
// Apply/Alert Chip
const [applyChipVisStyle, setApplyChipVisStyle] = React.useState(css.chipHidden);
const [chipMessage, setChipMessage] = React.useState("Apply to Show Footprints on Map");
const setApplyChip = (value) => {
setGotopage(value);
setChipMessage("Apply to Show Footprints on Map");
setApplyChipVisStyle(css.chipShown);
};
......@@ -148,12 +157,56 @@ export default function SearchAndFilterInput(props) {
setMaxPages(1);
setMaxNumberFootprints(0);
setNumberReturned(0);
setApplyChip("Apply to show Footprints");
setApplyChip("Apply to Show Footprints on Map");
//// Uncomment to close details on clear
// keywordDetails.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
const handleSortChange = (event) => {
setSortVal(event.target.value);
......@@ -211,9 +264,18 @@ export default function SearchAndFilterInput(props) {
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
useEffect(() => {
setTimeout(() => {
setCurrentPage(1);
setPageNumber(1);
setMaxNumberFootprints(getNumberMatched);
setNumberReturned(getNumberReturned);
setLimitVal(10);
......@@ -221,13 +283,27 @@ export default function SearchAndFilterInput(props) {
setMaxPages(getMaxNumberPages);
props.footprintNavClick();
}, 2000);
}, [props.target]);
}, [props.target.name]);
// Pagination
const handlePageChange = (event, value) => {
setCurrentPage(value);
setApplyChip("Apply to go to page " + value);
};
// Listen for any state change (input) and update the query string based on it
useEffect(() => {
buildQueryString();
}, [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:
applyButton
......@@ -429,7 +505,7 @@ export default function SearchAndFilterInput(props) {
<div style={applyChipVisStyle}>
<Chip
id="applyChip"
label={gotoPage}
label={chipMessage}
icon={<FlagIcon />}
onClick={handleApply}
variant="outlined"
......
......@@ -46,10 +46,11 @@ export default function Sidebar(props) {
);
const [showSidePanel, setShowSidePanel] = React.useState(true);
const [sidePanelSubStyle, setSidePanelSubStyle] = React.useState(css.shown);
const [expandResults, setExpandResults] = React.useState(true);
const [queryString, setQueryString] = React.useState("?");
const showHideSort = () => {
setShowSidePanel(!showSidePanel);
};
......@@ -72,6 +73,7 @@ export default function Sidebar(props) {
>
<SearchAndFilterInput
target={props.target}
setQueryString={setQueryString}
footprintNavClick={props.footprintNavClick}
/>
{!expandResults && <OutPortal node={footprintResultPortalNode} />}
......@@ -81,7 +83,7 @@ export default function Sidebar(props) {
)}
</div>
<InPortal node={footprintResultPortalNode}>
<FootprintResults changeLayout={handlePanelLayout} />
<FootprintResults target={props.target} queryString={queryString} changeLayout={handlePanelLayout} />
</InPortal>
</>
);
......
import React from "react";
import CircularProgress from '@mui/material/LinearProgress';
import SvgIcon from "@mui/material/SvgIcon";
import loadingImage from "../../images/logos/geostac-logo.svg";
import LinearProgress from "@mui/material/LinearProgress";
export default function SplashScreen() {
return(
......@@ -20,6 +22,7 @@ export default function SplashScreen() {
}}
component={loadingImage}
/>
<LinearProgress/>
</div>
</div>
)
......
......@@ -116,6 +116,7 @@ export default L.Control.AstroDrawFilterControl = L.Control.Draw.extend({
geoJson = geoJson["geometry"];
this.wkt.read(JSON.stringify(geoJson));
window.postMessage(this.shapesToFootprint(this.wkt.components[0]), "*");
},
/**
......
......@@ -316,14 +316,12 @@ Controls the CSS for projection buttons when there is no available projection
}
.resultContainer {
width: 268px;
width: 230px;
display: grid;
grid-template:
"ra rb"
"rc rc"
/ 20% 80%;
border-bottom: 3px double rgba(0, 0, 0, 0.4);
padding: 10px;
}
.resultSub {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment