diff --git a/src/GlobalStates/GlobalStore.ts b/src/GlobalStates/GlobalStore.ts index 7ffda107..7f7f98a2 100644 --- a/src/GlobalStates/GlobalStore.ts +++ b/src/GlobalStates/GlobalStore.ts @@ -33,6 +33,7 @@ type StoreState = { initStore:string; variable: string; variables: string[]; + openVariables: boolean; plotOn: boolean; isFlat: boolean; status: string | null; @@ -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; @@ -98,6 +100,7 @@ export const useGlobalStore = create((set, get) => ({ initStore: ESDC, variable: 'Default', variables: [], + openVariables: false, plotOn: false, isFlat:false, progress: 0, @@ -150,6 +153,7 @@ export const useGlobalStore = create((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 }), diff --git a/src/components/LandingHome.tsx b/src/components/LandingHome.tsx index 5b8df3fc..4caa89f2 100644 --- a/src/components/LandingHome.tsx +++ b/src/components/LandingHome.tsx @@ -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); } @@ -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, @@ -50,16 +45,31 @@ 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:', ''); + 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 => { + return loadNetCDF(blob, filename); + }) + .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) diff --git a/src/components/ui/MainPanel/Dataset.tsx b/src/components/ui/MainPanel/Dataset.tsx index af685562..a4705c23 100644 --- a/src/components/ui/MainPanel/Dataset.tsx +++ b/src/components/ui/MainPanel/Dataset.tsx @@ -32,7 +32,7 @@ const DescriptionContent = ({ setOpenVariables, onCloseDialog, }: { - setOpenVariables: React.Dispatch>; + setOpenVariables: (open: boolean) => void; onCloseDialog: () => void; }) => { const {titleDescription} = useGlobalStore(useShallow(state => ({ @@ -89,7 +89,7 @@ const DatasetOption = ({ }; -const Dataset = ({setOpenVariables} : {setOpenVariables: React.Dispatch>}) => { +const Dataset = () => { const [showStoreInput, setShowStoreInput] = useState(false); const [showLocalInput, setShowLocalInput] = useState(false); const [popoverSide, setPopoverSide] = useState<"left" | "top">("left"); @@ -101,9 +101,10 @@ const Dataset = ({setOpenVariables} : {setOpenVariables: React.Dispatch ({ setInitStore: state.setInitStore, + setOpenVariables: state.setOpenVariables, initStore: state.initStore, })) ); diff --git a/src/components/ui/MainPanel/LocalNetCDF.tsx b/src/components/ui/MainPanel/LocalNetCDF.tsx index beeb8bcf..ebc36e11 100644 --- a/src/components/ui/MainPanel/LocalNetCDF.tsx +++ b/src/components/ui/MainPanel/LocalNetCDF.tsx @@ -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, @@ -13,50 +13,29 @@ import { isMobile } from '../MobileUIHider'; interface LocalNCType { setShowLocal: React.Dispatch>; - setOpenVariables: React.Dispatch>; + 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(null); const handleFileSelect = async (event: ChangeEvent) => { - 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; - } - - 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) - }; + 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); + } catch (e) { + setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); + } + }; return (
diff --git a/src/components/ui/MainPanel/LocalZarr.tsx b/src/components/ui/MainPanel/LocalZarr.tsx index 3edf6d28..cff3fd09 100644 --- a/src/components/ui/MainPanel/LocalZarr.tsx +++ b/src/components/ui/MainPanel/LocalZarr.tsx @@ -8,7 +8,7 @@ import ZarrParser from '@/components/zarr/ZarrParser'; interface LocalZarrType { setShowLocal: React.Dispatch>; - setOpenVariables: React.Dispatch>; + setOpenVariables: (open: boolean) => void; setInitStore: (store: string) => void; } diff --git a/src/components/ui/MainPanel/MainPanel.tsx b/src/components/ui/MainPanel/MainPanel.tsx index 304f06d0..dd70d962 100644 --- a/src/components/ui/MainPanel/MainPanel.tsx +++ b/src/components/ui/MainPanel/MainPanel.tsx @@ -1,17 +1,15 @@ "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(false) + return ( - - + + diff --git a/src/components/ui/MainPanel/MetaDataInfo.tsx b/src/components/ui/MainPanel/MetaDataInfo.tsx index 4e258956..53abdfef 100644 --- a/src/components/ui/MainPanel/MetaDataInfo.tsx +++ b/src/components/ui/MainPanel/MetaDataInfo.tsx @@ -57,7 +57,7 @@ function HandleCustomSteps(e: string, chunkSize: number){ } -const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSide }: { meta: any, metadata: Record, setShowMeta: React.Dispatch>, setOpenVariables: React.Dispatch>, popoverSide: string }) => { +const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSide }: { meta: any, metadata: Record, setShowMeta: React.Dispatch>, 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, diff --git a/src/components/ui/MainPanel/Variables.tsx b/src/components/ui/MainPanel/Variables.tsx index 373ef705..81a15f4c 100644 --- a/src/components/ui/MainPanel/Variables.tsx +++ b/src/components/ui/MainPanel/Variables.tsx @@ -29,26 +29,20 @@ import { } from "@/components/ui/accordion"; -const Variables = ({ - openVariables, - setOpenVariables, -}: { - openVariables: boolean; - setOpenVariables: React.Dispatch>; -}) => { +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 })) ); diff --git a/src/utils/loadNetCDF.ts b/src/utils/loadNetCDF.ts new file mode 100644 index 00000000..a0d249bd --- /dev/null +++ b/src/utils/loadNetCDF.ts @@ -0,0 +1,36 @@ +// 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, setOpenVariables } = 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 }); + // setOpenVariables(true); + } finally { + setStatus(null); + } +} + +export { NETCDF_EXT_REGEX }; \ No newline at end of file