From 4a30709228bf847a630a1c8c2a286ecfd1aaae73 Mon Sep 17 00:00:00 2001 From: Andy Sweet Date: Fri, 23 Feb 2024 16:10:32 -0800 Subject: [PATCH] Basic state captured in URL with jotai --- package-lock.json | 30 ++++++++++++++++++++++++++++++ package.json | 2 ++ src/scene.tsx | 31 +++++++++++++++++++++++++++---- vite.config.ts | 4 +++- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55fd78c4..e38d972b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@mui/lab": "^5.0.0-alpha.160", "@mui/material": "^5.15.4", "czifui": "^14.5.0", + "jotai": "^2.6.5", + "jotai-location": "^0.5.3", "playwright": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -4807,6 +4809,34 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jotai": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.6.5.tgz", + "integrity": "sha512-T+yvY42GXKomvJYqs+NeTH0da9Z1tQ3Qk3zppPHIWnvmOBKpN6Qd4j8h/oo9dwxs3w/Z5r6Kk0I8h6z5orQ/HQ==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/jotai-location": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/jotai-location/-/jotai-location-0.5.3.tgz", + "integrity": "sha512-re7rBf0AYX7f8BNkYIkffuHWg7McZ7wJDf8UugEgV1HjsPWmSAkzN/9ZF/Mk3pClBeAZu/JzWUzA+E+0nA7MLQ==", + "peerDependencies": { + "jotai": ">=1.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index d807f044..76aeaf49 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "@mui/lab": "^5.0.0-alpha.160", "@mui/material": "^5.15.4", "czifui": "^14.5.0", + "jotai": "^2.6.5", + "jotai-location": "^0.5.3", "playwright": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/scene.tsx b/src/scene.tsx index bc368e06..6da172cd 100644 --- a/src/scene.tsx +++ b/src/scene.tsx @@ -1,4 +1,6 @@ import { useEffect, useRef, useState } from "react"; +import { useAtom } from 'jotai'; +import { atomWithHash } from 'jotai-location'; import { Button, InputSlider, InputText, InputToggle } from "@czi-sds/components"; import { PointCanvas } from "./PointCanvas"; import { TrackManager, loadTrackManager } from "./TrackManager"; @@ -16,16 +18,35 @@ interface SceneProps { renderHeight?: number; } +// Does atob/btoa only handle ASCII? Is that a problem for us? +// I don't think so because URLs can only contain ASCII? +function jsonDecodeBase64(data: string): string { + return JSON.parse(atob(data)); +} + +function jsonEncodeBase64(data: any): string { + return btoa(JSON.stringify(data)); +} + +function atomWithEncodedHash(key: string, initialValue: any) { + return atomWithHash(key, initialValue, {serialize: jsonEncodeBase64, deserialize: jsonDecodeBase64}); +} + +const dataUrlAtom = atomWithHash('dataUrl', DEFAULT_ZARR_URL); +const curTimeAtom = atomWithHash('curTime', 0); +const autoRotateAtom = atomWithHash('autoRotate', false); +const playingAtom = atomWithHash('playing', false); + export default function Scene(props: SceneProps) { const renderWidth = props.renderWidth || 800; const renderHeight = props.renderHeight || 600; const [trackManager, setTrackManager] = useState(); - const [dataUrl, setDataUrl] = useState(DEFAULT_ZARR_URL); + const [dataUrl, setDataUrl] = useAtom(dataUrlAtom); const [numTimes, setNumTimes] = useState(0); - const [curTime, setCurTime] = useState(0); - const [autoRotate, setAutoRotate] = useState(false); - const [playing, setPlaying] = useState(false); + const [curTime, setCurTime] = useAtom(curTimeAtom); + const [autoRotate, setAutoRotate] = useAtom(autoRotateAtom); + const [playing, setPlaying] = useAtom(playingAtom); // Use references here for two things: // * manage objects that should never change, even when the component re-renders @@ -176,6 +197,7 @@ export default function Scene(props: SceneProps) { { setAutoRotate((e.target as HTMLInputElement).checked); @@ -184,6 +206,7 @@ export default function Scene(props: SceneProps) { { setPlaying((e.target as HTMLInputElement).checked); diff --git a/vite.config.ts b/vite.config.ts index 1201335e..11546bf1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,9 +2,11 @@ /// import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import jotaiDebugLabel from 'jotai/babel/plugin-debug-label' +import jotaiReactRefresh from 'jotai/babel/plugin-react-refresh' export default defineConfig({ - plugins: [react()], + plugins: [react({ babel: { plugins: [jotaiDebugLabel, jotaiReactRefresh] } })], test: { environment: "jsdom", browser: {