diff --git a/app/scripts/components/exploration/atoms/atoms.ts b/app/scripts/components/exploration/atoms/atoms.ts index d20be9f22..f570555f9 100644 --- a/app/scripts/components/exploration/atoms/atoms.ts +++ b/app/scripts/components/exploration/atoms/atoms.ts @@ -9,8 +9,10 @@ import { DateRange, TimelineDataset, ZoomTransformPlain } from '../types.d.ts'; // Datasets to show on the timeline and their settings export const timelineDatasetsAtom = atom([]); -// Main timeline date. This date defines the datasets shown on the map. +// Main timeline date. This is the date for the datasets shown on the map. export const selectedDateAtom = atom(null); +// Compare date. This is the compare date for the datasets shown on the map. +export const selectedCompareDateAtom = atom(null); // Date range for L&R playheads. export const selectedIntervalAtom = atom(null); // Zoom transform for the timeline. Values as object instead of d3.ZoomTransform diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index 54eef4fe3..3a7e72190 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -1,9 +1,8 @@ import React, { useState } from 'react'; import { useAtomValue } from 'jotai'; -import { addMonths } from 'date-fns'; import { useStacMetadataOnDatasets } from '../../hooks/use-stac-metadata-datasets'; -import { selectedDateAtom, timelineDatasetsAtom } from '../../atoms/atoms'; +import { selectedCompareDateAtom, selectedDateAtom, timelineDatasetsAtom } from '../../atoms/atoms'; import { TimelineDatasetStatus, TimelineDatasetSuccess @@ -21,7 +20,7 @@ import { useBasemap } from '$components/common/map/controls/hooks/use-basemap'; import DrawControl from '$components/common/map/controls/aoi'; import useAois from '$components/common/map/controls/hooks/use-aois'; -export function ExplorationMap(props: { comparing: boolean }) { +export function ExplorationMap() { const [projection, setProjection] = useState(projectionDefault); const { @@ -36,6 +35,9 @@ export function ExplorationMap(props: { comparing: boolean }) { const datasets = useAtomValue(timelineDatasetsAtom); const selectedDay = useAtomValue(selectedDateAtom); + const selectedCompareDay = useAtomValue(selectedCompareDateAtom); + + const comparing = !!selectedCompareDay; // Reverse the datasets order to have the "top" layer, list-wise, at the "top" layer, z-order wise // Disabled eslint rule as slice() creates a shallow copy @@ -96,7 +98,7 @@ export function ExplorationMap(props: { comparing: boolean }) { - {props.comparing && ( + {comparing && ( // Compare map layers ))} diff --git a/app/scripts/components/exploration/components/timeline/timeline-controls.tsx b/app/scripts/components/exploration/components/timeline/timeline-controls.tsx index 8ef767ed4..64f4358b6 100644 --- a/app/scripts/components/exploration/components/timeline/timeline-controls.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline-controls.tsx @@ -7,8 +7,10 @@ import { scaleTime, ScaleTime } from 'd3'; import { glsp, themeVal } from '@devseed-ui/theme-provider'; import { CollecticonChevronDownSmall, + CollecticonPlusSmall, CollecticonResizeIn, - CollecticonResizeOut + CollecticonResizeOut, + CollecticonTrashBin } from '@devseed-ui/collecticons'; import { Button } from '@devseed-ui/button'; import { DatePicker } from '@devseed-ui/date-picker'; @@ -26,6 +28,7 @@ import { activeAnalysisMetricsAtom, isAnalysisAtom, isExpandedAtom, + selectedCompareDateAtom, selectedDateAtom, selectedIntervalAtom } from '$components/exploration/atoms/atoms'; @@ -69,6 +72,9 @@ export function TimelineControls(props: TimelineControlsProps) { const { xScaled, width } = props; const [selectedDay, setSelectedDay] = useAtom(selectedDateAtom); + const [selectedCompareDay, setSelectedCompareDay] = useAtom( + selectedCompareDateAtom + ); const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); const [activeMetrics, setActiveMetrics] = useAtom(activeAnalysisMetricsAtom); const isAnalysis = useAtomValue(isAnalysisAtom); @@ -86,20 +92,65 @@ export function TimelineControls(props: TimelineControlsProps) { - { - setSelectedDay(d.start!); - }} - renderTriggerElement={(props, label) => ( - - P - {label} - - + + { + setSelectedDay(d.start!); + }} + renderTriggerElement={(props, label) => ( + + A + {label} + + + )} + /> + + {selectedCompareDay ? ( + <> + { + setSelectedCompareDay(d.start!); + }} + renderTriggerElement={(props, label) => ( + + B + {label} + + + )} + /> + { + setSelectedCompareDay(null); + }} + > + + + + ) : ( + { + setSelectedCompareDay(selectedDay); + }} + > + + )} - /> + ; selectedDay: Date; @@ -32,7 +32,11 @@ interface TimelineHeadProps { children: React.ReactNode; } -export function TimelineHead(props: TimelineHeadProps) { +type TimelineHeadProps = Omit & { + label?: string; +}; + +export function TimelineHeadBase(props: TimelineHeadBaseProps) { const { domain, xScaled, selectedDay, width, onDayChange, children } = props; const theme = useTheme(); @@ -81,13 +85,7 @@ export function TimelineHead(props: TimelineHeadProps) { return ( - + {children} @@ -96,11 +94,12 @@ export function TimelineHead(props: TimelineHeadProps) { ); } -export function TimelineHeadP(props: Omit) { +export function TimelineHeadPoint(props: TimelineHeadProps) { const theme = useTheme(); + const { label, ...rest } = props; return ( - + ) { }} /> - P + {label ?? 'P'} - + ); } -export function TimelineHeadL(props: Omit) { +export function TimelineHeadIn(props: TimelineHeadProps) { const theme = useTheme(); + const { label, ...rest } = props; return ( - + ) { }} /> - L + {label ?? 'L'} - + ); } -export function TimelineHeadR(props: Omit) { +export function TimelineHeadOut(props: TimelineHeadProps) { const theme = useTheme(); + const { label, ...rest } = props; + return ( - + ) { dy='1em' textAnchor='end' > - R + {label ?? 'R'} - + ); } diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index 51722b8b8..23e6e605e 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -21,14 +21,15 @@ import { DatasetList } from '../datasets/dataset-list'; import { applyTransform, isEqualTransform, rescaleX } from './timeline-utils'; import { TimelineControls } from './timeline-controls'; import { - TimelineHeadL, - TimelineHeadP, - TimelineHeadR, + TimelineHeadIn, + TimelineHeadPoint, + TimelineHeadOut, TimelineRangeTrack } from './timeline-head'; import { DateGrid } from './date-axis'; import { + selectedCompareDateAtom, selectedDateAtom, selectedIntervalAtom, timelineDatasetsAtom, @@ -46,7 +47,10 @@ import { useScaleFactors, useScales } from '$components/exploration/hooks/scales-hooks'; -import { TimelineDatasetStatus, ZoomTransformPlain } from '$components/exploration/types.d.ts'; +import { + TimelineDatasetStatus, + ZoomTransformPlain +} from '$components/exploration/types.d.ts'; import { useInteractionRectHover } from '$components/exploration/hooks/use-dataset-hover'; import { datasetLayers } from '$components/exploration/data-utils'; @@ -154,6 +158,9 @@ export default function Timeline(props: TimelineProps) { const { contentWidth: width } = useAtomValue(timelineSizesAtom); const [selectedDay, setSelectedDay] = useAtom(selectedDateAtom); + const [selectedCompareDay, setSelectedCompareDay] = useAtom( + selectedCompareDateAtom + ); const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); const translateExtent = useMemo<[[number, number], [number, number]]>( @@ -377,59 +384,72 @@ export default function Timeline(props: TimelineProps) { - {shouldRenderTimeline && selectedDay ? ( - - ) : ( - false - )} - {shouldRenderTimeline && selectedInterval && ( + {shouldRenderTimeline && ( <> - { - setSelectedInterval((interval) => { - const prevDay = sub(interval!.end, { days: 1 }); - return { - end: interval!.end, - start: isAfter(d, prevDay) ? prevDay : d - }; - }); - }} - selectedDay={selectedInterval.start} - width={width} - /> - { - setSelectedInterval((interval) => { - const nextDay = add(interval!.start, { days: 1 }); - return { - start: interval!.start, - end: isBefore(d, nextDay) ? nextDay : d - }; - }); - }} - selectedDay={selectedInterval.end} - width={width} - /> - + {selectedDay && ( + + )} + {selectedCompareDay && ( + + )} + {selectedInterval && ( + <> + { + setSelectedInterval((interval) => { + const prevDay = sub(interval!.end, { days: 1 }); + return { + end: interval!.end, + start: isAfter(d, prevDay) ? prevDay : d + }; + }); + }} + selectedDay={selectedInterval.start} + width={width} + /> + { + setSelectedInterval((interval) => { + const nextDay = add(interval!.start, { days: 1 }); + return { + start: interval!.start, + end: isBefore(d, nextDay) ? nextDay : d + }; + }); + }} + selectedDay={selectedInterval.end} + width={width} + /> + + + )} + + )} - {shouldRenderTimeline && } - diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index 06b834ca0..80d8f5c07 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -55,7 +55,6 @@ const Container = styled.div` `; function Exploration() { - const [compare, setCompare] = useState(false); const [datasetModalRevealed, setDatasetModalRevealed] = useState(true); const openModal = useCallback(() => setDatasetModalRevealed(true), []); @@ -74,10 +73,12 @@ function Exploration() { - + setCompare((v) => !v)} + comparing={false} + onCompareClick={() => { + /* noop */ + }} />