From 1cc8ba7b1cc1499fd0670e97326fd9772855d027 Mon Sep 17 00:00:00 2001 From: Trevor Manz Date: Wed, 24 Jul 2024 22:06:39 -0400 Subject: [PATCH] Move viewState into a hook --- src/components/Viewer.tsx | 24 +++++++++--------------- src/hooks.ts | 10 +++++++++- src/index.tsx | 20 +++++++++++++------- src/state.ts | 17 ----------------- 4 files changed, 31 insertions(+), 40 deletions(-) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index f2fef3f..5196b68 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -1,12 +1,11 @@ import DeckGL from "deck.gl"; import { type Layer, OrthographicView } from "deck.gl"; -import { type WritableAtom, useAtom } from "jotai"; import { useAtomValue } from "jotai"; import * as React from "react"; +import { useViewState } from "@/hooks"; import type { LayerProps } from "@deck.gl/core/lib/layer"; import type { ZarrPixelSource } from "../ZarrPixelSource"; -import type { ViewState } from "../state"; import { layerAtoms } from "../state"; import { fitBounds, isInterleaved } from "../utils"; @@ -28,11 +27,8 @@ function getLayerSize(props: Data) { return { height, width, maxZoom }; } -function WrappedViewStateDeck(props: { - layers: Array; - viewStateAtom: WritableAtom; -}) { - const [viewState, setViewState] = useAtom(props.viewStateAtom); +function WrappedViewStateDeck(props: { layers: Array }) { + const [viewState, setViewState] = useViewState(); const deckRef = React.useRef(null); const firstLayerProps = props.layers[0]?.props; @@ -46,11 +42,6 @@ function WrappedViewStateDeck(props: { setViewState(bounds); } - // Enables screenshots of the canvas: https://github.com/visgl/deck.gl/issues/2200 - const glOptions: WebGLContextAttributes = { - preserveDrawingBuffer: true, - }; - return ( setViewState(e.viewState)} views={[new OrthographicView({ id: "ortho", controller: true })]} - glOptions={glOptions} + glOptions={{ + // Enables screenshots of the canvas: https://github.com/visgl/deck.gl/issues/2200 + preserveDrawingBuffer: true, + }} /> ); } -function Viewer({ viewStateAtom }: { viewStateAtom: WritableAtom }) { +function Viewer() { const layerConstructors = useAtomValue(layerAtoms); // @ts-expect-error - Viv types are giving up an issue const layers: Array = layerConstructors.map((layer) => { return !layer.on ? null : new layer.Layer(layer.layerProps); }); - return ; + return ; } export default Viewer; diff --git a/src/hooks.ts b/src/hooks.ts index 1ba431e..8099770 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,13 +1,15 @@ import { type PrimitiveAtom, useAtom, useAtomValue } from "jotai"; import * as React from "react"; -import { type SourceData, layerFamilyAtom } from "./state"; +import { type SourceData, type ViewState, layerFamilyAtom } from "./state"; import { assert } from "./utils"; type WithId = { id: string } & T; type SourceDataAtom = PrimitiveAtom>; +type ViewStateAtom = PrimitiveAtom; export const LayerContext = React.createContext(null); +export const ViewStateContext = React.createContext(null); function useSourceAtom(): SourceDataAtom { const sourceAtom = React.useContext(LayerContext); @@ -40,3 +42,9 @@ export function useLayerValue() { const layerAtom = useLayerAtom(); return useAtomValue(layerAtom); } + +export function useViewState() { + const viewStateAtom = React.useContext(ViewStateContext); + assert(viewStateAtom !== null, "useViewState must be used within a ViewStateContext.Provider"); + return useAtom(viewStateAtom); +} diff --git a/src/index.tsx b/src/index.tsx index d221194..f899439 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,12 +6,13 @@ import ReactDOM from "react-dom/client"; import Menu from "./components/Menu"; import Viewer from "./components/Viewer"; import "./codecs/register"; -import { type ImageLayerConfig, type ViewState, addImageAtom, atomWithEffect } from "./state"; +import { type ImageLayerConfig, type ViewState, addImageAtom } from "./state"; import { defer, typedEmitter } from "./utils"; export { version } from "../package.json"; import "./index.css"; +import { ViewStateContext } from "./hooks"; type Events = { viewStateChange: ViewState; @@ -29,9 +30,14 @@ export interface VizarrViewer { export function createViewer(element: HTMLElement, options: { menuOpen?: boolean } = {}): Promise { const ref = React.createRef(); const emitter = typedEmitter(); - const viewStateAtom = atomWithEffect( - atom(undefined), - ({ zoom, target }) => emitter.emit("viewStateChange", { zoom, target }), + const viewStateAtom = atom(undefined); + const viewStateAtomWithUrlSync = atom( + (get) => get(viewStateAtom), + (get, set, update) => { + const next = typeof update === "function" ? update(get(viewStateAtom)) : update; + next && emitter.emit("viewStateChange", { target: next.target, zoom: next.zoom }); + set(viewStateAtom, next); + }, ); const { promise, resolve } = defer(); @@ -54,10 +60,10 @@ export function createViewer(element: HTMLElement, options: { menuOpen?: boolean } }, []); return ( - <> + - - + + ); } let root = ReactDOM.createRoot(element); diff --git a/src/state.ts b/src/state.ts index 008adea..fee50b2 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,5 +1,4 @@ import type { ImageLayer, MultiscaleImageLayer } from "@hms-dbmi/viv"; -import type { PrimitiveAtom, WritableAtom } from "jotai"; import { atom } from "jotai"; import { atomFamily, splitAtom, waitForAll } from "jotai/utils"; import type { Matrix4 } from "math.gl"; @@ -14,22 +13,6 @@ export interface ViewState { target: [number, number]; } -export function atomWithEffect = void>( - baseAtom: WritableAtom Update), Result>, - callback: (data: Update) => void, -) { - const derivedAtom: typeof baseAtom = atom( - (get) => get(baseAtom), - (get, set, update) => { - const next = typeof update === "function" ? update(get(baseAtom)) : update; - const result = set(baseAtom, next); - callback(next); - return result; - }, - ); - return derivedAtom; -} - interface BaseConfig { source: string | Readable; axis_labels?: string[];