From a5c2b3c179af880bf9778f7c4a57c3685e55a3fb Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Wed, 20 Nov 2024 18:35:36 +0100 Subject: [PATCH 1/2] Create a derived atom for dataset hydration from Next.js, expose it in the veda lib --- .../exploration/atoms/datasetLayers.ts | 27 +++++++++++++++++++ .../components/exploration/atoms/datasets.ts | 12 ++++++--- .../hooks/use-timeline-dataset-atom.tsx | 6 ++--- app/scripts/index.ts | 8 +++++- .../atom-with-url-value-stability.ts | 9 +++++-- 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 app/scripts/components/exploration/atoms/datasetLayers.ts diff --git a/app/scripts/components/exploration/atoms/datasetLayers.ts b/app/scripts/components/exploration/atoms/datasetLayers.ts new file mode 100644 index 000000000..f014ccac3 --- /dev/null +++ b/app/scripts/components/exploration/atoms/datasetLayers.ts @@ -0,0 +1,27 @@ +import { atom } from 'jotai'; +import { DatasetLayer } from '$types/veda'; + +/** + * This is the primary storage atom for external datasets (e.g. passed from Next.js). + */ +export const externalDatasetsAtom = atom([]); + +/** + * Derived atom that transforms the provided datasets into layers. + * It is used by the timelineDatasetsAtom to rebuild state from URL parameters + * while it preserves the parent dataset metadata for each layer that comes + * from the MDX configuration. + */ +export const datasetLayersAtom = atom((get) => { + const datasets = get(externalDatasetsAtom); + + return datasets.flatMap((dataset) => { + return (dataset.layers || []).map((l: any) => ({ + ...l, + parentDataset: { + id: dataset.id, + name: dataset.name + } + })); + }); +}); diff --git a/app/scripts/components/exploration/atoms/datasets.ts b/app/scripts/components/exploration/atoms/datasets.ts index f78bbb18d..1229ceb2b 100644 --- a/app/scripts/components/exploration/atoms/datasets.ts +++ b/app/scripts/components/exploration/atoms/datasets.ts @@ -1,6 +1,6 @@ -import { datasetLayers } from '../data-utils'; import { reconcileDatasets } from '../data-utils-no-faux-module'; import { TimelineDataset, TimelineDatasetForUrl } from '../types.d.ts'; +import { datasetLayersAtom } from './datasetLayers'; import { atomWithUrlValueStability } from '$utils/params-location-atom/atom-with-url-value-stability'; function urlDatasetsDehydrate(datasets: TimelineDataset[]) { @@ -38,7 +38,7 @@ export const timelineDatasetsAtom = atomWithUrlValueStability< dehydrate: (datasets) => { return urlDatasetsDehydrate(datasets); }, - reconcile: (urlDatasets, storageDatasets) => { + reconcile: (urlDatasets, storageDatasets, get) => { // Reconcile what needs to be reconciled. const reconciledDatasets = urlDatasets.map((enc) => { // We only want to do this on load. If the dataset was already @@ -49,10 +49,14 @@ export const timelineDatasetsAtom = atomWithUrlValueStability< if (readyDataset) { return readyDataset; } + const currentDatasetLayers = get(datasetLayersAtom); // Reconcile the dataset with the internal data (from VEDA config files) // and then add the url stored settings. - // @TODO - replace datasetLayers - const [reconciled] = reconcileDatasets([enc.id], datasetLayers, []); + const [reconciled] = reconcileDatasets( + [enc.id], + currentDatasetLayers, + [] + ); if (enc.settings) { reconciled.settings = enc.settings; } diff --git a/app/scripts/components/exploration/hooks/use-timeline-dataset-atom.tsx b/app/scripts/components/exploration/hooks/use-timeline-dataset-atom.tsx index 46811c17d..2de830b30 100644 --- a/app/scripts/components/exploration/hooks/use-timeline-dataset-atom.tsx +++ b/app/scripts/components/exploration/hooks/use-timeline-dataset-atom.tsx @@ -2,10 +2,10 @@ import { useAtom } from 'jotai'; import { timelineDatasetsAtom } from '../atoms/datasets'; import { TimelineDataset } from '../types.d.ts'; -export default function useTimelineDatasetAtom (): [ +export default function useTimelineDatasetAtom(): [ TimelineDataset[], (datasets: TimelineDataset[]) => void -] { +] { const [datasets, setDatasets] = useAtom(timelineDatasetsAtom); return [datasets, setDatasets]; -} \ No newline at end of file +} diff --git a/app/scripts/index.ts b/app/scripts/index.ts index dab90628d..8c2c2b29c 100644 --- a/app/scripts/index.ts +++ b/app/scripts/index.ts @@ -29,6 +29,10 @@ import useTimelineDatasetAtom from '$components/exploration/hooks/use-timeline-d import { timelineDatasetsAtom } from '$components/exploration/atoms/datasets'; import { DatasetSelectorModal } from '$components/exploration/components/dataset-selector-modal'; import { EnvConfigProvider } from '$context/env-config'; +import { + datasetLayersAtom, + externalDatasetsAtom +} from '$components/exploration/atoms/datasetLayers'; // Adding .last property to array /* eslint-disable-next-line fp/no-mutating-methods */ @@ -77,5 +81,7 @@ export { InternalNavLink, // STATE - timelineDatasetsAtom + timelineDatasetsAtom, + externalDatasetsAtom, + datasetLayersAtom }; diff --git a/app/scripts/utils/params-location-atom/atom-with-url-value-stability.ts b/app/scripts/utils/params-location-atom/atom-with-url-value-stability.ts index e383c7d2a..a440d2d66 100644 --- a/app/scripts/utils/params-location-atom/atom-with-url-value-stability.ts +++ b/app/scripts/utils/params-location-atom/atom-with-url-value-stability.ts @@ -57,10 +57,15 @@ interface StableAtomOptions { * the information that goes to the url needs to be simplified. * @param urlValue The value stored in the URL parameter after hydration. * @param storageValue The value stored in the atom. + * @param get A getter function to access other atom values during reconciliation. * @returns The reconciled value. If the function is not provided the urlValue * is considered the reconciled value. */ - reconcile?: (urlValue: ValueUrl, storageValue: Value) => Value; + reconcile?: ( + urlValue: ValueUrl, + storageValue: Value, + get: (atom: any) => any + ) => Value; /** * An optional function to compare two atom values for equality. * @param prev The previous atom value. @@ -93,7 +98,7 @@ export function atomWithUrlValueStability( const storageValue = get(storage); // Reconcile the hydrated value with the storage value. - const reconciled = reconcile(hydrated, storageValue); + const reconciled = reconcile(hydrated, storageValue, get); // If the reconciled value is equal to the storage value, return the // storage value to ensure equality. From 41a3a48a0511a75dee00267d14d87c03a64aff28 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 21 Nov 2024 09:12:53 +0100 Subject: [PATCH 2/2] Hydrate the new atom for backwards compatibility in the VEDA UI as well --- app/scripts/components/exploration/container.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/scripts/components/exploration/container.tsx b/app/scripts/components/exploration/container.tsx index 434dcd457..b8439c738 100644 --- a/app/scripts/components/exploration/container.tsx +++ b/app/scripts/components/exploration/container.tsx @@ -1,12 +1,13 @@ import React, { useState } from 'react'; import { TourProvider } from '@reactour/tour'; import { DevTools } from 'jotai-devtools'; -import { useAtom } from 'jotai'; +import { useAtom, useSetAtom } from 'jotai'; import { PopoverTourComponent, TourManager } from './tour-manager'; import { DatasetSelectorModal } from './components/dataset-selector-modal'; import { allExploreDatasets } from './data-utils'; import useTimelineDatasetAtom from './hooks/use-timeline-dataset-atom'; +import { externalDatasetsAtom } from './atoms/datasetLayers'; import ExplorationAndAnalysis from '.'; import { urlAtom } from '$utils/params-location-atom/url'; import { DATASETS_PATH, EXPLORATION_PATH } from '$utils/routes'; @@ -32,6 +33,8 @@ const tourProviderStyles = { }; export default function ExplorationAndAnalysisContainer() { + const setExternalDatasets = useSetAtom(externalDatasetsAtom); + setExternalDatasets(allExploreDatasets); const [timelineDatasets, setTimelineDatasets] = useTimelineDatasetAtom(); const [datasetModalRevealed, setDatasetModalRevealed] = useState( !timelineDatasets.length @@ -40,8 +43,8 @@ export default function ExplorationAndAnalysisContainer() { // @NOTE: When Exploration page is preloaded (ex. Linked with react-router) // atomWithLocation gets initialized outside of Exploration page and returns the previous page's value // We check if url Atom actually returns the values for exploration page here. - const [currentUrl]= useAtom(urlAtom); - if(!currentUrl.pathname?.includes(EXPLORATION_PATH)) return null; + const [currentUrl] = useAtom(urlAtom); + if (!currentUrl.pathname?.includes(EXPLORATION_PATH)) return null; const openModal = () => setDatasetModalRevealed(true); const closeModal = () => setDatasetModalRevealed(false);