Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export default tseslint.config(
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
}
);
272 changes: 34 additions & 238 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import { useCallback, useMemo, useState, useReducer, ChangeEvent } from 'react';
import { MapGroupResponse, SourceGroup, Box } from './types/maps';
import { useState, useReducer } from 'react';
import { DefaultData } from './types/layers';
import { ColorMapControls } from './components/ColorMapControls';
import {
fetchBoxes,
fetchMaps,
fetchSources,
getHistogramData,
} from './utils/fetchUtils';
import { mapApi } from './api/client';
import {
assertInternalBaselayer,
baselayersReducer,
CHANGE_CMAP_TYPE,
CHANGE_CMAP_VALUES,
CHANGE_LOG_SCALE,
CHANGE_ABSOLUTE_VALUE,
initialBaselayersState,
SET_BASELAYERS_STATE,
} from './reducers/baselayersReducer';
import { useQuery } from './hooks/useQuery';
import { useBaselayerChange } from './hooks/useBaselayerChange';
import { OpenLayersMap } from './components/OpenLayersMap';
import { Login } from './components/Login';
import { LoadingOverlay } from './components/LoadingOverlay';
Expand All @@ -32,274 +22,80 @@ function App() {

const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);

const [flipTiles, setFlipTiles] = useState(true);

/** query the map groups to use as the baselayers of the map */
const { data: mapGroups, isLoading: areMapGroupsLoading } = useQuery<
MapGroupResponse[] | undefined
/** Fetch the default state to use as the initial baselayer and layer menu hierarchy */
const { data: defaultData, isLoading: isInitializing } = useQuery<
DefaultData | undefined
>({
initialData: undefined,
queryKey: [isAuthenticated],
queryFn: async () => {
// Fetch the maps and the map metadata in order to get the list of bands used as
// map baselayers
const { mapGroups, internalBaselayers } = await fetchMaps();
const { defaultMenuState, defaultLayer } = await mapApi.getInitialState();

if (!mapGroups.length || !internalBaselayers.length) {
// If we end up with no maps, SET_BASELAYERS_STATE will fall back to an external baselayer as its default initial baselayer
if (!defaultLayer) {
// If default state errors or is null, SET_BASELAYERS_STATE will fall back to an external baselayer as its default initial baselayer
dispatchBaselayersChange({
type: SET_BASELAYERS_STATE,
internalBaselayers: [],
defaultInternalBaselayer: undefined,
histogramData: undefined,
});
} else {
// Otherwise, get what will be the default baselayer's histogram data to set in the reducer state
const defaultInitialBaselayer = { ...internalBaselayers[0] };
const histogramData = await getHistogramData(
defaultInitialBaselayer.layer_id
const histogramData = await mapApi.getHistogramData(
defaultLayer.layer_id
);

// Check if the default baselayer has an undefined vmin or vmax; if so, set the
// vmin and vmax for the baselayer
if (
defaultInitialBaselayer.vmin === undefined ||
defaultInitialBaselayer.vmax === undefined
defaultLayer.vmin === undefined ||
defaultLayer.vmax === undefined
) {
const histogramData = await getHistogramData(
defaultInitialBaselayer.layer_id
);
defaultInitialBaselayer.vmin = histogramData.vmin;
defaultInitialBaselayer.vmax = histogramData.vmax;
internalBaselayers[0] = defaultInitialBaselayer;
defaultLayer.vmin = histogramData.vmin;
defaultLayer.vmax = histogramData.vmax;
}

// Set the baselayersState with the internalBaselayers; note that this action will also set the
// activeBaselayer to be the first element in internalBaselayers
// Set the baselayersState with the default baselayer; note that this action will also set the
// activeBaselayer to be the default baselayer
dispatchBaselayersChange({
type: SET_BASELAYERS_STATE,
internalBaselayers: internalBaselayers,
defaultInternalBaselayer: defaultLayer,
histogramData,
});
}

return mapGroups;
},
});

/** sourceLists are used as FeatureGroups in the map, which can be toggled on/off in the map legend */
const { data: sourceGroups, isLoading: areSourceGroupsLoading } = useQuery<
SourceGroup[] | undefined
>({
initialData: undefined,
queryKey: [isAuthenticated],
queryFn: async () => {
// Fetch the sources
const sourceGroups = await fetchSources();

return sourceGroups;
},
});

/** highlight boxes allow users to download submaps and to highlight regions of the map */
const { data: highlightBoxes, isLoading: areHighlightBoxesLoading } =
useQuery<Box[] | undefined>({
initialData: undefined,
queryKey: [isAuthenticated],
queryFn: async () => {
// Fetch the highlight boxes
const boxes = await fetchBoxes();

return boxes;
},
});

/** tracks highlight boxes that are "checked" and visible on the map */
const [activeBoxIds, setActiveBoxIds] = useState<number[]>([]);

const [activeSourceGroupIds, setActiveSourceGroupIds] = useState<string[]>(
[]
);

const onSelectedSourceGroupsChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
if (!sourceGroups) return;
if (e.target.checked) {
setActiveSourceGroupIds((prevState) =>
prevState.concat(e.target.value)
);
} else {
setActiveSourceGroupIds((prevState) =>
prevState.filter((id) => id !== e.target.value)
);
}
},
[sourceGroups]
);

const onSelectedHighlightBoxChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
if (!highlightBoxes) return;
if (e.target.checked) {
setActiveBoxIds((prevState) =>
prevState.concat(Number(e.target.value))
);
} else {
setActiveBoxIds((prevState) =>
prevState.filter((id) => id !== Number(e.target.value))
);
}
},
[highlightBoxes]
);

const onCmapValuesChange = useCallback(
(values: number[]) => {
if (baselayersState.activeBaselayer) {
dispatchBaselayersChange({
type: CHANGE_CMAP_VALUES,
activeBaselayer: baselayersState.activeBaselayer,
vmin: values[0],
vmax: values[1],
});
}
},
[baselayersState.activeBaselayer]
);

const onCmapChange = useCallback(
(cmap: string) => {
if (baselayersState.activeBaselayer) {
dispatchBaselayersChange({
type: CHANGE_CMAP_TYPE,
activeBaselayer: baselayersState.activeBaselayer,
cmap,
});
}
},
[baselayersState.activeBaselayer]
);

/** Creates an object of data needed by the submap endpoints to download and to add regions. Since it's
composed from state at this level, we must construct it here and pass it down. */
const submapData = useMemo(() => {
if (assertInternalBaselayer(baselayersState.activeBaselayer)) {
const { layer_id, cmap, vmin, vmax, isLogScale, isAbsoluteValue } =
baselayersState.activeBaselayer;
return {
layer_id,
vmin,
vmax,
cmap,
isLogScale,
isAbsoluteValue,
defaultMenuState,
defaultLayer,
};
}
}, [baselayersState.activeBaselayer]);

const onLogScaleChange = useCallback(
(checked: boolean) => {
if (baselayersState.activeBaselayer) {
dispatchBaselayersChange({
type: CHANGE_LOG_SCALE,
activeBaselayer: baselayersState.activeBaselayer,
isLogScale: checked,
});
}
},
[baselayersState.activeBaselayer]
);

const onAbsoluteValueChange = useCallback(
(checked: boolean) => {
if (baselayersState.activeBaselayer) {
dispatchBaselayersChange({
type: CHANGE_ABSOLUTE_VALUE,
activeBaselayer: baselayersState.activeBaselayer,
isAbsoluteValue: checked,
});
}
},
[baselayersState.activeBaselayer]
);

const {
changeBaselayer,
goBack,
goForward,
optimisticBaselayerId,
isPending,
disableGoBack,
disableGoForward,
} = useBaselayerChange(
baselayersState,
dispatchBaselayersChange,
flipTiles,
setFlipTiles
);
});

const { activeBaselayer, internalBaselayers, histogramData } =
baselayersState;
const { activeBaselayer, histogramData } = baselayersState;

return (
<>
<Login
isAuthenticated={isAuthenticated}
setIsAuthenticated={setIsAuthenticated}
/>
{isAuthenticated !== null &&
activeBaselayer &&
internalBaselayers &&
mapGroups && (
<OpenLayersMap
mapGroups={mapGroups}
baselayersState={baselayersState}
onBaselayerChange={changeBaselayer}
optimisticBaselayerId={optimisticBaselayerId}
isPending={isPending}
disableGoBack={disableGoBack}
disableGoForward={disableGoForward}
goBack={goBack}
goForward={goForward}
sourceGroups={sourceGroups}
activeSourceGroupIds={activeSourceGroupIds}
onSelectedSourceGroupsChange={onSelectedSourceGroupsChange}
highlightBoxes={highlightBoxes}
activeBoxIds={activeBoxIds}
setActiveBoxIds={setActiveBoxIds}
onSelectedHighlightBoxChange={onSelectedHighlightBoxChange}
submapData={submapData}
flipTiles={flipTiles}
setFlipTiles={setFlipTiles}
/>
)}
{isAuthenticated !== null && activeBaselayer && defaultData && (
<OpenLayersMap
defaultData={defaultData}
baselayersState={baselayersState}
dispatchBaselayersChange={dispatchBaselayersChange}
isAuthenticated={isAuthenticated}
/>
)}
{isAuthenticated !== null &&
assertInternalBaselayer(activeBaselayer) &&
activeBaselayer.vmin !== undefined &&
activeBaselayer.vmax !== undefined &&
histogramData && (
<ColorMapControls
values={[activeBaselayer.vmin, activeBaselayer.vmax]}
cmapRange={histogramData.vmax - histogramData.vmin}
onCmapValuesChange={onCmapValuesChange}
cmap={activeBaselayer.cmap}
onCmapChange={onCmapChange}
activeBaselayerId={activeBaselayer.layer_id}
units={activeBaselayer.units}
quantity={activeBaselayer.quantity}
isLogScale={activeBaselayer.isLogScale}
isAbsoluteValue={activeBaselayer.isAbsoluteValue}
onLogScaleChange={onLogScaleChange}
onAbsoluteValueChange={onAbsoluteValueChange}
activeBaselayer={activeBaselayer}
dispatchBaselayersChange={dispatchBaselayersChange}
histogramData={histogramData}
/>
)}
<LoadingOverlay
isLoading={
areMapGroupsLoading ||
areSourceGroupsLoading ||
areHighlightBoxesLoading
}
/>
<LoadingOverlay isLoading={isInitializing} />
</>
);
}
Expand Down
Loading
Loading