diff --git a/app/scripts/components/analysis/constants.ts b/app/scripts/components/analysis/constants.ts index bee2fe167..a5605fad5 100644 --- a/app/scripts/components/analysis/constants.ts +++ b/app/scripts/components/analysis/constants.ts @@ -1,13 +1,13 @@ import { FeatureCollection, Polygon } from 'geojson'; -import { makeFeatureCollection } from '$components/common/aoi/utils'; +import { featureCollection } from '@turf/helpers'; -export type RegionPreset = 'world'; +export type RegionPreset = 'world' | 'north-america'; export const FeatureByRegionPreset: Record< RegionPreset, FeatureCollection > = { - world: makeFeatureCollection([ + world: featureCollection([ { type: 'Feature', id: 'world', @@ -25,6 +25,25 @@ export const FeatureByRegionPreset: Record< type: 'Polygon' } } + ]), + 'north-america': featureCollection([ + { + type: 'Feature', + id: 'north-america', + properties: {}, + geometry: { + coordinates: [ + [ + [-180, 0], + [-180, 89], + [-60, 89], + [-60, 0], + [-180, 0] + ] + ], + type: 'Polygon' + } + } ]) }; diff --git a/app/scripts/components/analysis/define/aoi-selector.tsx b/app/scripts/components/analysis/define/aoi-selector.tsx index ce7b05da7..4ba1a3d87 100644 --- a/app/scripts/components/analysis/define/aoi-selector.tsx +++ b/app/scripts/components/analysis/define/aoi-selector.tsx @@ -19,7 +19,7 @@ import { import { Button, ButtonGroup } from '@devseed-ui/button'; import { Dropdown, DropMenu, DropTitle } from '@devseed-ui/dropdown'; import { - CollecticonArrowLoop, + CollecticonTrashBin, CollecticonHandPan, CollecticonMarker, CollecticonPencil, @@ -27,12 +27,11 @@ import { } from '@devseed-ui/collecticons'; import { FeatureByRegionPreset, RegionPreset } from '../constants'; import AoIUploadModal from './aoi-upload-modal'; +import { FoldWGuideLine, FoldTitleWOAccent } from '.'; import { - Fold, FoldHeader, FoldHeadline, FoldHeadActions, - FoldTitle, FoldBody } from '$components/common/fold'; import MapboxMap, { MapboxMapRef } from '$components/common/mapbox'; @@ -120,7 +119,7 @@ export default function AoiSelector({ const [aoiModalRevealed, setAoIModalRevealed] = useState(false); return ( - + - Area + Select area of interest +

+ Use the pencil tool to draw a shape on the map or upload your own + shapefile. +

onAoiEvent('aoi.clear')} disabled={!featureCollection?.features.length} > - + @@ -179,6 +182,13 @@ export default function AoiSelector({ World +
  • + onRegionPresetClick('north-america')} + > + North America + +
  • @@ -194,6 +204,6 @@ export default function AoiSelector({ /> -
    + ); } diff --git a/app/scripts/components/analysis/define/index.tsx b/app/scripts/components/analysis/define/index.tsx index 503613123..1fd1a8104 100644 --- a/app/scripts/components/analysis/define/index.tsx +++ b/app/scripts/components/analysis/define/index.tsx @@ -7,8 +7,7 @@ import React, { } from 'react'; import styled from 'styled-components'; import { media, multiply, themeVal } from '@devseed-ui/theme-provider'; -import { Toolbar, ToolbarIconButton, ToolbarLabel } from '@devseed-ui/toolbar'; -import { Dropdown, DropMenu, DropTitle } from '@devseed-ui/dropdown'; +import { Toolbar, ToolbarLabel } from '@devseed-ui/toolbar'; import { Form, FormCheckable, @@ -17,15 +16,17 @@ import { } from '@devseed-ui/form'; import { CollecticonCircleInformation, - CollecticonEllipsisVertical + CollecticonSignDanger } from '@devseed-ui/collecticons'; import { Overline } from '@devseed-ui/typography'; -import { datasets, DatasetLayer, VedaDatum, DatasetData } from 'veda'; +import { datasets, DatasetLayer } from 'veda'; +import { Button, ButtonGroup } from '@devseed-ui/button'; import { useAnalysisParams } from '../results/use-analysis-params'; +import SavedAnalysisControl from '../saved-analysis-control'; import AoiSelector from './aoi-selector'; -import PageHeroActions from './page-hero-actions'; import { useStacCollectionSearch } from './use-stac-collection-search'; +import PageFooterActions from './page-footer.actions'; import { variableGlsp } from '$styles/variable-utils'; import { PageMainContent } from '$styles/page'; @@ -47,14 +48,14 @@ import { getRangeFromPreset, inputFormatToDate } from '$utils/date'; -import DropMenuItemButton from '$styles/drop-menu-item-button'; + import { MapboxMapRef } from '$components/common/mapbox'; +import { ANALYSIS_PATH } from '$utils/routes'; const FormBlock = styled.div` display: flex; flex-flow: row nowrap; gap: ${variableGlsp(0.5)}; - > * { width: 50%; } @@ -100,11 +101,131 @@ export const Note = styled.div` } `; +const UnselectableInfo = styled.div` + font-size: 0.825rem; + font-weight: bold; + display: flex; + align-items: center; + gap: ${variableGlsp(0.5)}; + + & path { + fill: ${themeVal('color.danger')}; + } +`; + +const FormCheckableUnselectable = styled(FormCheckableCustom)` + pointer-events: none; + background: #F0F0F5; +`; + +const DataPointsWarning = styled.div` + display: flex; + align-items: center; + background: #FC3D2119; + border-radius: 99px; + font-size: 0.825rem; + font-weight: bold; + margin-top: ${variableGlsp(0.5)}; + padding: 2px 0 2px 6px; + color: ${themeVal('color.danger')}; + + & path { + fill: ${themeVal('color.danger')}; + } +`; + +const FloatingFooter = styled.div<{ isSticky: boolean }>` + position: sticky; + left: 0; + right: 0; + // trick to get the IntersectionObserver to fire + bottom: -1px; + padding: ${variableGlsp(0.5)}; + background: ${themeVal('color.surface')}; + z-index: 99; + margin-bottom: ${variableGlsp(1)}; + ${(props) => + props.isSticky && + ` + box-shadow: 0 0 10px 0 #0003; + `} +`; + +const FoldWithBullet = styled(Fold)<{ number: string }>` + ${media.largeUp` + padding-left: ${variableGlsp(1)}; + > div { + padding-left: ${variableGlsp(2)}; + position: relative; + // bullet + &::after { + position: absolute; + width: ${variableGlsp(1.5)}; + height: ${variableGlsp(1.5)}; + background-color: #1565EF; + color: ${themeVal('color.surface')}; + border-radius: ${themeVal('shape.ellipsoid')}; + font-size: 1.75rem; + display: flex; + justify-content: center; + align-items: center; + font-weight: 600; + ${(props) => `content: "${props.number}";`} + } + } +`} +`; + +export const FoldWGuideLine = styled(FoldWithBullet)` + ${media.largeUp` + padding-bottom: 0; + > div { + padding-bottom: ${variableGlsp(2)}; + &::before { + position: absolute; + content: ''; + height: 100%; + left: ${variableGlsp(0.7)}; + border-left : 3px solid ${themeVal('color.base-200a')}; + } + } + `} +`; + +const FoldWOPadding = styled(Fold)` + padding: 0; +`; + +export const FoldTitleWOAccent = styled(FoldTitle)` + ${media.largeUp` + &::before { + content: none; + } + `} +`; + +const FormGroupStructureCustom = styled(FormGroupStructure)` + ${media.largeUp` + display: inline-flex; + align-items: center; + `} +`; + +const ToolbarLabelWithSpace = styled(ToolbarLabel)` + margin-right: ${variableGlsp(0.5)}; +`; + +const FoldBodyCustom = styled(FoldBody)` + ${media.largeUp` + flex-flow: row; + flex-grow: 3; + justify-content: space-between; + `} +`; + const findParentDataset = (layerId: string) => { const parentDataset = Object.values(datasets).find((dataset) => - (dataset as VedaDatum).data.layers.find( - (l) => l.id === layerId - ) + dataset!.data.layers.find((l) => l.id === layerId) ); return parentDataset?.data; }; @@ -112,7 +233,7 @@ const findParentDataset = (layerId: string) => { export const allAvailableDatasetsLayers: DatasetLayer[] = Object.values( datasets ) - .map((dataset) => (dataset as VedaDatum).data.layers) + .map((dataset) => dataset!.data.layers) .flat() .filter((d) => d.type !== 'vector' && !d.analysis?.exclude); @@ -183,12 +304,16 @@ export default function Analysis() { [setAnalysisParam, datasetsLayers] ); - const { selectableDatasetLayers, stacSearchStatus, readyToLoadDatasets } = - useStacCollectionSearch({ - start, - end, - aoi: aoiDrawState.featureCollection - }); + const { + selectableDatasetLayers, + unselectableDatasetLayers, + stacSearchStatus, + readyToLoadDatasets + } = useStacCollectionSearch({ + start, + end, + aoi: aoiDrawState.featureCollection + }); // Update datasetsLayers when stac search is refreshed in case some // datasetsLayers are not available anymore @@ -197,7 +322,7 @@ export default function Analysis() { const selectableDatasetLayersIds = selectableDatasetLayers.map( (layer) => layer.id ); - const cleanedDatasetsLayers = datasetsLayers?.filter((l) => + const cleanedDatasetsLayers = datasetsLayers.filter((l) => selectableDatasetLayersIds.includes(l.id) ); @@ -206,7 +331,7 @@ export default function Analysis() { // read/set state loop }, [selectableDatasetLayers, setAnalysisParam]); - const showTip = !readyToLoadDatasets || !datasetsLayers?.length; + const notReady = !readyToLoadDatasets || !datasetsLayers?.length; const infoboxMessage = useMemo(() => { if ( @@ -230,163 +355,196 @@ export default function Analysis() { } }, [readyToLoadDatasets, stacSearchStatus, selectableDatasetLayers.length]); + const footerRef = useRef(null); + const [isFooterSticky, setIsFooterSticky] = React.useState(false); + useEffect(() => { + if (!footerRef.current) return; + const observer = new IntersectionObserver( + ([e]) => { + setIsFooterSticky(e.intersectionRatio < 1); + }, + { threshold: [1] } + ); + observer.observe(footerRef.current); + return () => observer.disconnect(); + }, []); + return ( - - - ( - - )} - /> - - - - - - - Date - - - - Actions - ( - - - - )} - > - Select a date preset - -
  • - onDatePresetClick(e, 'yearToDate')} - > - This year - -
  • -
  • - onDatePresetClick(e, 'last30Days')} - > - Last 30 days - -
  • -
  • - onDatePresetClick(e, 'lastYear')} - > - Last year - -
  • -
  • - onDatePresetClick(e, 'last10Years')} - > - Last 10 years - -
  • -
    -
    -
    -
    -
    - -
    - - - - - - - - - -
    -
    -
    - - - - - Datasets - - - - {!infoboxMessage ? ( + <> + + + ( + + )} + /> + + + + + + Pick a date period +

    + Select start and end date of time series, or choose a pre-set + date range. +

    +
    + +
    +
    - - {selectableDatasetLayers.map((datasetLayer) => ( - - - From: {findParentDataset(datasetLayer.id)?.name} - - {datasetLayer.name} - - ))} - + + + + + + + + +
    - ) : ( - - -

    {infoboxMessage}

    -
    - )} -
    -
    -
    + + Presets + + + + + + + + + + + + Select datasets +

    + Select from available dataset layers for the area and date range + selected. +

    +
    +
    + + {!infoboxMessage ? ( + <> +
    + + {selectableDatasetLayers.map((datasetLayer) => ( + + + From: {findParentDataset(datasetLayer.id)?.name} + + {datasetLayer.name} + + ))} + +
    + {!!unselectableDatasetLayers.length && ( + <> + + + The current area and date selection has returned ( + {unselectableDatasetLayers.length}) datasets with a very + large number of data points. To make them available, + please define a smaller area or a select a shorter date + period. + + +
    + + {unselectableDatasetLayers.map((datasetLayer) => ( + + + From: {findParentDataset(datasetLayer.id)?.name} + + {datasetLayer.name} + + ~ + {datasetLayer.numberOfItems} data points + + + ))} + +
    + + )} + + ) : ( + + +

    {infoboxMessage}

    +
    + )} +
    +
    + + + + + + + + + ); } diff --git a/app/scripts/components/analysis/define/page-footer.actions.tsx b/app/scripts/components/analysis/define/page-footer.actions.tsx new file mode 100644 index 000000000..fdaa862ad --- /dev/null +++ b/app/scripts/components/analysis/define/page-footer.actions.tsx @@ -0,0 +1,136 @@ +import React, { useMemo } from 'react'; +import { Link } from 'react-router-dom'; +import { format } from 'date-fns'; +import { Button, ButtonProps } from '@devseed-ui/button'; +import { CollecticonTickSmall } from '@devseed-ui/collecticons'; + +import { DatasetLayer } from 'veda'; +import { FeatureCollection, Polygon } from 'geojson'; +import styled from 'styled-components'; +import { analysisParams2QueryString } from '../results/use-analysis-params'; +import useSavedSettings from '../use-saved-settings'; + +import { composeVisuallyDisabled } from '$utils/utils'; +import { ANALYSIS_RESULTS_PATH } from '$utils/routes'; +import { calcFeatCollArea } from '$components/common/aoi/utils'; + +const SaveButton = composeVisuallyDisabled(Button); + +interface PageFooterActionsProps { + isNewAnalysis: boolean; + start?: Date; + end?: Date; + datasetsLayers?: DatasetLayer[]; + aoi?: FeatureCollection | null; + disabled?: boolean; +} + +const FooterActions = styled.div` + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +`; + +const FooterRight = styled.div` + display: flex; + flex-flow: row nowrap; + align-items: center; + gap: 1rem; +`; + +const AnalysisDescription = styled.div` + font-size: 0.875rem; + opacity: 0.5; +`; + +export default function PageFooterActions({ + // size, + isNewAnalysis, + start, + end, + datasetsLayers, + aoi, + disabled +}: PageFooterActionsProps) { + const analysisParamsQs = useMemo(() => { + if (!start || !end || !datasetsLayers || !aoi) return ''; + return analysisParams2QueryString({ + start, + end, + datasetsLayers, + aoi + }); + }, [start, end, datasetsLayers, aoi]); + + const { onGenerateClick } = useSavedSettings({ + analysisParamsQs, + params: { + start, + end, + datasets: datasetsLayers?.map((d) => d.name), + aoi: aoi ?? undefined + } + }); + + const analysisDescription = useMemo(() => { + if (!start || !end || !datasetsLayers || !aoi || !datasetsLayers.length) + return ''; + const dataset = + datasetsLayers.length === 1 + ? datasetsLayers[0].name + : `${datasetsLayers.length} datasets`; + const area = `over a ${calcFeatCollArea(aoi)} kmĀ² area`; + const dates = `from ${format(start, 'MMM d, yyyy')} to ${format( + end, + 'MMM d, yyyy' + )}`; + return [dataset, area, dates].join(' '); + }, [start, end, datasetsLayers, aoi]); + + return ( + +
    + {!isNewAnalysis && ( + + )} +
    + + {analysisDescription} + + {disabled ? ( + + Generate analysis + + ) : ( + + )} + +
    + ); +} diff --git a/app/scripts/components/analysis/define/page-hero-actions.tsx b/app/scripts/components/analysis/define/page-hero-actions.tsx deleted file mode 100644 index 225ad2aab..000000000 --- a/app/scripts/components/analysis/define/page-hero-actions.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import { sticky } from 'tippy.js'; -import { FeatureCollection, Polygon } from 'geojson'; -import { Button, ButtonProps } from '@devseed-ui/button'; -import { - CollecticonTickSmall, - CollecticonXmarkSmall -} from '@devseed-ui/collecticons'; -import { VerticalDivider } from '@devseed-ui/toolbar'; -import { DatasetLayer } from 'veda'; - -import { analysisParams2QueryString } from '../results/use-analysis-params'; -import useSavedSettings from '../use-saved-settings'; -import SavedAnalysisControl from '../saved-analysis-control'; - -import { Tip } from '$components/common/tip'; -import { composeVisuallyDisabled } from '$utils/utils'; -import { useMediaQuery } from '$utils/use-media-query'; -import { ANALYSIS_PATH, ANALYSIS_RESULTS_PATH } from '$utils/routes'; - -const SaveButton = composeVisuallyDisabled(Button); - -interface PageHeroActionsProps { - size: ButtonProps['size']; - isNewAnalysis: boolean; - showTip: boolean; - start?: Date; - end?: Date; - datasetsLayers?: DatasetLayer[]; - aoi?: FeatureCollection | null; -} - -export default function PageHeroActions({ - size, - isNewAnalysis, - showTip, - start, - end, - datasetsLayers, - aoi -}: PageHeroActionsProps) { - const { isLargeUp } = useMediaQuery(); - - const analysisParamsQs = useMemo(() => { - if (!start || !end || !datasetsLayers || !aoi) return ''; - return analysisParams2QueryString({ - start, - end, - datasetsLayers, - aoi - }); - }, [start, end, datasetsLayers, aoi]); - - const { onGenerateClick } = useSavedSettings({ - analysisParamsQs, - params: { - start, - end, - datasets: datasetsLayers?.map((d) => d.name), - aoi: aoi ?? undefined - } - }); - - let tipContents; - - if (showTip) { - tipContents = 'To get results, '; - let instructions: string[] = []; - if (!start || !end) - instructions = [...instructions, 'pick start and end dates']; - if (!aoi) instructions = [...instructions, 'define an area']; - if (!datasetsLayers?.length) - instructions = [...instructions, 'select datasets']; - - const instructionsString = instructions - .join(', ') - .replace(/,\s([^,]+)$/, ' and $1.'); - - tipContents = [tipContents, instructionsString].join(''); - } - - return ( - <> - {!isNewAnalysis && ( - - )} - {showTip ? ( - - - Generate - - - ) : ( - - )} - - - - - - ); -} diff --git a/app/scripts/components/analysis/define/use-stac-collection-search.ts b/app/scripts/components/analysis/define/use-stac-collection-search.ts index 66bfd7d3c..86e324a7f 100644 --- a/app/scripts/components/analysis/define/use-stac-collection-search.ts +++ b/app/scripts/components/analysis/define/use-stac-collection-search.ts @@ -5,14 +5,13 @@ import { useQuery } from '@tanstack/react-query'; import booleanIntersects from '@turf/boolean-intersects'; import bboxPolygon from '@turf/bbox-polygon'; import { - areIntervalsOverlapping, - eachDayOfInterval, - eachMonthOfInterval, - eachYearOfInterval + areIntervalsOverlapping } from 'date-fns'; import { DatasetLayer } from 'veda'; +import { MAX_QUERY_NUM } from '../constants'; import { TimeseriesDataResult } from '../results/timeseries-data'; +import { getNumberOfItemsWithinTimeRange } from './utils'; import { allAvailableDatasetsLayers } from '.'; import { utcString2userTzDate } from '$utils/date'; @@ -24,13 +23,7 @@ interface UseStacSearchProps { } export type DatasetWithTimeseriesData = TimeseriesDataResult & - DatasetLayer & { numberOfItems?: number }; - -const DATE_INTERVAL_FN = { - day: eachDayOfInterval, - month: eachMonthOfInterval, - year: eachYearOfInterval -}; + DatasetLayer & { numberOfItems: number }; const collectionUrl = `${process.env.API_STAC_ENDPOINT}/collections`; @@ -52,7 +45,7 @@ export function useStacCollectionSearch({ enabled: readyToLoadDatasets }); - const selectableDatasetLayers = useMemo(() => { + const datasetLayersInRange = useMemo(() => { try { return getInTemporalAndSpatialExtent(result.data, aoi, { start, @@ -63,43 +56,34 @@ export function useStacCollectionSearch({ } }, [result.data, aoi, start, end]); - const selectableDatasetLayersWithNumberOfItems: DatasetWithTimeseriesData[] = + const datasetLayersInRangeWithNumberOfItems: DatasetWithTimeseriesData[] = useMemo(() => { - return selectableDatasetLayers.map((l) => { + return datasetLayersInRange.map((l) => { const numberOfItems = getNumberOfItemsWithinTimeRange(start, end, l); return { ...l, numberOfItems }; }); - }, [selectableDatasetLayers, start, end]); + }, [datasetLayersInRange, start, end]); + + const selectableDatasetLayers = useMemo(() => { + return datasetLayersInRangeWithNumberOfItems.filter( + (l) => l.numberOfItems <= MAX_QUERY_NUM + ); + }, [datasetLayersInRangeWithNumberOfItems]); + + const unselectableDatasetLayers = useMemo(() => { + return datasetLayersInRangeWithNumberOfItems.filter( + (l) => l.numberOfItems > MAX_QUERY_NUM + ); + }, [datasetLayersInRangeWithNumberOfItems]); return { - selectableDatasetLayers: selectableDatasetLayersWithNumberOfItems, + selectableDatasetLayers, + unselectableDatasetLayers, stacSearchStatus: result.status, readyToLoadDatasets }; } -/** - * For each collection, get the number of items within the time range, - * taking into account the time density. - */ -function getNumberOfItemsWithinTimeRange(userStart, userEnd, collection) { - const { isPeriodic, timeDensity, domain, timeseries } = collection; - if (!isPeriodic) { - return timeseries.length; // Check in with back-end team - } - const eachOf = DATE_INTERVAL_FN[timeDensity]; - const start = - +new Date(domain[0]) > +new Date(userStart) - ? new Date(domain[0]) - : new Date(userStart); - const end = - +new Date(domain[1]) < +new Date(userEnd) - ? new Date(domain[1]) - : new Date(userEnd); - - return eachOf({ start, end }).length; -} - function getInTemporalAndSpatialExtent(collectionData, aoi, timeRange) { const matchingCollectionIds = collectionData.reduce((acc, col) => { const id = col.id; diff --git a/app/scripts/components/analysis/define/utils.test.ts b/app/scripts/components/analysis/define/utils.test.ts new file mode 100644 index 000000000..d4f6a2cb6 --- /dev/null +++ b/app/scripts/components/analysis/define/utils.test.ts @@ -0,0 +1,38 @@ +import { getNumberOfItemsWithinTimeRange } from './utils'; + +describe('Item number logic', () => { + + it('checks when the dataset is not periodic', () => { + const notPeriodicDataset = { + isPeriodic: false, + timeseries: [ + "2001-01-16T15:07:02Z", + "2001-12-02T15:05:04Z", + "2002-12-21T15:04:52Z", + "2004-02-26T15:05:40Z", + "2005-02-12T15:06:08Z", + "2006-12-16T15:06:44Z", + "2007-01-17T15:06:46Z", + "2008-01-04T15:06:55Z", + "2008-02-21T15:06:48Z", + "2008-12-05T15:05:57Z", + "2009-12-08T15:07:25Z", + "2010-01-09T15:07:59Z", // match + "2010-01-25T15:08:13Z", // match + "2010-02-10T15:08:25Z", // match + "2010-12-27T15:09:41Z", // match + "2011-01-12T15:09:50Z", // match + "2011-01-28T15:09:56Z", // match + "2011-11-12T15:10:06Z", + "2011-12-30T15:10:33Z"] + }; + + const userStart = "2010-01-01T00:00:00Z"; + const userEnd = "2011-11-01T00:00:00Z"; + + const numberOfDates = getNumberOfItemsWithinTimeRange(userStart, userEnd, notPeriodicDataset); + + + expect(numberOfDates).toEqual(6); + }); +}); diff --git a/app/scripts/components/analysis/define/utils.ts b/app/scripts/components/analysis/define/utils.ts new file mode 100644 index 000000000..dba32fb8b --- /dev/null +++ b/app/scripts/components/analysis/define/utils.ts @@ -0,0 +1,43 @@ +import { + eachDayOfInterval, + eachMonthOfInterval, + eachYearOfInterval +} from 'date-fns'; + +const DATE_INTERVAL_FN = { + day: eachDayOfInterval, + month: eachMonthOfInterval, + year: eachYearOfInterval +}; + +/** + * For each collection, get the number of items within the time range, + * taking into account the time density. + * Separated the function from use-stac-collection-search for easy unit test + */ + +export function getNumberOfItemsWithinTimeRange(userStart, userEnd, collection) { + + const { isPeriodic, timeDensity, domain, timeseries } = collection; + if (!isPeriodic) { + const numberOfItems = timeseries.reduce((acc, t) => { + const date = +new Date(t); + if (date >= +new Date(userStart) && date <= +new Date(userEnd)) { + return acc + 1; + } else { + return acc; + } + }, 0); + return numberOfItems; // Check in with back-end team + } + const eachOf = DATE_INTERVAL_FN[timeDensity]; + const start = + +new Date(domain[0]) > +new Date(userStart) + ? new Date(domain[0]) + : new Date(userStart); + const end = + +new Date(domain[1]) < +new Date(userEnd) + ? new Date(domain[1]) + : new Date(userEnd); + return eachOf({ start, end }).length; +} \ No newline at end of file diff --git a/app/scripts/components/analysis/results/index.tsx b/app/scripts/components/analysis/results/index.tsx index d99ad8477..264215d94 100644 --- a/app/scripts/components/analysis/results/index.tsx +++ b/app/scripts/components/analysis/results/index.tsx @@ -193,6 +193,7 @@ export default function AnalysisResults() { forwardedAs={Link} to={`${ANALYSIS_PATH}${analysisParamsQs}`} size={size} + radius='square' variation='achromic-outline' > Refine diff --git a/app/scripts/components/analysis/results/use-analysis-params.ts b/app/scripts/components/analysis/results/use-analysis-params.ts index b377be0f8..4877578ab 100644 --- a/app/scripts/components/analysis/results/use-analysis-params.ts +++ b/app/scripts/components/analysis/results/use-analysis-params.ts @@ -25,8 +25,8 @@ type AnyAnalysisParamsKey = keyof AnalysisParams; type AnyAnalysisParamsType = Date | DatasetLayer[] | FeatureCollection; const initialState: AnalysisParamsNull = { - start: undefined, - end: undefined, + start: new Date(2018, 0, 1), + end: new Date(2022, 11, 31), datasetsLayers: undefined, aoi: undefined, errors: null diff --git a/app/scripts/components/analysis/saved-analysis-control.tsx b/app/scripts/components/analysis/saved-analysis-control.tsx index 4a6463f23..635ecd275 100644 --- a/app/scripts/components/analysis/saved-analysis-control.tsx +++ b/app/scripts/components/analysis/saved-analysis-control.tsx @@ -4,10 +4,9 @@ import { FeatureCollection, Polygon } from 'geojson'; import styled, { useTheme } from 'styled-components'; import bbox from '@turf/bbox'; import { glsp, themeVal } from '@devseed-ui/theme-provider'; -import { ButtonProps } from '@devseed-ui/button'; +import { Button, ButtonProps } from '@devseed-ui/button'; import { CollecticonClockBack } from '@devseed-ui/collecticons'; -import { ToolbarIconButton } from '@devseed-ui/toolbar'; import { Dropdown, DropMenu, @@ -91,14 +90,15 @@ export default function SavedAnalysisControl({ ( - - - + Past analyses + )} > Past analyses diff --git a/app/scripts/components/common/fold.tsx b/app/scripts/components/common/fold.tsx index a00f7d393..7b412b1cf 100644 --- a/app/scripts/components/common/fold.tsx +++ b/app/scripts/components/common/fold.tsx @@ -49,7 +49,9 @@ export const FoldHeader = styled.div` `; export const FoldHeadline = styled.div` - /* styled-component */ + p { + margin: 1rem 0 0 0; + } `; export const FoldHeadActions = styled.div` diff --git a/app/scripts/styles/theme.ts b/app/scripts/styles/theme.ts index f5498e7a4..1bbadb824 100644 --- a/app/scripts/styles/theme.ts +++ b/app/scripts/styles/theme.ts @@ -21,6 +21,7 @@ export const VEDA_OVERRIDE_THEME = { color: { base: '#2c3e50', primary: '#2276ac', + danger: '#FC3D21', infographicA: '#fcab10', infographicB: '#f4442e', infographicC: '#b62b6e', @@ -57,6 +58,7 @@ export const VEDA_OVERRIDE_THEME = { }; export default function themeOverrides() { + console.log(defaultsDeep({}, theme, VEDA_OVERRIDE_THEME)) if (theme) { return createUITheme(defaultsDeep({}, theme, VEDA_OVERRIDE_THEME)); } else { diff --git a/app/scripts/utils/date.ts b/app/scripts/utils/date.ts index dccf9266c..429e4d462 100644 --- a/app/scripts/utils/date.ts +++ b/app/scripts/utils/date.ts @@ -164,12 +164,13 @@ export type DateRangePreset = | 'yearToDate' | 'last30Days' | 'lastYear' - | 'last10Years'; + | 'last10Years' + | '2018-2022'; export function getRangeFromPreset(preset: DateRangePreset): { start: Date; end: Date; } { - const end = new Date(); + let end = new Date(); let start = startOfYear(new Date()); if (preset === 'last30Days') { start = sub(end, { days: 30 }); @@ -177,6 +178,9 @@ export function getRangeFromPreset(preset: DateRangePreset): { start = sub(end, { years: 1 }); } else if (preset === 'last10Years') { start = sub(end, { years: 10 }); + } else if (preset === '2018-2022') { + start = new Date(2018, 0, 1); + end = new Date(2022, 11, 31); } return { start, diff --git a/package.json b/package.json index cec2d9cd4..83f483556 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "@turf/bbox-polygon": "^6.5.0", "@turf/boolean-intersects": "^6.5.0", "@turf/centroid": "^6.5.0", + "@turf/helpers": "^6.5.0", "@turf/simplify": "^6.5.0", "@turf/union": "^6.5.0", "@types/geojson": "^7946.0.10",