Skip to content

Commit

Permalink
Store selected exploration datasets on url
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfdsilva committed Oct 23, 2023
1 parent bd3440c commit 0b124b1
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 9 deletions.
84 changes: 80 additions & 4 deletions app/scripts/components/exploration/atoms/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,87 @@
import { atom } from 'jotai';
import { atomWithLocation } from 'jotai-location';

import { HEADER_COLUMN_WIDTH, RIGHT_AXIS_SPACE } from '../constants';
import { DateRange, TimelineDataset, ZoomTransformPlain } from '../types.d.ts';
import {
datasetLayers,
reconcileDatasets,
urlDatasetsDehydrate,
urlDatasetsHydrate
} from '../data-utils';
import {
DateRange,
TimelineDataset,
TimelineDatasetForUrl,
ZoomTransformPlain
} from '../types.d.ts';

// Datasets to show on the timeline and their settings
export const timelineDatasetsAtom = atom<TimelineDataset[]>([]);
// Main timeline date. This is the date for the datasets shown on the map.
// This is the atom acting as a single source of truth for the AOIs.
const locAtom = atomWithLocation();

// 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.
const datasetsUrlConfig = atom(
(get): TimelineDatasetForUrl[] => {
try {
const serialized = get(locAtom).searchParams?.get('datasets') ?? '[]';
return urlDatasetsHydrate(serialized);
} catch (error) {
return [];
}
},
(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]])
}));
}
);

const timelineDatasetsStorageAtom = atom<TimelineDataset[]>([]);

// Datasets to show on the timeline and their settings.
export const timelineDatasetsAtom = atom(
(get) => {
const urlDatasets = get(datasetsUrlConfig);
const datasets = get(timelineDatasetsStorageAtom);

// Reconcile what needs to be reconciled.
return urlDatasets.map((enc) => {
// We only want to do this on load. If the dataset was already
// initialized, skip.
// WARNING: This means that changing settings directly in the url without
// a page refresh will do nothing.
const readyDataset = datasets.find((d) => d.data.id === enc.id);
if (readyDataset) {
return readyDataset;
}
// Reconcile the dataset with the internal data (from VEDA config files)
// and then add the url stored settings.
const [reconciled] = reconcileDatasets([enc.id], datasetLayers, []);
if (enc.settings) {
reconciled.settings = enc.settings;
}
return reconciled;
});
},
(
get,
set,
updates: TimelineDataset[] | (<T extends TimelineDataset>(prev: T[]) => T[])
) => {
const newData =
typeof updates === 'function'
? updates(get(timelineDatasetsStorageAtom))
: updates;

set(datasetsUrlConfig, newData);
set(timelineDatasetsStorageAtom, newData);
}
);
// Main timeline date. This date defines the datasets shown on the map.
export const selectedDateAtom = atom<Date | null>(null);
// Compare date. This is the compare date for the datasets shown on the map.
export const selectedCompareDateAtom = atom<Date | null>(null);
Expand Down
24 changes: 22 additions & 2 deletions app/scripts/components/exploration/data-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import {
StacDatasetData,
TimeDensity,
TimelineDataset,
TimelineDatasetForUrl,
TimelineDatasetStatus
} from './types.d.ts';
import { DataMetric, DATA_METRICS } from './components/datasets/analysis-metrics';
import {
DataMetric,
DATA_METRICS
} from './components/datasets/analysis-metrics';

import { utcString2userTzDate } from '$utils/date';

Expand All @@ -30,7 +34,6 @@ export const datasetLayers = Object.values(datasets).flatMap(
(dataset) => dataset!.data.layers
);


/**
* Returns an array of metrics based on the given Dataset Layer configuration.
* If the layer has metrics defined, it returns only the metrics that match the
Expand Down Expand Up @@ -145,3 +148,20 @@ export function getTimeDensityStartDate(date: Date, timeDensity: TimeDensity) {

return startOfDay(date);
}

export function urlDatasetsDehydrate(datasets: TimelineDataset[]) {
return JSON.stringify(
datasets.map((d) => ({
id: d.data.id,
settings: d.settings
}))
);
}

export function urlDatasetsHydrate(
encoded: string | null | undefined
): TimelineDatasetForUrl[] {
if (!encoded) return [];
const parsed = JSON.parse(encoded);
return parsed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function useStacMetadataOnDatasets() {
useEffectPrevious<[typeof datasetsQueryData, TimelineDataset[]]>(
(prev) => {
const prevQueryData = prev[0];
if (!prevQueryData) return;
const hasPrev = !!prevQueryData;

const { changed, data: updatedDatasets } = datasets
.filter((d) => !(d as any).mocked)
Expand All @@ -155,7 +155,9 @@ export function useStacMetadataOnDatasets() {
(acc, dataset, idx) => {
const curr = datasetsQueryData[idx];

if (didDataChange(curr, prevQueryData[idx])) {
// We want to reconcile the data event if it is the first time.
// In practice data will have changes, since prev is undefined.
if (!hasPrev || didDataChange(curr, prevQueryData[idx])) {
// Changed
return {
changed: true,
Expand Down
7 changes: 6 additions & 1 deletion app/scripts/components/exploration/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { useCallback, useState } from 'react';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import styled from 'styled-components';
import { useAtomValue } from 'jotai';
import { themeVal } from '@devseed-ui/theme-provider';

import { MockControls } from './datasets-mock';
import Timeline from './components/timeline/timeline';
import { ExplorationMap } from './components/map';
import { DatasetSelectorModal } from './components/dataset-selector-modal';
import { timelineDatasetsAtom } from './atoms/atoms';

import { LayoutProps } from '$components/common/layout-root';
import PageHero from '$components/common/page-hero';
Expand Down Expand Up @@ -55,7 +57,10 @@ const Container = styled.div`
`;

function Exploration() {
const [datasetModalRevealed, setDatasetModalRevealed] = useState(true);
const datasets = useAtomValue(timelineDatasetsAtom);
const [datasetModalRevealed, setDatasetModalRevealed] = useState(
!datasets.length
);

const openModal = useCallback(() => setDatasetModalRevealed(true), []);
const closeModal = useCallback(() => setDatasetModalRevealed(false), []);
Expand Down
5 changes: 5 additions & 0 deletions app/scripts/components/exploration/types.d.ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export type TimelineDataset =

// END TimelineDataset type discriminants

export interface TimelineDatasetForUrl {
id: string;
settings?: TimelineDatasetSettings;
}

export interface DateRange {
start: Date;
end: Date;
Expand Down

0 comments on commit 0b124b1

Please sign in to comment.