Skip to content
Merged
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
4 changes: 4 additions & 0 deletions src/GlobalStates/GlobalStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type StoreState = {
initStore:string;
variable: string;
variables: string[];
openVariables: boolean;
plotOn: boolean;
isFlat: boolean;
status: string | null;
Expand Down Expand Up @@ -66,6 +67,7 @@ type StoreState = {
setInitStore: (initStore:string ) => void;
setVariable: (variable: string) => void;
setVariables: (variables: string[]) => void;
setOpenVariables: (openVariables: boolean) => void;
setPlotOn: (plotOn: boolean) => void;
setIsFlat: (isFlat: boolean) => void;
setProgress: (progress: number) => void;
Expand Down Expand Up @@ -98,6 +100,7 @@ export const useGlobalStore = create<StoreState>((set, get) => ({
initStore: ESDC,
variable: 'Default',
variables: [],
openVariables: false,
plotOn: false,
isFlat:false,
progress: 0,
Expand Down Expand Up @@ -150,6 +153,7 @@ export const useGlobalStore = create<StoreState>((set, get) => ({
setInitStore: (initStore) => set({ initStore }),
setVariable: (variable) => set({ variable }),
setVariables: (variables) => set({ variables }),
setOpenVariables: (openVariables) => set({ openVariables }),
setPlotOn: (plotOn) => set({ plotOn }),
setIsFlat: (isFlat) => set({ isFlat }),
setProgress: (progress) => set({ progress }),
Expand Down
50 changes: 31 additions & 19 deletions src/components/LandingHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,20 @@ import * as THREE from 'three'
THREE.Cache.enabled = true;
import { GetZarrMetadata, GetVariableNames, GetTitleDescription } from '@/components/zarr/GetMetadata';
import { GetStore } from '@/components/zarr/ZarrLoaderLRU';
import { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import { PlotArea, Plot, LandingShapes } from '@/components/plots';
import { MainPanel } from '@/components/ui';
import { Loading, Navbar, Error as ErrorComponent } from '@/components/ui';
import { useGlobalStore } from '@/GlobalStates/GlobalStore';
import { useZarrStore } from '@/GlobalStates/ZarrStore';
import { useShallow } from 'zustand/shallow';
import { loadNetCDF, NETCDF_EXT_REGEX } from '@/utils/loadNetCDF';

async function sendPing() {
const url = "https://www.bgc-jena.mpg.de/~jpoehls/browzarr/visitor_logger.php";

try {
const response = await fetch(url, {
method: "GET",
});

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const response = await fetch(url, { method: "GET" });
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
} catch (error) {
console.error("Request failed:", error);
}
Expand All @@ -30,9 +25,9 @@ async function sendPing() {
export function LandingHome() {
const {
initStore, timeSeries, variable, plotOn,
setZMeta, setVariables, setPlotOn, setTitleDescription,
setZMeta, setVariables, setPlotOn, setTitleDescription,
} = useGlobalStore(useShallow(state => ({
initStore: state.initStore,
initStore: state.initStore,
timeSeries: state.timeSeries,
variable: state.variable,
plotOn: state.plotOn,
Expand All @@ -50,16 +45,33 @@ export function LandingHome() {
setXSlice: state.setXSlice,
setUseNC: state.setUseNC
})))
function resetSlices(){
setZSlice([0,null])
setYSlice([0,null])
setXSlice([0,null])
}
useEffect(() => { // Update store if URL changes

function resetSlices() {
setZSlice([0, null])
setYSlice([0, null])
setXSlice([0, null])
}

useEffect(() => {
resetSlices();
if (initStore.startsWith('local')){ // Don't fetch store if local
return
if (initStore.startsWith('local:')) {
const path = initStore.replace('local:npm', '');
if (!NETCDF_EXT_REGEX.test(path)) return; // TODO: handled zarr
const filename = path.split('/').pop() ?? 'file.nc';
fetch(`/file?path=${encodeURIComponent(path)}`)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.blob();
})
.then(blob => {
loadNetCDF(blob, filename);
useGlobalStore.setState({openVariables: true})
return
})
Comment on lines +66 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The loadNetCDF function is asynchronous, but it's not being awaited here. This means useGlobalStore.setState({openVariables: true}) will execute immediately, potentially before loadNetCDF has finished its async operations and updated the necessary state. This can lead to race conditions and an inconsistent UI state.

Additionally, this useEffect hook initiates an asynchronous fetch operation but lacks a cleanup mechanism. If the initStore prop changes while a fetch is in progress, a new fetch will start, but the old one might still complete and update the state, leading to further race conditions. A cleanup function should be implemented to handle this, for example using a mounted flag as seen in another useEffect in this file.

        .then(async (blob) => {
          await loadNetCDF(blob, filename);
          useGlobalStore.setState({openVariables: true});
        })

.catch(e => useGlobalStore.getState().setStatus(`Failed to load: ${e instanceof Error ? e.message : String(e)}`));
return;
}
if (initStore.startsWith('local')) return; // local_ set by LocalNetCDF/LocalZarr after load
setUseNC(false)
const newStore = GetStore(initStore)
setCurrentStore(newStore)
Expand Down
9 changes: 5 additions & 4 deletions src/components/ui/MainPanel/Dataset.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const DescriptionContent = ({
setOpenVariables,
onCloseDialog,
}: {
setOpenVariables: React.Dispatch<SetStateAction<boolean>>;
setOpenVariables: (open: boolean) => void;
onCloseDialog: () => void;
}) => {
const {titleDescription} = useGlobalStore(useShallow(state => ({
Expand Down Expand Up @@ -89,7 +89,7 @@ const DatasetOption = ({
};


const Dataset = ({setOpenVariables} : {setOpenVariables: React.Dispatch<React.SetStateAction<boolean>>}) => {
const Dataset = () => {
const [showStoreInput, setShowStoreInput] = useState(false);
const [showLocalInput, setShowLocalInput] = useState(false);
const [popoverSide, setPopoverSide] = useState<"left" | "top">("left");
Expand All @@ -101,9 +101,10 @@ const Dataset = ({setOpenVariables} : {setOpenVariables: React.Dispatch<React.Se
useNC:state.fetchNC
})))

const { initStore, setInitStore } = useGlobalStore(
const { initStore, setInitStore, setOpenVariables } = useGlobalStore(
useShallow((state) => ({
setInitStore: state.setInitStore,
setOpenVariables: state.setOpenVariables,
initStore: state.initStore,
}))
);
Expand Down Expand Up @@ -259,7 +260,7 @@ const Dataset = ({setOpenVariables} : {setOpenVariables: React.Dispatch<React.Se
<Switcher leftText='Zarr' rightText='NetCDF' state={!useNC} onClick={()=>useZarrStore.setState({fetchNC:!useNC})} />
{
useNC ?
<LocalNetCDF setShowLocal={setShowLocalInput} setOpenVariables={popoverSide === 'top' ? setShowDescriptionDialog : setOpenDescriptionPopover} />
<LocalNetCDF setOpenVariables={popoverSide === 'top' ? setShowDescriptionDialog : setOpenDescriptionPopover} />
: isSafari ?
<div className="p-3 rounded-md border border-yellow-600 text-tiny max-w-[300px]">
<strong>Local folder upload is not supported in Safari.</strong> Please use Chrome, Firefox, or Edge instead.
Expand Down
58 changes: 19 additions & 39 deletions src/components/ui/MainPanel/LocalNetCDF.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import React, {ChangeEvent, useState} from 'react'
import { Input } from '../input'
import { useGlobalStore } from '@/GlobalStates/GlobalStore';
import { useZarrStore } from '@/GlobalStates/ZarrStore';
import { NetCDF4 } from '@earthyscience/netcdf4-wasm';
import { loadNetCDF, NETCDF_EXT_REGEX } from '@/utils/loadNetCDF';

import {
Alert,
AlertDescription,
Expand All @@ -12,51 +12,31 @@ import {
import { isMobile } from '../MobileUIHider';

interface LocalNCType {
setShowLocal: React.Dispatch<React.SetStateAction<boolean>>;
setOpenVariables: React.Dispatch<React.SetStateAction<boolean>>;
setOpenVariables: (open: boolean) => void;
}

const NETCDF_EXT_REGEX = /\.(nc|netcdf|nc3|nc4)$/i;

const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => {
const {setStatus } = useGlobalStore.getState()
const {ncModule} = useZarrStore.getState()
// const {ncModule} = useZarrStore.getState()
const [ncError, setError] = useState<string | null>(null);

const handleFileSelect = async (event: ChangeEvent<HTMLInputElement>) => {
setError(null);
const files = event.target.files;
if (!files || files.length === 0) {
setStatus(null)
return;
}
const file = files[0]
// Manual validation (iOS-safe)
if (!NETCDF_EXT_REGEX.test(file.name)) {
setError('Please select a valid NetCDF (.nc, .netcdf, .nc3, .nc4) file.');
return;
}
setError(null);
const files = event.target.files;
if (!files || files.length === 0) { setStatus(null); return; }
const file = files[0];
if (!NETCDF_EXT_REGEX.test(file.name)) {
setError('Please select a valid NetCDF (.nc, .netcdf, .nc3, .nc4) file.');
return;
}
try {
await loadNetCDF(file, file.name);
setOpenVariables(true)

if (ncModule) ncModule.close();
setStatus("Loading...")
const data = await NetCDF4.fromBlobLazy(file)
const [variables, attrs, metadata] = await Promise.all([
data.getVariables(),
data.getGlobalAttributes(),
data.getFullMetadata()
])
useGlobalStore.setState({variables: Object.keys(variables), zMeta: metadata, initStore:`local_${file.name}`})
useZarrStore.setState({ useNC: true, ncModule: data})
const titleDescription = {
title: attrs.title?? file.name,
description: attrs.history?? ''
}
useGlobalStore.setState({titleDescription})

setOpenVariables(true)
// setShowLocal(false)
setStatus(null)
};
} catch (e) {
setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`);
}
};

return (
<div className='w-[100%]'>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/MainPanel/LocalZarr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ZarrParser from '@/components/zarr/ZarrParser';

interface LocalZarrType {
setShowLocal: React.Dispatch<React.SetStateAction<boolean>>;
setOpenVariables: React.Dispatch<React.SetStateAction<boolean>>;
setOpenVariables: (open: boolean) => void;
setInitStore: (store: string) => void;
}

Expand Down
8 changes: 4 additions & 4 deletions src/components/ui/MainPanel/MainPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"use client";

import React, {useState} from 'react'
import React from 'react'
import '../css/MainPanel.css'
import {PlotType, Variables, Colormaps, AdjustPlot, Dataset, PlayButton, AnalysisOptions} from '../index'
import { Card } from "@/components/ui/card"


const MainPanel = () => {
const [openVariables, setOpenVariables] = useState<boolean>(false)

return (
<Card className="panel-container">
<Dataset setOpenVariables={setOpenVariables} />
<Variables openVariables={openVariables} setOpenVariables={setOpenVariables} />
<Dataset />
<Variables />
<PlotType />
<Colormaps />
<AdjustPlot />
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/MainPanel/MetaDataInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function HandleCustomSteps(e: string, chunkSize: number){
}


const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSide }: { meta: any, metadata: Record<string, any>, setShowMeta: React.Dispatch<React.SetStateAction<boolean>>, setOpenVariables: React.Dispatch<React.SetStateAction<boolean>>, popoverSide: string }) => {
const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSide }: { meta: any, metadata: Record<string, any>, setShowMeta: React.Dispatch<React.SetStateAction<boolean>>, setOpenVariables: (open: boolean) => void, popoverSide: string }) => {
const {is4D, idx4D, variable, initStore, setIs4D, setIdx4D, setVariable, setTextureArrayDepths} = useGlobalStore(useShallow(state => ({
is4D: state.is4D, idx4D: state.idx4D, variable: state.variable,
initStore: state.initStore,
Expand Down
16 changes: 5 additions & 11 deletions src/components/ui/MainPanel/Variables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,20 @@ import {
} from "@/components/ui/accordion";


const Variables = ({
openVariables,
setOpenVariables,
}: {
openVariables: boolean;
setOpenVariables: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const Variables = () => {
const [popoverSide, setPopoverSide] = useState<"left" | "top">("left");
const [openMetaPopover, setOpenMetaPopover] = useState(false);

const [showMeta, setShowMeta] = useState(false);
const { variables, zMeta, metadata, setMetadata, initStore } = useGlobalStore(
const { variables, zMeta, metadata, initStore, openVariables, setMetadata, setOpenVariables } = useGlobalStore(
useShallow((state) => ({
variables: state.variables,
zMeta: state.zMeta,
metadata: state.metadata,
dimNames:state.dimNames,
initStore: state.initStore,
openVariables: state.openVariables,
setMetadata: state.setMetadata,
setDimNames:state.setDimNames,
initStore: state.initStore
setOpenVariables: state.setOpenVariables
}))
);

Expand Down
35 changes: 35 additions & 0 deletions src/utils/loadNetCDF.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// utils/loadNetCDF.ts
import { NetCDF4 } from '@earthyscience/netcdf4-wasm';
import { useGlobalStore } from '@/GlobalStates/GlobalStore';
import { useZarrStore } from '@/GlobalStates/ZarrStore';

const NETCDF_EXT_REGEX = /\.(nc|netcdf|nc3|nc4)$/i;

export async function loadNetCDF(file: Blob, filename: string) {
const { setStatus } = useGlobalStore.getState();
const { ncModule } = useZarrStore.getState();
if (ncModule) ncModule.close();
setStatus("Loading...");
try {
const data = await NetCDF4.fromBlobLazy(file);
const [variables, attrs, metadata] = await Promise.all([
data.getVariables(),
data.getGlobalAttributes(),
data.getFullMetadata(),
]);
useGlobalStore.setState({
variables: Object.keys(variables),
zMeta: metadata,
initStore: `local_${filename}`,
titleDescription: {
title: attrs.title ?? filename,
description: attrs.history ?? '',
},
});
useZarrStore.setState({ useNC: true, ncModule: data });
} finally {
setStatus(null);
}
}

export { NETCDF_EXT_REGEX };
Loading