From b5bab98542e4fa233201f31696d9298a9b469d94 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Thu, 26 Oct 2023 10:15:16 +0100 Subject: [PATCH] Store exploration dates on url --- .../common/map/controls/aoi/atoms.ts | 25 ++--- .../components/exploration/atoms/atoms.ts | 91 ++++++++++++++++--- 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/app/scripts/components/common/map/controls/aoi/atoms.ts b/app/scripts/components/common/map/controls/aoi/atoms.ts index 0b85f5710..1809e8a78 100644 --- a/app/scripts/components/common/map/controls/aoi/atoms.ts +++ b/app/scripts/components/common/map/controls/aoi/atoms.ts @@ -1,23 +1,24 @@ -import { atom } from "jotai"; -import { atomWithLocation } from "jotai-location"; -import { Feature, Polygon } from "geojson"; -import { AoIFeature } from "../../types"; -import { decodeAois, encodeAois } from "$utils/polygon-url"; +import { atom } from 'jotai'; +import { atomWithLocation } from 'jotai-location'; +import { Feature, Polygon } from 'geojson'; +import { AoIFeature } from '../../types'; +import { decodeAois, encodeAois } from '$utils/polygon-url'; // This is the atom acting as a single source of truth for the AOIs. export const aoisAtom = atomWithLocation(); const aoisSerialized = atom( - (get) => get(aoisAtom).searchParams?.get("aois"), + (get) => get(aoisAtom).searchParams?.get('aois'), (get, set, aois) => { - set(aoisAtom, (prev) => ({ - ...prev, - searchParams: new URLSearchParams([["aois", aois as string]]) - })); + set(aoisAtom, (prev) => { + const searchParams = prev.searchParams ?? new URLSearchParams(); + searchParams.set('aois', aois as string); + + return { ...prev, searchParams }; + }); } ); - // Getter atom to get AoiS as GeoJSON features from the hash. export const aoisFeaturesAtom = atom((get) => { const hash = get(aoisSerialized); @@ -69,4 +70,4 @@ export const aoisDeleteAtom = atom(null, (get, set, ids: string[]) => { export const aoiDeleteAllAtom = atom(null, (get, set) => { set(aoisSerialized, encodeAois([])); -}); \ No newline at end of file +}); diff --git a/app/scripts/components/exploration/atoms/atoms.ts b/app/scripts/components/exploration/atoms/atoms.ts index b3c98a2bf..fe52dd9d1 100644 --- a/app/scripts/components/exploration/atoms/atoms.ts +++ b/app/scripts/components/exploration/atoms/atoms.ts @@ -18,6 +18,23 @@ import { // This is the atom acting as a single source of truth for the AOIs. const locAtom = atomWithLocation(); +const setUrlParam = (name: string, value: string) => (prev) => { + const searchParams = prev.searchParams ?? new URLSearchParams(); + searchParams.set(name, value); + + return { ...prev, searchParams }; +}; + +const getValidDateOrNull = (value: any) => { + if (!value) { + return null; + } + const date = new Date(value); + return isNaN(date.getTime()) ? null : date; +}; + +type ValueUpdater = T | ((prev: T) => T); + // Dataset data that is serialized to the url. Only the data needed to // reconstruct the dataset (and user interaction data like settings) is stored // in the url, otherwise it would be too long. @@ -33,10 +50,7 @@ const datasetsUrlConfig = atom( (get, set, datasets: TimelineDataset[]) => { // Extract need properties from the datasets and encode them. const encoded = urlDatasetsDehydrate(datasets); - set(locAtom, (prev) => ({ - ...prev, - searchParams: new URLSearchParams([['datasets', encoded]]) - })); + set(locAtom, setUrlParam('datasets', encoded)); } ); @@ -67,11 +81,7 @@ export const timelineDatasetsAtom = atom( return reconciled; }); }, - ( - get, - set, - updates: TimelineDataset[] | ((prev: T[]) => T[]) - ) => { + (get, set, updates: ValueUpdater) => { const newData = typeof updates === 'function' ? updates(get(timelineDatasetsStorageAtom)) @@ -81,21 +91,77 @@ export const timelineDatasetsAtom = atom( set(timelineDatasetsStorageAtom, newData); } ); + // Main timeline date. This date defines the datasets shown on the map. -export const selectedDateAtom = atom(null); +export const selectedDateAtom = atom( + (get) => { + const txtDate = get(locAtom).searchParams?.get('date'); + return getValidDateOrNull(txtDate); + }, + (get, set, updates: ValueUpdater) => { + const newData = + typeof updates === 'function' + ? updates(get(selectedCompareDateAtom)) + : updates; + + set(locAtom, setUrlParam('date', newData?.toISOString() ?? '')); + } +); + // Compare date. This is the compare date for the datasets shown on the map. -export const selectedCompareDateAtom = atom(null); +export const selectedCompareDateAtom = atom( + (get) => { + const txtDate = get(locAtom).searchParams?.get('dateCompare'); + return getValidDateOrNull(txtDate); + }, + (get, set, updates: ValueUpdater) => { + const newData = + typeof updates === 'function' + ? updates(get(selectedCompareDateAtom)) + : updates; + + set(locAtom, setUrlParam('dateCompare', newData?.toISOString() ?? '')); + } +); + // Date range for L&R playheads. -export const selectedIntervalAtom = atom(null); +export const selectedIntervalAtom = atom( + (get) => { + const txtDate = get(locAtom).searchParams?.get('dateRange'); + const [start, end] = txtDate?.split('|') ?? []; + + const dateStart = getValidDateOrNull(start); + const dateEnd = getValidDateOrNull(end); + + if (!dateStart || !dateEnd) return null; + + return { start: dateStart, end: dateEnd }; + }, + (get, set, updates: ValueUpdater) => { + const newData = + typeof updates === 'function' + ? updates(get(selectedIntervalAtom)) + : updates; + + const value = newData + ? `${newData.start.toISOString()}|${newData.end.toISOString()}` + : ''; + + set(locAtom, setUrlParam('dateRange', value)); + } +); + // Zoom transform for the timeline. Values as object instead of d3.ZoomTransform export const zoomTransformAtom = atom({ x: 0, y: 0, k: 1 }); + // Width of the whole timeline item. Set via a size observer and then used to // compute the different element sizes. export const timelineWidthAtom = atom(undefined); + // Derived atom with the different sizes of the timeline elements. export const timelineSizesAtom = atom((get) => { const totalWidth = get(timelineWidthAtom); @@ -109,6 +175,7 @@ export const timelineSizesAtom = atom((get) => { ) }; }); + // Whether or not the dataset rows are expanded. export const isExpandedAtom = atom(false);