From 9e424653e27c3bdb6f538e603bd979796321773d Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 14:45:40 +0200 Subject: [PATCH 1/7] Stash --- src/GlobalStates/ZarrStore.ts | 2 + src/components/StoreInitializer.tsx | 36 ++++- src/components/ui/MainPanel/LocalNetCDF.tsx | 57 +++++--- src/components/ui/NavBar/Navbar.tsx | 2 + src/components/ui/NavBar/ParameterExport.tsx | 146 +++++++++++++++++++ 5 files changed, 225 insertions(+), 18 deletions(-) create mode 100644 src/components/ui/NavBar/ParameterExport.tsx diff --git a/src/GlobalStates/ZarrStore.ts b/src/GlobalStates/ZarrStore.ts index 00755ee3..c7b90006 100644 --- a/src/GlobalStates/ZarrStore.ts +++ b/src/GlobalStates/ZarrStore.ts @@ -23,6 +23,7 @@ type ZarrState = { fetchOptions: FetchStoreOptions | null; abortController: AbortController | null; fetchKey: number; + ncBlobKey: string | undefined; // The key for the stored File blob for a local NC setZSlice: (zSlice: [number , number | null]) => void; setYSlice: (ySlice: [number , number | null]) => void; @@ -63,6 +64,7 @@ export const useZarrStore = create((set, get) => ({ fetchOptions: null, abortController: null, fetchKey: 0, + ncBlobKey: undefined, setZSlice: (zSlice) => set({ zSlice }), setYSlice: (ySlice) => set({ ySlice }), diff --git a/src/components/StoreInitializer.tsx b/src/components/StoreInitializer.tsx index 199308a4..c09cbcf5 100644 --- a/src/components/StoreInitializer.tsx +++ b/src/components/StoreInitializer.tsx @@ -3,8 +3,21 @@ import { useEffect, Suspense } from "react"; import { useSearchParams } from "next/navigation"; import { useGlobalStore } from "@/GlobalStates/GlobalStore"; import { useZarrStore } from '@/GlobalStates/ZarrStore'; +import { usePlotStore } from "@/GlobalStates/PlotStore"; import { useShallow } from 'zustand/shallow'; import { isRemoteStore } from '@/utils/isRemoteStore'; +import { loadNetCDF } from "@/utils/loadNetCDF"; +import { openDB } from "./ui/MainPanel/LocalNetCDF"; + +async function LoadNCBlob(blobKey:string){ + const db = await openDB(); + return new Promise((res, rej) => { + const tx = db.transaction('blobs', 'readonly'); + const req = tx.objectStore('blobs').get(blobKey); + req.onsuccess = () => res(req.result ?? null); + req.onerror = () => rej(req.error); + }); +} function StoreInitializerInner() { const searchParams = useSearchParams(); @@ -17,11 +30,32 @@ function StoreInitializerInner() { useEffect(() => { const store = searchParams.get("store"); + let data = searchParams.get("data") + if (data){ + const fullObj = JSON.parse(data); + console.log(fullObj.zarrState) + if (fullObj.zarrState.useNC){ + const blobKey = fullObj.zarrState.ncBlobKey + LoadNCBlob(blobKey).then(cache =>{ + //@ts-ignore cache is what we want + const file = cache.file + loadNetCDF(file, file.name).then(() => { + useZarrStore.setState(fullObj.zarrState); + useGlobalStore.setState(fullObj.globalState); + usePlotStore.setState(fullObj.plotState); + }) + }) + } else { + useZarrStore.setState(fullObj.zarrState) + useGlobalStore.setState(fullObj.globalState) + usePlotStore.setState(fullObj.plotState) + } + } if (!store) { setStoreFromURL(false); return; } - + const isNC = searchParams.get("format") === "nc"; setUseNC(isNC); setFetchNC(isNC); diff --git a/src/components/ui/MainPanel/LocalNetCDF.tsx b/src/components/ui/MainPanel/LocalNetCDF.tsx index fe16b3cc..8f386eef 100644 --- a/src/components/ui/MainPanel/LocalNetCDF.tsx +++ b/src/components/ui/MainPanel/LocalNetCDF.tsx @@ -10,33 +10,56 @@ import { AlertTitle, } from '@/components/ui/alert'; import { isMobile } from '../MobileUIHider'; +import { useZarrStore } from '@/GlobalStates/ZarrStore'; interface LocalNCType { setOpenVariables: (open: boolean) => void; } +const DB_NAME = 'browzarr-files'; +const STORE = 'blobs'; + +export function openDB(): Promise { // This will store File Blobs on disk for re-opening NetCDFs from searchParams. + return new Promise((res, rej) => { + const req = indexedDB.open(DB_NAME, 1); + req.onupgradeneeded = () => req.result.createObjectStore(STORE); + req.onsuccess = () => res(req.result); + req.onerror = () => rej(req.error); + }); +} + const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { const {setStatus } = useGlobalStore.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]; - 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) - - } catch (e) { - setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); - } - }; + const handleFileSelect = async (event: ChangeEvent) => { + 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); + const db = await openDB(); + const key = `local_${file.name}` + new Promise((res, rej) => { + const tx = db.transaction(STORE, 'readwrite'); + tx.objectStore(STORE).put({ file, key }, key); + tx.oncomplete = () => { + res(key); + useZarrStore.setState({ncBlobKey:key}) + }; + tx.onerror = () => rej(tx.error); + }); + setOpenVariables(true) + } catch (e) { + setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); + } + }; return (
diff --git a/src/components/ui/NavBar/Navbar.tsx b/src/components/ui/NavBar/Navbar.tsx index aa8ffb23..fe1efee4 100644 --- a/src/components/ui/NavBar/Navbar.tsx +++ b/src/components/ui/NavBar/Navbar.tsx @@ -19,6 +19,7 @@ import { import { cn } from "@/lib/utils"; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; +import { ParameterExport } from "./ParameterExport"; import { Orthographic, Perspective } from "../Elements/Icons"; import PerformanceMode from "./PerformanceMode"; @@ -108,6 +109,7 @@ const Navbar = React.memo(function Navbar() { +
diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx new file mode 100644 index 00000000..693480e3 --- /dev/null +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -0,0 +1,146 @@ +import React, { useState } from 'react' +import { useGlobalStore } from '@/GlobalStates/GlobalStore' +import { usePlotStore } from '@/GlobalStates/PlotStore' +import { useZarrStore } from '@/GlobalStates/ZarrStore' +import { BiExport } from "react-icons/bi"; +import { FiCopy } from "react-icons/fi"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" +import { Button } from '../button'; + +function pick(obj: Record, keys: string[]) { + return Object.fromEntries( + keys.map((k) => [k, obj[k]]) + ) +} +const globalValues = [ + 'initStore', + 'storeFromURL', + 'variable', +] + +const plotValues = [ + 'PlotType', + 'pointSize', + 'scalePoints', + 'scaleIntensity', + 'timeScale', + 'valueRange', + 'xRange', + 'yRange', + 'zRange', + 'showPoints', + 'linePointSize', + 'lineWidth', + 'lineColor', + 'pointColor', + 'useLineColor', + 'lineResolution', + 'cOffset', + 'cScale', + 'useFragOpt', + 'useCustomColor', + 'useCustomPointColor', + 'transparency', + 'nanTransparency', + 'nanColor', + 'showBorders', + 'borderColor', + 'lonExtent', + 'latExtent', + 'originalExtent', + 'lonResolution', + 'latResolution', + 'colorIdx', + 'vTransferRange', + 'vTransferScale', + 'sphereResolution', + 'displacement', + 'displaceSurface', + 'offsetNegatives', + 'zSlice', + 'ySlice', + 'xSlice', + 'interpPixels', + 'useOrtho', + 'rotateFlat', + 'fillValue', + 'coarsen', + 'kernel', + 'useBorderTexture', + 'maskValue', + 'borderWidth', + 'cameraPosition', + 'disablePointScale', + +] + +const zarrValues = [ + 'zSlice', + 'ySlice', + 'xSlice', + 'compress', + 'useNC', // This one is more static and so toggling switch doesn't break all other logic + 'fetchNC', + 'coarsen', + 'kernelSize', + 'kernelDepth', + 'icechunkOptions', + 'fetchOptions', + 'fetchKey', + 'ncBlobKey' +] + + +export const ParameterExport = () => { + const [copied, setCopied] = useState(false); + + function generateURL(){ + const fullObj = { + globalState: pick(useGlobalStore.getState(), globalValues), + plotState: pick(usePlotStore.getState(), plotValues), + zarrState: pick(useZarrStore.getState(), zarrValues), + } + const jString = JSON.stringify(fullObj, (_, v) => typeof v === 'bigint' ? v.toString() : v) + const params = `https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` + return params + } + + const copyToClipboard = async () => { + const url = generateURL() + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + + return ( + + + + + +
+ + + +
+
+
+ ) +} + + From 30c635d59ff507a0001d8029f0b2b73511021147 Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 15:43:44 +0200 Subject: [PATCH 2/7] working with NetCDF --- src/GlobalStates/ZarrStore.ts | 4 +-- src/components/StoreInitializer.tsx | 21 +++---------- src/components/plots/AxisLines.tsx | 6 ++-- src/components/ui/MainPanel/LocalNetCDF.tsx | 26 +++------------ src/components/ui/MainPanel/LocalZarr.tsx | 5 ++- src/components/ui/NavBar/ParameterExport.tsx | 4 +-- src/utils/IndexDB.ts | 33 ++++++++++++++++++++ 7 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 src/utils/IndexDB.ts diff --git a/src/GlobalStates/ZarrStore.ts b/src/GlobalStates/ZarrStore.ts index c7b90006..423796df 100644 --- a/src/GlobalStates/ZarrStore.ts +++ b/src/GlobalStates/ZarrStore.ts @@ -23,7 +23,7 @@ type ZarrState = { fetchOptions: FetchStoreOptions | null; abortController: AbortController | null; fetchKey: number; - ncBlobKey: string | undefined; // The key for the stored File blob for a local NC + blobKey: string | undefined; // The key for the stored File blob for a local NC setZSlice: (zSlice: [number , number | null]) => void; setYSlice: (ySlice: [number , number | null]) => void; @@ -64,7 +64,7 @@ export const useZarrStore = create((set, get) => ({ fetchOptions: null, abortController: null, fetchKey: 0, - ncBlobKey: undefined, + blobKey: undefined, setZSlice: (zSlice) => set({ zSlice }), setYSlice: (ySlice) => set({ ySlice }), diff --git a/src/components/StoreInitializer.tsx b/src/components/StoreInitializer.tsx index c09cbcf5..f84f6a5d 100644 --- a/src/components/StoreInitializer.tsx +++ b/src/components/StoreInitializer.tsx @@ -7,17 +7,7 @@ import { usePlotStore } from "@/GlobalStates/PlotStore"; import { useShallow } from 'zustand/shallow'; import { isRemoteStore } from '@/utils/isRemoteStore'; import { loadNetCDF } from "@/utils/loadNetCDF"; -import { openDB } from "./ui/MainPanel/LocalNetCDF"; - -async function LoadNCBlob(blobKey:string){ - const db = await openDB(); - return new Promise((res, rej) => { - const tx = db.transaction('blobs', 'readonly'); - const req = tx.objectStore('blobs').get(blobKey); - req.onsuccess = () => res(req.result ?? null); - req.onerror = () => rej(req.error); - }); -} +import { loadFile } from "@/utils/IndexDB"; function StoreInitializerInner() { const searchParams = useSearchParams(); @@ -33,12 +23,11 @@ function StoreInitializerInner() { let data = searchParams.get("data") if (data){ const fullObj = JSON.parse(data); - console.log(fullObj.zarrState) - if (fullObj.zarrState.useNC){ - const blobKey = fullObj.zarrState.ncBlobKey - LoadNCBlob(blobKey).then(cache =>{ + if (fullObj.zarrState.useNC){ // If NC must load file beforehand + const blobKey = fullObj.zarrState.blobKey + loadFile(blobKey).then(cache =>{ //@ts-ignore cache is what we want - const file = cache.file + const file = cache.blob as File loadNetCDF(file, file.name).then(() => { useZarrStore.setState(fullObj.zarrState); useGlobalStore.setState(fullObj.globalState); diff --git a/src/components/plots/AxisLines.tsx b/src/components/plots/AxisLines.tsx index b21196bb..86d9a67d 100644 --- a/src/components/plots/AxisLines.tsx +++ b/src/components/plots/AxisLines.tsx @@ -114,9 +114,9 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const zDimScale = zResolution/(zResolution-1) const zValDelta = 1/(zResolution-1) - const xTitleOffset = useMemo(() => (dimNames[shapeLength - 1].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); - const yTitleOffset = useMemo(() => (dimNames[shapeLength - 2].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); - const zTitleOffset = useMemo(() => (dimNames[shapeLength - 3].length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); + const xTitleOffset = useMemo(() => (dimNames[shapeLength - 1]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); + const yTitleOffset = useMemo(() => (dimNames[shapeLength - 2]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); + const zTitleOffset = useMemo(() => (dimNames[shapeLength - 3]?.length * AXIS_CONSTANTS.TITLE_FONT_SIZE_FACTOR / 2 + 0.1) * globalScale, [dimNames, globalScale]); return ( diff --git a/src/components/ui/MainPanel/LocalNetCDF.tsx b/src/components/ui/MainPanel/LocalNetCDF.tsx index 8f386eef..bce758f7 100644 --- a/src/components/ui/MainPanel/LocalNetCDF.tsx +++ b/src/components/ui/MainPanel/LocalNetCDF.tsx @@ -3,7 +3,7 @@ import React, {ChangeEvent, useState} from 'react' import { Input } from '../input' import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { loadNetCDF, NETCDF_EXT_REGEX } from '@/utils/loadNetCDF'; - +import { saveFile } from '@/utils/IndexDB'; import { Alert, AlertDescription, @@ -19,15 +19,6 @@ interface LocalNCType { const DB_NAME = 'browzarr-files'; const STORE = 'blobs'; -export function openDB(): Promise { // This will store File Blobs on disk for re-opening NetCDFs from searchParams. - return new Promise((res, rej) => { - const req = indexedDB.open(DB_NAME, 1); - req.onupgradeneeded = () => req.result.createObjectStore(STORE); - req.onsuccess = () => res(req.result); - req.onerror = () => rej(req.error); - }); -} - const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { const {setStatus } = useGlobalStore.getState() // const {ncModule} = useZarrStore.getState() @@ -44,17 +35,10 @@ const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { } try { await loadNetCDF(file, file.name); - const db = await openDB(); - const key = `local_${file.name}` - new Promise((res, rej) => { - const tx = db.transaction(STORE, 'readwrite'); - tx.objectStore(STORE).put({ file, key }, key); - tx.oncomplete = () => { - res(key); - useZarrStore.setState({ncBlobKey:key}) - }; - tx.onerror = () => rej(tx.error); - }); + const blobKey = `local_${file.name}` + await saveFile(file, file.name) + useZarrStore.setState({blobKey}) + console.log('Set Key?') setOpenVariables(true) } catch (e) { setError(`Failed to load file: ${e instanceof Error ? e.message : String(e)}`); diff --git a/src/components/ui/MainPanel/LocalZarr.tsx b/src/components/ui/MainPanel/LocalZarr.tsx index f5404046..50c37555 100644 --- a/src/components/ui/MainPanel/LocalZarr.tsx +++ b/src/components/ui/MainPanel/LocalZarr.tsx @@ -5,6 +5,7 @@ import { useZarrStore } from '@/GlobalStates/ZarrStore'; import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { Input } from '../input'; import ZarrParser from '@/components/zarr/ZarrParser'; +import { saveFile } from '@/utils/IndexDB'; interface LocalZarrType { setShowLocal: React.Dispatch>; @@ -52,10 +53,12 @@ const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) store = await ZarrParser(files, customStore) } const gs = await zarr.open(store, {kind: 'group'}); + const blobKey = `local_${baseDir}` + //saveFile(gs, blobKey) setCurrentStore(gs); setShowLocal(false); setOpenVariables(true); - setInitStore(`local_${baseDir}`) + setInitStore(blobKey) setStatus(null) useZarrStore.setState({ useNC: false}) } catch (error) { diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx index 693480e3..a13c0ace 100644 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -87,7 +87,7 @@ const zarrValues = [ 'icechunkOptions', 'fetchOptions', 'fetchKey', - 'ncBlobKey' + 'blobKey' ] @@ -101,7 +101,7 @@ export const ParameterExport = () => { zarrState: pick(useZarrStore.getState(), zarrValues), } const jString = JSON.stringify(fullObj, (_, v) => typeof v === 'bigint' ? v.toString() : v) - const params = `https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` + const params = `http://localhost:3000/?data=${encodeURIComponent(jString)}` //`https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` return params } diff --git a/src/utils/IndexDB.ts b/src/utils/IndexDB.ts new file mode 100644 index 00000000..d9e45268 --- /dev/null +++ b/src/utils/IndexDB.ts @@ -0,0 +1,33 @@ +const DB_NAME = 'browzarr-files'; +const STORE = 'blobs'; + + +export function openDB(): Promise { + return new Promise((res, rej) => { + const req = indexedDB.open(DB_NAME, 1); + req.onupgradeneeded = () => req.result.createObjectStore(STORE); + req.onsuccess = () => res(req.result); + req.onerror = () => rej(req.error); + }); +} + +export async function saveFile(blob: Blob, key: string): Promise { + const db = await openDB(); + return new Promise((res, rej) => { + const tx = db.transaction(STORE, 'readwrite'); + tx.objectStore(STORE).put({ blob, key }, key); + tx.oncomplete = () => res(key); + tx.onerror = () => rej(tx.error); + }); +} + +export async function loadFile(key: string): Promise<{ blob: Blob; name: string } | null> { + const db = await openDB(); + console.log(key) + return new Promise((res, rej) => { + const tx = db.transaction(STORE, 'readonly'); + const req = tx.objectStore(STORE).get(key); + req.onsuccess = () => res(req.result ?? null); + req.onerror = () => rej(req.error); + }); +} \ No newline at end of file From e86ee3b49e3e93581fc1f7bf02a1eb37f0755467 Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 16:17:20 +0200 Subject: [PATCH 3/7] LoadZarrFunction --- src/components/StoreInitializer.tsx | 38 ++++++------ src/components/ui/MainPanel/LocalZarr.tsx | 70 ++++++++++++----------- src/components/zarr/ZarrParser.ts | 2 + src/utils/IndexDB.ts | 4 +- 4 files changed, 63 insertions(+), 51 deletions(-) diff --git a/src/components/StoreInitializer.tsx b/src/components/StoreInitializer.tsx index f84f6a5d..cc55823a 100644 --- a/src/components/StoreInitializer.tsx +++ b/src/components/StoreInitializer.tsx @@ -8,6 +8,7 @@ import { useShallow } from 'zustand/shallow'; import { isRemoteStore } from '@/utils/isRemoteStore'; import { loadNetCDF } from "@/utils/loadNetCDF"; import { loadFile } from "@/utils/IndexDB"; +import { LoadLocalZarr } from "./ui/MainPanel/LocalZarr"; function StoreInitializerInner() { const searchParams = useSearchParams(); @@ -22,23 +23,28 @@ function StoreInitializerInner() { const store = searchParams.get("store"); let data = searchParams.get("data") if (data){ - const fullObj = JSON.parse(data); - if (fullObj.zarrState.useNC){ // If NC must load file beforehand - const blobKey = fullObj.zarrState.blobKey - loadFile(blobKey).then(cache =>{ - //@ts-ignore cache is what we want - const file = cache.blob as File - loadNetCDF(file, file.name).then(() => { - useZarrStore.setState(fullObj.zarrState); - useGlobalStore.setState(fullObj.globalState); - usePlotStore.setState(fullObj.plotState); + const fullObj = JSON.parse(data); + if (fullObj.zarrState.blobKey){ // If NC local must load file beforehand + const blobKey = fullObj.zarrState.blobKey + const isNC = fullObj.zarrState.useNC + loadFile(blobKey).then(cache =>{ + if (!isNC){ + LoadLocalZarr(cache?.blob as File[]) + } else { + //@ts-ignore cache is what we want + const file = cache.blob as File + loadNetCDF(file, file.name).then(() => { + useZarrStore.setState(fullObj.zarrState); + useGlobalStore.setState(fullObj.globalState); + usePlotStore.setState(fullObj.plotState); + }) + } }) - }) - } else { - useZarrStore.setState(fullObj.zarrState) - useGlobalStore.setState(fullObj.globalState) - usePlotStore.setState(fullObj.plotState) - } + } else { + useZarrStore.setState(fullObj.zarrState) + useGlobalStore.setState(fullObj.globalState) + usePlotStore.setState(fullObj.plotState) + } } if (!store) { setStoreFromURL(false); diff --git a/src/components/ui/MainPanel/LocalZarr.tsx b/src/components/ui/MainPanel/LocalZarr.tsx index 50c37555..de03a3f3 100644 --- a/src/components/ui/MainPanel/LocalZarr.tsx +++ b/src/components/ui/MainPanel/LocalZarr.tsx @@ -13,18 +13,9 @@ interface LocalZarrType { setInitStore: (store: string) => void; } -const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) => { - const setCurrentStore = useZarrStore(state => state.setCurrentStore) - const {setStatus} = useGlobalStore.getState() - const handleFileSelect = async (event: ChangeEvent) => { - const files = event.target.files; - if (!files || files.length === 0) { - setStatus(null) - return; - } - // Create a Map to hold the Zarr store data +export async function LoadLocalZarr(files:File[]){ + // Create a Map to hold the Zarr store data const fileMap = new Map(); - // The base directory name will be the first part of the relative path const baseDir = files[0].webkitRelativePath.split('/')[0]; @@ -36,7 +27,6 @@ const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) fileMap.set('/' + relativePath, file); // Zarrita looks for a leading slash before variables. Need to add it back } } - // Create a custom zarrita store from the Map const customStore: zarr.AsyncReadable = { async get(key: string): Promise { @@ -49,39 +39,53 @@ const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) // Open the Zarr store using the custom store let store = await zarr.withMaybeConsolidatedMetadata(customStore); if (!('contents' in store)){ + console.log("Trying to parse?") // Metadata is missing. We will need to parse variables here. store = await ZarrParser(files, customStore) } + console.log("Here?") const gs = await zarr.open(store, {kind: 'group'}); const blobKey = `local_${baseDir}` - //saveFile(gs, blobKey) - setCurrentStore(gs); - setShowLocal(false); - setOpenVariables(true); - setInitStore(blobKey) - setStatus(null) - useZarrStore.setState({ useNC: false}) + useGlobalStore.setState({initStore:blobKey}) + useZarrStore.setState({ useNC: false, blobKey, currentStore:gs}) } catch (error) { - setStatus(null) + if (error instanceof Error) { console.log(`Error opening Zarr store: ${error.message}`); } else { console.log('An unknown error occurred when opening the Zarr store.'); } } - }; - return ( -
- -
- ) +} + +const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) => { + const {setStatus} = useGlobalStore.getState() + const handleFileSelect = async (event: ChangeEvent) => { + const files = event.target.files; + if (!files || files.length === 0) { + setStatus(null) + return; + } + const baseDir = files[0].webkitRelativePath.split('/')[0]; + LoadLocalZarr(Array.from(files)).then(()=>{ + saveFile(Array.from(files), `local_${baseDir}`) + setShowLocal(false); + setOpenVariables(true); + setStatus(null) + }) + }; + return ( +
+ +
+ ) } export default LocalZarr diff --git a/src/components/zarr/ZarrParser.ts b/src/components/zarr/ZarrParser.ts index 74b47475..a8cdef0d 100644 --- a/src/components/zarr/ZarrParser.ts +++ b/src/components/zarr/ZarrParser.ts @@ -11,6 +11,7 @@ function is_v3(meta: any) { return "zarr_format" in meta && meta.zarr_format === 3; } async function ZarrParser(files: any, store: any){ + console.log(store) const fileCount = files.length; const vars = [] const metadata: { [key: string]: any } = {} @@ -26,6 +27,7 @@ async function ZarrParser(files: any, store: any){ } for (const variable of vars){ const decoded = await store.get(variable) + console.log(decoded) metadata[variable.slice(1)] = jsonDecodeObject(decoded) } const v2_meta = {metadata, zarr_consolidated_format: 1} diff --git a/src/utils/IndexDB.ts b/src/utils/IndexDB.ts index d9e45268..51ea99a7 100644 --- a/src/utils/IndexDB.ts +++ b/src/utils/IndexDB.ts @@ -11,7 +11,7 @@ export function openDB(): Promise { }); } -export async function saveFile(blob: Blob, key: string): Promise { +export async function saveFile(blob: any, key: string): Promise { const db = await openDB(); return new Promise((res, rej) => { const tx = db.transaction(STORE, 'readwrite'); @@ -21,7 +21,7 @@ export async function saveFile(blob: Blob, key: string): Promise { }); } -export async function loadFile(key: string): Promise<{ blob: Blob; name: string } | null> { +export async function loadFile(key: string): Promise<{ blob: any; name: string } | null> { const db = await openDB(); console.log(key) return new Promise((res, rej) => { From cb6d4fe7ccab009148ed06dfc877507c5e0dd181 Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 17:23:33 +0200 Subject: [PATCH 4/7] Good enough --- src/GlobalStates/PlotStore.ts | 6 +++++ src/components/plots/Plot.tsx | 8 +++++++ src/components/ui/MainPanel/LocalContent.tsx | 1 - src/components/ui/MainPanel/LocalZarr.tsx | 24 +++++++++----------- src/components/ui/NavBar/ParameterExport.tsx | 24 ++++++++++++++------ src/utils/IndexDB.ts | 1 - 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 5f0e34f7..1b87962e 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -1,5 +1,6 @@ import { create } from "zustand"; import * as THREE from "three"; +import { cameraPosition } from "three/src/nodes/TSL.js"; type PlotState ={ plotType: string; @@ -64,6 +65,7 @@ type PlotState ={ maskValue: number; cameraPosition: THREE.Vector3; disablePointScale: boolean; + camera: THREE.Camera | undefined; setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -119,8 +121,10 @@ type PlotState ={ setUseOrtho: (useOrtho: boolean) => void; setFillValue: (fillValue: number | undefined) => void; setCameraPosition: (cameraPosition: THREE.Vector3) => void; + } + export const usePlotStore = create((set, get) => ({ plotType: "volume", pointSize: 5, @@ -185,6 +189,7 @@ export const usePlotStore = create((set, get) => ({ borderWidth: 0.05, cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, + camera: undefined, setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), @@ -242,4 +247,5 @@ export const usePlotStore = create((set, get) => ({ setUseOrtho: (useOrtho) => set({ useOrtho }), setFillValue: (fillValue) => set({ fillValue }), setCameraPosition: (cameraPosition) => set({ cameraPosition }), + })) \ No newline at end of file diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index d54e6ab3..b757a14a 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -39,6 +39,7 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ const hasMounted = useRef(false); const cameraRef = useRef(null) const {set, camera, size} = useThree() + // Reset Camera Position and Target useEffect(()=>{ if (!hasMounted.current) { @@ -81,6 +82,7 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[resetCamera, isFlat]) + // ---- Switch from Perspective to Orthographic ---- // useEffect(()=>{ if (hasMounted.current){ let newCamera; @@ -117,6 +119,7 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[useOrtho]) + // ---- Move camera to position ---- // useEffect(()=>{ const cam = cameraRef.current const controls = orbitRef.current @@ -136,6 +139,11 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ } },[cameraPosition]) + // ---- Camera Ref for state saves ---- // + useEffect(()=>{ + usePlotStore.setState({camera}) + },[camera]) + return ( {})} setOpenVariables={onOpenDescription} - setInitStore={setInitStore} /> )} diff --git a/src/components/ui/MainPanel/LocalZarr.tsx b/src/components/ui/MainPanel/LocalZarr.tsx index de03a3f3..216ecbd9 100644 --- a/src/components/ui/MainPanel/LocalZarr.tsx +++ b/src/components/ui/MainPanel/LocalZarr.tsx @@ -10,7 +10,6 @@ import { saveFile } from '@/utils/IndexDB'; interface LocalZarrType { setShowLocal: React.Dispatch>; setOpenVariables: (open: boolean) => void; - setInitStore: (store: string) => void; } export async function LoadLocalZarr(files:File[]){ @@ -43,7 +42,6 @@ export async function LoadLocalZarr(files:File[]){ // Metadata is missing. We will need to parse variables here. store = await ZarrParser(files, customStore) } - console.log("Here?") const gs = await zarr.open(store, {kind: 'group'}); const blobKey = `local_${baseDir}` useGlobalStore.setState({initStore:blobKey}) @@ -58,7 +56,7 @@ export async function LoadLocalZarr(files:File[]){ } } -const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) => { +const LocalZarr = ({setShowLocal, setOpenVariables}:LocalZarrType) => { const {setStatus} = useGlobalStore.getState() const handleFileSelect = async (event: ChangeEvent) => { const files = event.target.files; @@ -68,21 +66,21 @@ const LocalZarr = ({setShowLocal, setOpenVariables, setInitStore}:LocalZarrType) } const baseDir = files[0].webkitRelativePath.split('/')[0]; LoadLocalZarr(Array.from(files)).then(()=>{ - saveFile(Array.from(files), `local_${baseDir}`) - setShowLocal(false); - setOpenVariables(true); - setStatus(null) + saveFile(Array.from(files), `local_${baseDir}`) + setShowLocal(false); + setOpenVariables(true); + setStatus(null) }) }; return (
) diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx index a13c0ace..245c02e6 100644 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -71,7 +71,6 @@ const plotValues = [ 'borderWidth', 'cameraPosition', 'disablePointScale', - ] const zarrValues = [ @@ -95,6 +94,8 @@ export const ParameterExport = () => { const [copied, setCopied] = useState(false); function generateURL(){ + const {camera} = usePlotStore.getState() + usePlotStore.setState({cameraPosition:camera?.position}) const fullObj = { globalState: pick(useGlobalStore.getState(), globalValues), plotState: pick(usePlotStore.getState(), plotValues), @@ -109,7 +110,7 @@ export const ParameterExport = () => { const url = generateURL() await navigator.clipboard.writeText(url); setCopied(true); - setTimeout(() => setCopied(false), 1500); + setTimeout(() => setCopied(false), 1500); //Use for a pop-up that fades away }; return ( @@ -119,24 +120,33 @@ export const ParameterExport = () => { variant="ghost" size="icon" className="cursor-pointer" - onClick={copyToClipboard} > - +
- - +
+ Copied! +
diff --git a/src/utils/IndexDB.ts b/src/utils/IndexDB.ts index 51ea99a7..e48ad0ca 100644 --- a/src/utils/IndexDB.ts +++ b/src/utils/IndexDB.ts @@ -23,7 +23,6 @@ export async function saveFile(blob: any, key: string): Promise { export async function loadFile(key: string): Promise<{ blob: any; name: string } | null> { const db = await openDB(); - console.log(key) return new Promise((res, rej) => { const tx = db.transaction(STORE, 'readonly'); const req = tx.objectStore(STORE).get(key); From 5759f69111cdf18fc99c552819b218d2dff2f8cb Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 17:26:50 +0200 Subject: [PATCH 5/7] last comment --- src/components/ui/NavBar/ParameterExport.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx index 245c02e6..49d6128f 100644 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -95,7 +95,7 @@ export const ParameterExport = () => { function generateURL(){ const {camera} = usePlotStore.getState() - usePlotStore.setState({cameraPosition:camera?.position}) + usePlotStore.setState({cameraPosition:camera?.position}) // Set Camera position first to copy visual state const fullObj = { globalState: pick(useGlobalStore.getState(), globalValues), plotState: pick(usePlotStore.getState(), plotValues), From d82cf0fa04b9552f489348795ff99123e8f36ecc Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 17:28:00 +0200 Subject: [PATCH 6/7] locahost --> browzarr --- src/components/ui/NavBar/ParameterExport.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx index 49d6128f..43e6a4f7 100644 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -102,7 +102,7 @@ export const ParameterExport = () => { zarrState: pick(useZarrStore.getState(), zarrValues), } const jString = JSON.stringify(fullObj, (_, v) => typeof v === 'bigint' ? v.toString() : v) - const params = `http://localhost:3000/?data=${encodeURIComponent(jString)}` //`https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` + const params = `https://browzarr.io/latest/?data=${encodeURIComponent(jString)}` return params } From de24a8c4c616e8cbbcca7a742287a29ecafb61df Mon Sep 17 00:00:00 2001 From: Jeran Date: Fri, 19 Jun 2026 17:55:17 +0200 Subject: [PATCH 7/7] Gemini suggestions --- src/GlobalStates/PlotStore.ts | 1 - src/components/StoreInitializer.tsx | 46 +++++++++++--------- src/components/plots/Plot.tsx | 3 ++ src/components/ui/MainPanel/LocalNetCDF.tsx | 5 +-- src/components/ui/NavBar/ParameterExport.tsx | 2 +- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 1b87962e..39219a62 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -1,6 +1,5 @@ import { create } from "zustand"; import * as THREE from "three"; -import { cameraPosition } from "three/src/nodes/TSL.js"; type PlotState ={ plotType: string; diff --git a/src/components/StoreInitializer.tsx b/src/components/StoreInitializer.tsx index cc55823a..3cf178fb 100644 --- a/src/components/StoreInitializer.tsx +++ b/src/components/StoreInitializer.tsx @@ -23,27 +23,31 @@ function StoreInitializerInner() { const store = searchParams.get("store"); let data = searchParams.get("data") if (data){ - const fullObj = JSON.parse(data); - if (fullObj.zarrState.blobKey){ // If NC local must load file beforehand - const blobKey = fullObj.zarrState.blobKey - const isNC = fullObj.zarrState.useNC - loadFile(blobKey).then(cache =>{ - if (!isNC){ - LoadLocalZarr(cache?.blob as File[]) - } else { - //@ts-ignore cache is what we want - const file = cache.blob as File - loadNetCDF(file, file.name).then(() => { - useZarrStore.setState(fullObj.zarrState); - useGlobalStore.setState(fullObj.globalState); - usePlotStore.setState(fullObj.plotState); - }) - } - }) - } else { - useZarrStore.setState(fullObj.zarrState) - useGlobalStore.setState(fullObj.globalState) - usePlotStore.setState(fullObj.plotState) + try{ + const fullObj = JSON.parse(data); + if (fullObj.zarrState.blobKey){ // If NC local must load file beforehand + const blobKey = fullObj.zarrState.blobKey + const isNC = fullObj.zarrState.useNC + loadFile(blobKey).then(cache =>{ + if (!isNC){ + LoadLocalZarr(cache?.blob as File[]) + } else { + //@ts-ignore cache is what we want + const file = cache.blob as File + loadNetCDF(file, file.name).then(() => { + useZarrStore.setState(fullObj.zarrState); + useGlobalStore.setState(fullObj.globalState); + usePlotStore.setState(fullObj.plotState); + }) + } + }) + } else { + useZarrStore.setState(fullObj.zarrState) + useGlobalStore.setState(fullObj.globalState) + usePlotStore.setState(fullObj.plotState) + } + } catch { + console.error('Something Failed :/') } } if (!store) { diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index b757a14a..344d8ef9 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -142,6 +142,9 @@ const Orbiter = ({isFlat} : {isFlat : boolean}) =>{ // ---- Camera Ref for state saves ---- // useEffect(()=>{ usePlotStore.setState({camera}) + return () => { + usePlotStore.setState({camera: undefined}) + } },[camera]) return ( diff --git a/src/components/ui/MainPanel/LocalNetCDF.tsx b/src/components/ui/MainPanel/LocalNetCDF.tsx index bce758f7..fa9ee7d9 100644 --- a/src/components/ui/MainPanel/LocalNetCDF.tsx +++ b/src/components/ui/MainPanel/LocalNetCDF.tsx @@ -16,9 +16,6 @@ interface LocalNCType { setOpenVariables: (open: boolean) => void; } -const DB_NAME = 'browzarr-files'; -const STORE = 'blobs'; - const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { const {setStatus } = useGlobalStore.getState() // const {ncModule} = useZarrStore.getState() @@ -36,7 +33,7 @@ const LocalNetCDF = ({ setOpenVariables}:LocalNCType) => { try { await loadNetCDF(file, file.name); const blobKey = `local_${file.name}` - await saveFile(file, file.name) + await saveFile(file, blobKey) useZarrStore.setState({blobKey}) console.log('Set Key?') setOpenVariables(true) diff --git a/src/components/ui/NavBar/ParameterExport.tsx b/src/components/ui/NavBar/ParameterExport.tsx index 43e6a4f7..446206fc 100644 --- a/src/components/ui/NavBar/ParameterExport.tsx +++ b/src/components/ui/NavBar/ParameterExport.tsx @@ -19,7 +19,7 @@ const globalValues = [ ] const plotValues = [ - 'PlotType', + 'plotType', 'pointSize', 'scalePoints', 'scaleIntensity',