From ea6323f41dab5b13494064fa49bdb7c3b8ea1e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1rbara=20Chaves?= Date: Tue, 27 Feb 2024 18:25:04 +0100 Subject: [PATCH] Replace recoil for jotai and nuqs (#50) * Replace recoil for jotai and nuqs * Fix default interactive maps value * Add apng layer fix --- client/package.json | 4 +- client/src/app/(landing)/layout-providers.tsx | 28 +-- client/src/app/(static)/layout-providers.tsx | 4 +- .../map/layers/animated-tile/index.ts | 2 +- .../map/legend/item-types/switch/index.tsx | 8 +- .../map/legend/item-types/timeline/index.tsx | 10 +- .../src/containers/home/categories/item.tsx | 10 +- .../containers/home/datasets/layers/item.tsx | 8 +- client/src/containers/home/filters/index.tsx | 8 +- client/src/containers/home/filters/item.tsx | 28 +-- client/src/containers/home/header/index.tsx | 4 +- client/src/containers/home/index.tsx | 17 +- client/src/containers/home/sidebar/index.tsx | 6 +- client/src/containers/home/sync-store.tsx | 7 - client/src/containers/map/index.tsx | 16 +- .../containers/map/layer-manager/index.tsx | 8 +- .../src/containers/map/layer-manager/item.tsx | 25 ++- client/src/containers/map/legend/index.tsx | 14 +- client/src/containers/map/legend/item.tsx | 6 +- .../map/markers/home-markers/index.tsx | 6 +- .../map/markers/story-markers/index.tsx | 6 +- client/src/containers/map/popup/index.tsx | 10 +- client/src/containers/map/popup/item.tsx | 8 +- .../map/settings/basemaps/item/index.tsx | 8 +- .../map/settings/boundaries/index.tsx | 8 +- .../containers/map/settings/labels/index.tsx | 8 +- .../containers/map/settings/manager/index.tsx | 6 +- .../containers/map/settings/roads/index.tsx | 8 +- client/src/containers/story/index.tsx | 30 +-- .../steps/controller/controller-item.tsx | 6 +- client/src/containers/story/steps/index.tsx | 6 +- .../story/steps/layouts/outro-step.tsx | 6 +- client/src/lib/recoil/devtools.tsx | 18 -- client/src/lib/recoil/index.tsx | 31 --- client/src/lib/recoil/useSyncURLNext.ts | 62 ------ client/src/lib/scroll/index.tsx | 5 +- client/src/store/home.ts | 79 ++------ client/src/store/index.ts | 176 ------------------ client/src/store/map.ts | 57 ++++++ client/src/store/stories.ts | 21 +-- yarn.lock | 79 ++++---- 41 files changed, 262 insertions(+), 595 deletions(-) delete mode 100644 client/src/containers/home/sync-store.tsx delete mode 100644 client/src/lib/recoil/devtools.tsx delete mode 100644 client/src/lib/recoil/index.tsx delete mode 100644 client/src/lib/recoil/useSyncURLNext.ts delete mode 100644 client/src/store/index.ts create mode 100644 client/src/store/map.ts diff --git a/client/package.json b/client/package.json index ca19dd5..8c3a58d 100644 --- a/client/package.json +++ b/client/package.json @@ -50,9 +50,11 @@ "eslint": "8.42.0", "eslint-config-next": "13.4.5", "framer-motion": "^10.16.4", + "jotai": "^2.6.4", "lucide-react": "^0.252.0", "mapbox-gl": "^3.0.1", "next": "13.4.5", + "nuqs": "^1.16.1", "postcss": "8.4.24", "react": "18.2.0", "react-chartjs-2": "^5.2.0", @@ -60,8 +62,6 @@ "react-hook-form": "^7.45.0", "react-map-gl": "7.1.5", "react-markdown": "8.0.7", - "recoil": "^0.7.7", - "recoil-sync": "^0.2.0", "rooks": "7.14.1", "tailwind-merge": "^1.13.2", "tailwindcss": "3.3.2", diff --git a/client/src/app/(landing)/layout-providers.tsx b/client/src/app/(landing)/layout-providers.tsx index 9d2324b..df6eb9a 100644 --- a/client/src/app/(landing)/layout-providers.tsx +++ b/client/src/app/(landing)/layout-providers.tsx @@ -1,38 +1,16 @@ 'use client'; -import { PropsWithChildren, useCallback } from 'react'; +import { PropsWithChildren } from 'react'; import { MapProvider } from 'react-map-gl'; -import { RecoilRoot } from 'recoil'; - -import { RecoilURLSyncNext } from '@/lib/recoil'; -import type { Deserialize, Serialize } from '@/lib/recoil'; -import RecoilDevTools from '@/lib/recoil/devtools'; +import { Provider } from 'jotai'; export default function Providers({ children }: PropsWithChildren) { - const serialize: Serialize = useCallback((x) => { - return x === undefined ? '' : JSON.stringify(x); - }, []); - - //Demo of custom deserialization - const deserialize: Deserialize = useCallback((x: string) => { - return JSON.parse(x); - }, []); - return ( <> - - - - {children} - - + {children} ); diff --git a/client/src/app/(static)/layout-providers.tsx b/client/src/app/(static)/layout-providers.tsx index dd2e3c8..452950b 100644 --- a/client/src/app/(static)/layout-providers.tsx +++ b/client/src/app/(static)/layout-providers.tsx @@ -2,12 +2,12 @@ import { PropsWithChildren } from 'react'; -import { RecoilRoot } from 'recoil'; +import { Provider } from 'jotai'; export default function Providers({ children }: PropsWithChildren) { return ( <> - {children} + {children} ); } diff --git a/client/src/components/map/layers/animated-tile/index.ts b/client/src/components/map/layers/animated-tile/index.ts index d707ce2..ff20812 100644 --- a/client/src/components/map/layers/animated-tile/index.ts +++ b/client/src/components/map/layers/animated-tile/index.ts @@ -75,7 +75,7 @@ export class AnimatedTile { image: FRAME.bitmapData, bounds: [west, south, east, north], getPolygonOffset: () => { - return [0, 20000]; + return [0, 100000]; }, zoom, visible, diff --git a/client/src/components/map/legend/item-types/switch/index.tsx b/client/src/components/map/legend/item-types/switch/index.tsx index dad03c7..e1d2883 100644 --- a/client/src/components/map/legend/item-types/switch/index.tsx +++ b/client/src/components/map/legend/item-types/switch/index.tsx @@ -1,18 +1,18 @@ import { useCallback, useMemo } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { layersSettingsAtom } from '@/store'; +import { layersSettingsAtom } from '@/store/map'; import { Switch } from '@/components/ui/switch'; import { LegendTypeSwitchProps } from '../../types'; const LegendTypeSwitch = ({ layerId, param, layerTitle }: LegendTypeSwitchProps) => { - const layersSettings = useRecoilValue(layersSettingsAtom); + const layersSettings = useAtomValue(layersSettingsAtom); const checked = useMemo(() => layersSettings[layerId]?.[param], [layerId, layersSettings, param]); - const setLayersSettings = useSetRecoilState(layersSettingsAtom); + const setLayersSettings = useSetAtom(layersSettingsAtom); const handleChangeVisibility = useCallback( (checked: boolean) => { diff --git a/client/src/components/map/legend/item-types/timeline/index.tsx b/client/src/components/map/legend/item-types/timeline/index.tsx index 7ee348e..19e903b 100644 --- a/client/src/components/map/legend/item-types/timeline/index.tsx +++ b/client/src/components/map/legend/item-types/timeline/index.tsx @@ -4,9 +4,9 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Root, Track, Thumb } from '@radix-ui/react-slider'; import { PauseIcon, PlayIcon } from 'lucide-react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { layersSettingsAtom, timelineAtom } from '@/store'; +import { layersSettingsAtom, timelineAtom } from '@/store/map'; import { LegendTypeTimelineProps } from '@/components/map/legend/types'; import { Button } from '@/components/ui/button'; @@ -24,12 +24,12 @@ export const LegendTypeTimeline: React.FC = ({ const textMarginX = 16; const intervalRef = useRef(); - const setLayersSettings = useSetRecoilState(layersSettingsAtom); + const setLayersSettings = useSetAtom(layersSettingsAtom); const [isPlaying, setIsPlaying] = useState(false); - const timelines = useRecoilValue(timelineAtom); - const setTimelines = useSetRecoilState(timelineAtom); + const timelines = useAtomValue(timelineAtom); + const setTimelines = useSetAtom(timelineAtom); const frame = useMemo(() => timelines[id]?.frame || 0, [id, timelines]); diff --git a/client/src/containers/home/categories/item.tsx b/client/src/containers/home/categories/item.tsx index c9d5c4d..11c9c7f 100644 --- a/client/src/containers/home/categories/item.tsx +++ b/client/src/containers/home/categories/item.tsx @@ -1,8 +1,6 @@ -import { useRecoilState, useResetRecoilState } from 'recoil'; - import { cn } from '@/lib/classnames'; -import { categoryAtom } from '@/store/home'; +import { useSyncCategory } from '@/store/home'; import { Category } from '@/types/generated/strapi.schemas'; @@ -13,12 +11,12 @@ import { TooltipTrigger, TooltipContent, Tooltip } from '@/components/ui/tooltip type CategoryProps = Pick; const Category = ({ name, slug }: CategoryProps) => { - const [category, setCategory] = useRecoilState(categoryAtom); - const resetCategory = useResetRecoilState(categoryAtom); + const [category, setCategory] = useSyncCategory(); const handleClick = (slug: string) => { if (category === slug) { - return resetCategory(); + setCategory(null); + return; } setCategory(slug); }; diff --git a/client/src/containers/home/datasets/layers/item.tsx b/client/src/containers/home/datasets/layers/item.tsx index b9bd0b3..52934f4 100644 --- a/client/src/containers/home/datasets/layers/item.tsx +++ b/client/src/containers/home/datasets/layers/item.tsx @@ -1,16 +1,16 @@ 'use client'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { layersAtom } from '@/store'; +import { layersAtom } from '@/store/map'; import { LayerListResponseDataItem } from '@/types/generated/strapi.schemas'; import { Switch } from '@/components/ui/switch'; export default function LayersItem({ id, attributes }: Required) { - const layers = useRecoilValue(layersAtom); - const setLayers = useSetRecoilState(layersAtom); + const layers = useAtomValue(layersAtom); + const setLayers = useSetAtom(layersAtom); const handleLayerChange = () => { if (!id) return; diff --git a/client/src/containers/home/filters/index.tsx b/client/src/containers/home/filters/index.tsx index 206f738..9165a28 100644 --- a/client/src/containers/home/filters/index.tsx +++ b/client/src/containers/home/filters/index.tsx @@ -1,9 +1,9 @@ +import { useAtom } from 'jotai'; import { XIcon } from 'lucide-react'; -import { useRecoilState } from 'recoil'; import { cn } from '@/lib/classnames'; -import { FilterName, filtersOpenAtom } from '@/store/home'; +import { filtersOpenAtom } from '@/store/home'; import { Button } from '@/components/ui/button'; @@ -11,7 +11,7 @@ import FilterItem from './item'; const filtersData: { title: string; - id: FilterName; + id: string; options: { name: string; id: string }[]; }[] = [ { @@ -48,7 +48,7 @@ const filtersData: { ]; export const Filters = () => { - const [isOpen, setIsOpen] = useRecoilState(filtersOpenAtom); + const [isOpen, setIsOpen] = useAtom(filtersOpenAtom); return (
{ - const [filter, setFilter] = useRecoilState(filterSelector(id)); - const resetFilter = useResetRecoilState(filterSelector(id)); + const [filters, setFilters] = useSyncFilters(); + + const filter = filters[id as keyof typeof filters]; + + const setFilter = (value: string[] | null) => setFilters({ ...filters, [id]: value }); const handleChangeFilter = (id: string) => { - setFilter((prev) => { - if (!prev.includes(id)) { - return [...prev, id]; - } - return prev.filter((item) => item !== id); - }); + if (!filter?.includes(id)) { + setFilter([...(filter || []), id]); + } else { + setFilter(filter?.filter((item) => item !== id) || null); + } }; return (

{title}

-
@@ -39,7 +39,7 @@ const FilterItem = ({ filter: { id, options, title } }: FilterItemProps) => { return ( handleChangeFilter(optionId)} label={name} diff --git a/client/src/containers/home/header/index.tsx b/client/src/containers/home/header/index.tsx index e2fb669..948395a 100644 --- a/client/src/containers/home/header/index.tsx +++ b/client/src/containers/home/header/index.tsx @@ -2,8 +2,8 @@ import Image from 'next/image'; +import { useAtom } from 'jotai'; import { FilterIcon } from 'lucide-react'; -import { useRecoilState } from 'recoil'; import { filtersOpenAtom } from '@/store/home'; @@ -11,7 +11,7 @@ import { Button } from '@/components/ui/button'; import GradientLine from '@/components/ui/gradient-line'; const Header = () => { - const [open, setOpen] = useRecoilState(filtersOpenAtom); + const [open, setOpen] = useAtom(filtersOpenAtom); const handleClickFilters = () => { setOpen(!open); diff --git a/client/src/containers/home/index.tsx b/client/src/containers/home/index.tsx index 2613929..9346fae 100644 --- a/client/src/containers/home/index.tsx +++ b/client/src/containers/home/index.tsx @@ -2,11 +2,10 @@ import { useEffect } from 'react'; -import { useResetRecoilState, useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; -import { layersAtom, tmpBboxAtom } from '@/store'; - -import { stepAtom } from '@/store/stories'; +import { layersAtom, tmpBboxAtom } from '@/store/map'; +import { useSyncStep } from '@/store/stories'; import { DEFAULT_MAP_BBOX, DEFAULT_MAP_STATE } from '@/constants/map'; @@ -22,9 +21,9 @@ import Header from './header'; import TopStories from './top-stories'; export default function Home() { - const setTmpBbox = useSetRecoilState(tmpBboxAtom); - const resetLayers = useResetRecoilState(layersAtom); - const resetStep = useResetRecoilState(stepAtom); + const setTmpBbox = useSetAtom(tmpBboxAtom); + const setLayers = useSetAtom(layersAtom); + const { removeStep } = useSyncStep(); useEffect(() => { const tmpbbox: [number, number, number, number] = DEFAULT_MAP_BBOX; @@ -32,8 +31,8 @@ export default function Home() { }, [setTmpBbox]); useEffect(() => { - resetLayers(); - resetStep(); + setLayers([]); + removeStep(); }, []); return ( diff --git a/client/src/containers/home/sidebar/index.tsx b/client/src/containers/home/sidebar/index.tsx index 4aeec8a..fc630f9 100644 --- a/client/src/containers/home/sidebar/index.tsx +++ b/client/src/containers/home/sidebar/index.tsx @@ -2,14 +2,14 @@ import { PropsWithChildren } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { cn } from '@/lib/classnames'; -import { sidebarOpenAtom } from '@/store'; +import { sidebarOpenAtom } from '@/store/map'; export default function Sidebar({ children }: PropsWithChildren) { - const open = useRecoilValue(sidebarOpenAtom); + const open = useAtomValue(sidebarOpenAtom); return (
| null>(null); - const bbox = useRecoilValue(bboxAtom); - const tmpBbox = useRecoilValue(tmpBboxAtom); - // const isFlyingBack = useRecoilValue(isFlyingBackAtom); + const bbox = useAtomValue(bboxAtom); + const tmpBbox = useAtomValue(tmpBboxAtom); + // const isFlyingBack = useAtomValue(isFlyingBackAtom); - const layersInteractiveIds = useRecoilValue(layersInteractiveIdsAtom); + const layersInteractiveIds = useAtomValue(layersInteractiveIdsAtom); - const setBbox = useSetRecoilState(bboxAtom); - const setTmpBbox = useSetRecoilState(tmpBboxAtom); + const setBbox = useSetAtom(bboxAtom); + const setTmpBbox = useSetAtom(tmpBboxAtom); const pathname = usePathname(); diff --git a/client/src/containers/map/layer-manager/index.tsx b/client/src/containers/map/layer-manager/index.tsx index 82ab646..9404959 100644 --- a/client/src/containers/map/layer-manager/index.tsx +++ b/client/src/containers/map/layer-manager/index.tsx @@ -2,17 +2,17 @@ import { Layer } from 'react-map-gl'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; -import { layersAtom, layersSettingsAtom } from '@/store'; +import { layersAtom, layersSettingsAtom } from '@/store/map'; import LayerManagerItem from '@/containers/map/layer-manager/item'; import { DeckMapboxOverlayProvider } from '@/components/map/provider'; const LayerManager = () => { - const layers = useRecoilValue(layersAtom); - const layersSettings = useRecoilValue(layersSettingsAtom); + const layers = useAtomValue(layersAtom); + const layersSettings = useAtomValue(layersSettingsAtom); return ( diff --git a/client/src/containers/map/layer-manager/item.tsx b/client/src/containers/map/layer-manager/item.tsx index 172814e..c1ccab0 100644 --- a/client/src/containers/map/layer-manager/item.tsx +++ b/client/src/containers/map/layer-manager/item.tsx @@ -2,11 +2,16 @@ import { useCallback, useEffect } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { parseConfig } from '@/lib/json-converter'; -import { layersInteractiveAtom, layersInteractiveIdsAtom, layersSettingsAtom } from '@/store'; +import { + LayersSettingsAtom, + layersInteractiveAtom, + layersInteractiveIdsAtom, + layersSettingsAtom, +} from '@/store/map'; import { useGetLayersId } from '@/types/generated/layer'; import { LayerResponseDataObject } from '@/types/generated/strapi.schemas'; @@ -24,10 +29,10 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => const { data } = useGetLayersId(id, { populate: 'metadata', }); - const layersInteractive = useRecoilValue(layersInteractiveAtom); - const setLayersInteractive = useSetRecoilState(layersInteractiveAtom); - const setLayersInteractiveIds = useSetRecoilState(layersInteractiveIdsAtom); - const setLayersSettings = useSetRecoilState(layersSettingsAtom); + const layersInteractive = useAtomValue(layersInteractiveAtom); + const setLayersInteractive = useSetAtom(layersInteractiveAtom); + const setLayersInteractiveIds = useSetAtom(layersInteractiveIdsAtom); + const [layerSettings, setLayersSettings] = useAtom(layersSettingsAtom); const handleAddMapboxLayer = useCallback( ({ styles }: Config) => { @@ -71,16 +76,16 @@ const LayerManagerItem = ({ id, beforeId, settings }: LayerManagerItemProps) => if (data?.data?.attributes) { const { params_config } = data.data.attributes as LayerTyped; if (params_config?.length) { - setLayersSettings((prev) => ({ - ...prev, + setLayersSettings({ + ...layerSettings, [id]: params_config.reduce( - (acc, curr) => ({ + (acc: LayersSettingsAtom, curr) => ({ ...acc, [curr.key as unknown as string]: curr.default, }), {} ), - })); + }); } } }, [data?.data?.attributes]); diff --git a/client/src/containers/map/legend/index.tsx b/client/src/containers/map/legend/index.tsx index 3b87214..5f47991 100644 --- a/client/src/containers/map/legend/index.tsx +++ b/client/src/containers/map/legend/index.tsx @@ -1,20 +1,20 @@ import { useCallback, useMemo } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { cn } from '@/lib/classnames'; -import { layersSettingsAtom, layersAtom, DEFAULT_SETTINGS } from '@/store'; +import { layersSettingsAtom, layersAtom, DEFAULT_SETTINGS } from '@/store/map'; import MapLegendItem from '@/containers/map/legend/item'; import Legend from '@/components/map/legend'; const MapLegends = ({ className = '' }) => { - const layers = useRecoilValue(layersAtom); - const setLayers = useSetRecoilState(layersAtom); - const layersSettings = useRecoilValue(layersSettingsAtom); - const setLayersSettings = useSetRecoilState(layersSettingsAtom); + const layers = useAtomValue(layersAtom); + const setLayers = useSetAtom(layersAtom); + const layersSettings = useAtomValue(layersSettingsAtom); + const setLayersSettings = useSetAtom(layersSettingsAtom); const handleChangeOrder = useCallback( (order: string[]) => { @@ -67,7 +67,7 @@ const MapLegends = ({ className = '' }) => { ); const ITEMS = useMemo(() => { - return layers.map((layer) => { + return layers?.map((layer) => { const settings = layersSettings[layer] ?? { opacity: 1, visibility: true, expand: true }; return ( diff --git a/client/src/containers/map/legend/item.tsx b/client/src/containers/map/legend/item.tsx index 634a80b..e3e848e 100644 --- a/client/src/containers/map/legend/item.tsx +++ b/client/src/containers/map/legend/item.tsx @@ -2,11 +2,11 @@ import { ReactElement, createElement, isValidElement, useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { parseConfig } from '@/lib/json-converter'; -import { layersSettingsAtom } from '@/store'; +import { layersSettingsAtom } from '@/store/map'; import { useGetLayersId } from '@/types/generated/layer'; import { LayerTyped, LegendConfig } from '@/types/layers'; @@ -59,7 +59,7 @@ const getSettingsManager = (data: LayerTyped = {} as LayerTyped): SettingsManage }; const MapLegendItem = ({ id, ...props }: MapLegendItemProps) => { - const layersSettings = useRecoilValue(layersSettingsAtom); + const layersSettings = useAtomValue(layersSettingsAtom); const { data, isError, isFetched, isFetching, isPlaceholderData } = useGetLayersId(id, { populate: 'metadata', diff --git a/client/src/containers/map/markers/home-markers/index.tsx b/client/src/containers/map/markers/home-markers/index.tsx index f6c09df..e5ce3e3 100644 --- a/client/src/containers/map/markers/home-markers/index.tsx +++ b/client/src/containers/map/markers/home-markers/index.tsx @@ -4,11 +4,9 @@ import { useMemo } from 'react'; import { Layer, Source } from 'react-map-gl'; -import { useRecoilValue } from 'recoil'; - import { getStoriesParams } from '@/lib/stories'; -import { categoryAtom } from '@/store/home'; +import { useSyncCategory } from '@/store/home'; import { useGetCategories } from '@/types/generated/category'; import { useGetStories } from '@/types/generated/story'; @@ -17,7 +15,7 @@ import { StoryStepMap } from '@/types/story'; import { useMapImage } from '@/hooks/map'; const StoryMarkers = () => { - const category = useRecoilValue(categoryAtom); + const [category] = useSyncCategory(); const { data: categories } = useGetCategories(); const categoryId = useMemo(() => { diff --git a/client/src/containers/map/markers/story-markers/index.tsx b/client/src/containers/map/markers/story-markers/index.tsx index ac594cb..9671f78 100644 --- a/client/src/containers/map/markers/story-markers/index.tsx +++ b/client/src/containers/map/markers/story-markers/index.tsx @@ -4,9 +4,7 @@ import { useMemo, useState } from 'react'; import { useParams } from 'next/navigation'; -import { useRecoilValue } from 'recoil'; - -import { stepAtom } from '@/store/stories'; +import { useSyncStep } from '@/store/stories'; import { useGetStoriesId } from '@/types/generated/story'; import { StoryStepMap } from '@/types/story'; @@ -28,7 +26,7 @@ type StoryMarker = { }; const StoryMarkers = () => { - const step = useRecoilValue(stepAtom); + const { step } = useSyncStep(); const { id } = useParams(); const { data: storyData } = useGetStoriesId(+id, { diff --git a/client/src/containers/map/popup/index.tsx b/client/src/containers/map/popup/index.tsx index 02f6cc1..048af19 100644 --- a/client/src/containers/map/popup/index.tsx +++ b/client/src/containers/map/popup/index.tsx @@ -1,17 +1,17 @@ import { Popup } from 'react-map-gl'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { layersInteractiveAtom, popupAtom } from '@/store/index'; +import { layersInteractiveAtom, popupAtom } from '@/store/map'; import PopupItem from '@/containers/map/popup/item'; const PopupContainer = () => { - const popup = useRecoilValue(popupAtom); - const layersInteractive = useRecoilValue(layersInteractiveAtom); + const popup = useAtomValue(popupAtom); + const layersInteractive = useAtomValue(layersInteractiveAtom); const lys = [...layersInteractive].reverse(); - const setPopup = useSetRecoilState(popupAtom); + const setPopup = useSetAtom(popupAtom); if (!popup) return null; diff --git a/client/src/containers/map/popup/item.tsx b/client/src/containers/map/popup/item.tsx index 2f9f9c6..7b280cc 100644 --- a/client/src/containers/map/popup/item.tsx +++ b/client/src/containers/map/popup/item.tsx @@ -3,11 +3,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useMap } from 'react-map-gl'; import type { Feature } from 'geojson'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; import { format } from '@/lib/utils/formats'; -import { layersInteractiveIdsAtom, popupAtom } from '@/store'; +import { layersInteractiveIdsAtom, popupAtom } from '@/store/map'; import { useGetLayersId } from '@/types/generated/layer'; import { LayerTyped } from '@/types/layers'; @@ -23,8 +23,8 @@ const PopupItem = ({ id }: PopupItemProps) => { const { default: map } = useMap(); - const popup = useRecoilValue(popupAtom); - const layersInteractiveIds = useRecoilValue(layersInteractiveIdsAtom); + const popup = useAtomValue(popupAtom); + const layersInteractiveIds = useAtomValue(layersInteractiveIdsAtom); const { data, isFetching, isFetched, isError, isPlaceholderData } = useGetLayersId(id, { populate: 'metadata', diff --git a/client/src/containers/map/settings/basemaps/item/index.tsx b/client/src/containers/map/settings/basemaps/item/index.tsx index 162a43c..bbfdbd0 100644 --- a/client/src/containers/map/settings/basemaps/item/index.tsx +++ b/client/src/containers/map/settings/basemaps/item/index.tsx @@ -3,11 +3,11 @@ import { useCallback } from 'react'; import Image from 'next/image'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; import { cn } from '@/lib/classnames'; -import { mapSettingsAtom } from '@/store/index'; +import { mapSettingsAtom } from '@/store/map'; export interface BasemapItemProps { label: string; @@ -16,8 +16,8 @@ export interface BasemapItemProps { } const BasemapItem = ({ label, value, preview }: BasemapItemProps) => { - const { basemap } = useRecoilValue(mapSettingsAtom); - const setMapSettings = useSetRecoilState(mapSettingsAtom); + const { basemap } = useAtomValue(mapSettingsAtom); + const setMapSettings = useSetAtom(mapSettingsAtom); const handleToggleBasemap = useCallback(() => { setMapSettings((prev) => ({ diff --git a/client/src/containers/map/settings/boundaries/index.tsx b/client/src/containers/map/settings/boundaries/index.tsx index b66440b..6b9f5b2 100644 --- a/client/src/containers/map/settings/boundaries/index.tsx +++ b/client/src/containers/map/settings/boundaries/index.tsx @@ -1,15 +1,15 @@ import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { mapSettingsAtom } from '@/store/index'; +import { mapSettingsAtom } from '@/store/map'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; const Boundaries = () => { - const { boundaries } = useRecoilValue(mapSettingsAtom); - const setMapSettings = useSetRecoilState(mapSettingsAtom); + const { boundaries } = useAtomValue(mapSettingsAtom); + const setMapSettings = useSetAtom(mapSettingsAtom); const handleChange = useCallback( (v: boolean) => { diff --git a/client/src/containers/map/settings/labels/index.tsx b/client/src/containers/map/settings/labels/index.tsx index ba34a4e..58bebcc 100644 --- a/client/src/containers/map/settings/labels/index.tsx +++ b/client/src/containers/map/settings/labels/index.tsx @@ -1,8 +1,8 @@ import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { mapSettingsAtom } from '@/store/index'; +import { mapSettingsAtom } from '@/store/map'; import { LABELS } from '@/constants/basemaps'; @@ -10,8 +10,8 @@ import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; const Labels = () => { - const { labels } = useRecoilValue(mapSettingsAtom); - const setMapSettings = useSetRecoilState(mapSettingsAtom); + const { labels } = useAtomValue(mapSettingsAtom); + const setMapSettings = useSetAtom(mapSettingsAtom); const handleChange = useCallback( (v: string) => { diff --git a/client/src/containers/map/settings/manager/index.tsx b/client/src/containers/map/settings/manager/index.tsx index ec63dbf..e776a2f 100644 --- a/client/src/containers/map/settings/manager/index.tsx +++ b/client/src/containers/map/settings/manager/index.tsx @@ -3,9 +3,9 @@ import { useCallback, useEffect } from 'react'; import { useMap } from 'react-map-gl'; import { AnyLayer } from 'mapbox-gl'; -import { useRecoilValue } from 'recoil'; +import { useAtomValue } from 'jotai'; -import { mapSettingsAtom } from '@/store/index'; +import { mapSettingsAtom } from '@/store/map'; import { BASEMAPS } from '@/constants/basemaps'; @@ -16,7 +16,7 @@ type AnyLayerWithMetadata = AnyLayer & { const MapSettingsManager = () => { const { default: mapRef } = useMap(); const loaded = mapRef?.loaded(); - const { basemap, labels, boundaries, roads } = useRecoilValue(mapSettingsAtom); + const { basemap, labels, boundaries, roads } = useAtomValue(mapSettingsAtom); const handleGroup = useCallback( (groups: string[], groupId: string, visible = true) => { diff --git a/client/src/containers/map/settings/roads/index.tsx b/client/src/containers/map/settings/roads/index.tsx index 6d8bcfa..9ff0f35 100644 --- a/client/src/containers/map/settings/roads/index.tsx +++ b/client/src/containers/map/settings/roads/index.tsx @@ -1,15 +1,15 @@ import { useCallback } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useAtomValue, useSetAtom } from 'jotai'; -import { mapSettingsAtom } from '@/store/index'; +import { mapSettingsAtom } from '@/store/map'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; const Roads = () => { - const { roads } = useRecoilValue(mapSettingsAtom); - const setMapSettings = useSetRecoilState(mapSettingsAtom); + const { roads } = useAtomValue(mapSettingsAtom); + const setMapSettings = useSetAtom(mapSettingsAtom); const handleChange = useCallback( (v: boolean) => { diff --git a/client/src/containers/story/index.tsx b/client/src/containers/story/index.tsx index 9de69d1..38f5ad8 100644 --- a/client/src/containers/story/index.tsx +++ b/client/src/containers/story/index.tsx @@ -4,15 +4,14 @@ import { useEffect, useMemo } from 'react'; import { useParams, useRouter } from 'next/navigation'; +import { useSetAtom } from 'jotai'; import { ArrowLeft, Share2 } from 'lucide-react'; -import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'; import { cn } from '@/lib/classnames'; import { ScrollProvider } from '@/lib/scroll'; -import { layersAtom, tmpBboxAtom } from '@/store'; - -import { stepAtom } from '@/store/stories'; +import { layersAtom, tmpBboxAtom } from '@/store/map'; +import { useSyncStep } from '@/store/stories'; import { useGetStoriesId } from '@/types/generated/story'; @@ -27,10 +26,9 @@ const headerButtonClassName = 'rounded-4xl h-auto border-gray-800 bg-[hsl(198,100%,14%)]/75 px-5 py-2.5 hover:bg-gray-800'; const Story = () => { - const step = useRecoilValue(stepAtom); - const setTmpBbox = useSetRecoilState(tmpBboxAtom); - const setLayers = useSetRecoilState(layersAtom); - const resetLayers = useResetRecoilState(layersAtom); + const { step } = useSyncStep(); + const setTmpBbox = useSetAtom(tmpBboxAtom); + const setLayers = useSetAtom(layersAtom); const { push } = useRouter(); const { id } = useParams(); @@ -42,13 +40,13 @@ const Story = () => { const steps = useMemo(() => story?.steps || [], [story]); const handleGoHome = () => { - resetLayers(); + setLayers([]); push('/'); }; useEffect(() => { if (!steps) return; - const currStep = steps[step]; + const currStep = steps[step - 1]; if (!currStep || !isMapNotEmpty(currStep.map)) { return; @@ -92,8 +90,8 @@ const Story = () => { {steps?.map((mapStep, index) => { return ( - - + + ); })} @@ -102,10 +100,12 @@ const Story = () => { ))} diff --git a/client/src/containers/story/steps/controller/controller-item.tsx b/client/src/containers/story/steps/controller/controller-item.tsx index 9a9abd4..d956ce5 100644 --- a/client/src/containers/story/steps/controller/controller-item.tsx +++ b/client/src/containers/story/steps/controller/controller-item.tsx @@ -2,11 +2,9 @@ import { useCallback } from 'react'; -import { useRecoilValue } from 'recoil'; - import { useScrollToItem } from '@/lib/scroll'; -import { stepAtom } from '@/store/stories'; +import { useSyncStep } from '@/store/stories'; import { Button } from '@/components/ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; @@ -20,7 +18,7 @@ type ScrollItemControllerProps = { export const ScrollItemController = ({ title, newStep, className }: ScrollItemControllerProps) => { const scrollToItem = useScrollToItem(); - const currStep = useRecoilValue(stepAtom); + const { step: currStep } = useSyncStep(); const handleSCrollToItem = useCallback(() => { if (newStep !== currStep) { diff --git a/client/src/containers/story/steps/index.tsx b/client/src/containers/story/steps/index.tsx index 4484f75..bb48475 100644 --- a/client/src/containers/story/steps/index.tsx +++ b/client/src/containers/story/steps/index.tsx @@ -1,11 +1,9 @@ 'use client'; import { PropsWithChildren, useMemo } from 'react'; -import { useRecoilValue } from 'recoil'; - import { cn } from '@/lib/classnames'; -import { stepAtom } from '@/store/stories'; +import { useSyncStep } from '@/store/stories'; import { StepLayoutOutroStepComponentMedia, @@ -25,7 +23,7 @@ type StepProps = PropsWithChildren<{ }>; const Step = ({ step, category, index }: StepProps) => { - const currentStep = useRecoilValue(stepAtom); + const { step: currentStep } = useSyncStep(); const type = getStepType(step); const STEP_COMPONENT = useMemo(() => { diff --git a/client/src/containers/story/steps/layouts/outro-step.tsx b/client/src/containers/story/steps/layouts/outro-step.tsx index 60ab1a9..3e8aaa1 100644 --- a/client/src/containers/story/steps/layouts/outro-step.tsx +++ b/client/src/containers/story/steps/layouts/outro-step.tsx @@ -6,11 +6,11 @@ import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useScroll, motion, useTransform } from 'framer-motion'; -import { useSetRecoilState } from 'recoil'; +import { useSetAtom } from 'jotai'; import { getImageSrc } from '@/lib/image-src'; -import { isFlyingBackAtom } from '@/store'; +import { isFlyingBackAtom } from '@/store/map'; import { StepLayoutOutroStepComponent } from '@/types/generated/strapi.schemas'; @@ -36,7 +36,7 @@ const links = [ const OutroStepLayout = ({ step, showContent }: MediaStepLayoutProps) => { const { push } = useRouter(); - const setIsFlyingBack = useSetRecoilState(isFlyingBackAtom); + const setIsFlyingBack = useSetAtom(isFlyingBackAtom); const { content, title } = step as StepLayoutOutroStepComponent; const containerRef = useRef(null); diff --git a/client/src/lib/recoil/devtools.tsx b/client/src/lib/recoil/devtools.tsx deleted file mode 100644 index c6c606a..0000000 --- a/client/src/lib/recoil/devtools.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect } from 'react'; - -import { useRecoilSnapshot } from 'recoil'; - -function RecoilDevTools() { - const snapshot = useRecoilSnapshot(); - - useEffect(() => { - console.debug('The following atoms were modified:'); - for (const node of snapshot.getNodes_UNSTABLE({ isModified: true })) { - console.debug(node.key, snapshot.getLoadable(node)); - } - }, [snapshot]); - - return null; -} - -export default RecoilDevTools; diff --git a/client/src/lib/recoil/index.tsx b/client/src/lib/recoil/index.tsx deleted file mode 100644 index 2d7e78f..0000000 --- a/client/src/lib/recoil/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { RecoilURLSync, RecoilURLSyncOptions } from 'recoil-sync'; - -import { useSyncURLNext } from './useSyncURLNext'; - -type Props = Omit & { - decodedQueryParams?: boolean; -}; - -export type Serialize = (data: unknown) => string; - -export type Deserialize = (str: string) => unknown; - -export const RecoilURLSyncNext: React.FC = ({ children, ...options }) => { - const { decodedQueryParams = true } = options; - - const { browserInterface, ...defaultOptions } = useSyncURLNext({ - decodedQueryParams, - }); - - return ( - - {children} - - ); -}; diff --git a/client/src/lib/recoil/useSyncURLNext.ts b/client/src/lib/recoil/useSyncURLNext.ts deleted file mode 100644 index 4d1f20d..0000000 --- a/client/src/lib/recoil/useSyncURLNext.ts +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import { useCallback } from 'react'; - -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; - -import { BrowserInterface, RecoilURLSyncOptions } from 'recoil-sync'; - -type UseSyncURLNextOptions = { - decodedQueryParams?: boolean; -}; - -export function useSyncURLNext( - options: UseSyncURLNextOptions -): Partial> { - const { decodedQueryParams } = options; - - const pathname = usePathname(); - const searchParams = useSearchParams(); - const { - // replace, - push, - } = useRouter(); - - const browserInterface: BrowserInterface = { - replaceURL: useCallback( - (url: string) => { - const u = decodedQueryParams ? decodeURIComponent(url) : url; - return window.history.replaceState({}, '', u); - // return replace(u, { shallow: true }); - }, - [decodedQueryParams] - ), - - pushURL: useCallback( - (url: string) => { - const u = decodedQueryParams ? decodeURIComponent(url) : url; - return push(u, { shallow: true }); - }, - [decodedQueryParams, push] - ), - - getURL: useCallback(() => { - const url = new URL( - `${pathname}${searchParams ? `?${searchParams.toString()}` : ''}`, - globalThis?.document?.location?.href ?? 'http://localhost:3000' - ); - - return url.toString(); - }, [pathname, searchParams]), - - listenChangeURL: useCallback((handler2: () => void) => { - return () => { - handler2(); - }; - }, []), - }; - - return { - browserInterface, - }; -} diff --git a/client/src/lib/scroll/index.tsx b/client/src/lib/scroll/index.tsx index 0d2d38e..f1a0479 100644 --- a/client/src/lib/scroll/index.tsx +++ b/client/src/lib/scroll/index.tsx @@ -11,9 +11,8 @@ import { } from 'react'; import { motionValue, MotionValue, useMotionValueEvent, useScroll } from 'framer-motion'; -import { useSetRecoilState } from 'recoil'; -import { stepAtom } from '@/store/stories'; +import { useSyncStep } from '@/store/stories'; type ScrollItem = { key: string | number; @@ -52,7 +51,7 @@ export const ScrollProvider = ({ children }: PropsWithChildren) => { offset: ['start end', 'start center'], }); - const setStep = useSetRecoilState(stepAtom); + const { setStep } = useSyncStep(); const addScrollItem = useCallback( (data) => { diff --git a/client/src/store/home.ts b/client/src/store/home.ts index 2c19bb8..38773ad 100644 --- a/client/src/store/home.ts +++ b/client/src/store/home.ts @@ -1,72 +1,17 @@ -'use client'; +import { atom } from 'jotai'; +import { parseAsArrayOf, useQueryStates, parseAsString, useQueryState } from 'nuqs'; -import { array, string } from '@recoiljs/refine'; -import { atom, selectorFamily } from 'recoil'; -import { urlSyncEffect } from 'recoil-sync'; +// NUQS SYNC STATE HOOKS -export const categoryAtom = atom({ - key: 'category', - default: undefined, - effects: [ - urlSyncEffect({ - refine: string(), - }), - ], -}); +export const useSyncCategory = () => useQueryState('category', parseAsString); -export const filtersOpenAtom = atom({ - key: 'filtersOpen', - default: false, -}); +export const useSyncFilters = () => + useQueryStates({ + tags: parseAsArrayOf(parseAsString), + ifi: parseAsArrayOf(parseAsString), + status: parseAsArrayOf(parseAsString), + }); -export const tagsAtom = atom({ - key: 'tags', - default: [], - effects: [ - urlSyncEffect({ - refine: array(string()), - }), - ], -}); +// JOTAI ATOMS -export const ifiAtom = atom({ - key: 'ifi', - default: [], - effects: [ - urlSyncEffect({ - refine: array(string()), - }), - ], -}); - -export const statusAtom = atom({ - key: 'status', - default: [], - effects: [ - urlSyncEffect({ - refine: array(string()), - }), - ], -}); - -const filterAtoms = { - tags: tagsAtom, - ifi: ifiAtom, - status: statusAtom, -}; - -export type FilterName = keyof typeof filterAtoms; - -export const filterSelector = selectorFamily({ - key: 'filter', - get: - (name: FilterName) => - ({ get }) => - get(filterAtoms[name]), - set: - (name: FilterName) => - ({ set, reset }, newValue) => - Array.isArray(newValue) && !newValue.length - ? reset(filterAtoms[name]) - : set(filterAtoms[name], newValue), -}); +export const filtersOpenAtom = atom(false); diff --git a/client/src/store/index.ts b/client/src/store/index.ts deleted file mode 100644 index 78cb4f6..0000000 --- a/client/src/store/index.ts +++ /dev/null @@ -1,176 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; - -import { MapLayerMouseEvent } from 'react-map-gl'; - -import { - array, - bool, - mixed, - nullable, - number, - object, - string, - tuple, - writableDict, -} from '@recoiljs/refine'; -import { MapboxGeoJSONFeature } from 'mapbox-gl'; -import { atom, useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; -import { urlSyncEffect } from 'recoil-sync'; - -export type TmpBbox = { - bbox: readonly [number, number, number, number]; - options: { - zoom: number; - pitch: number; - bearing: number; - longitude: number; - latitude: number; - }; -}; - -// Map settings -export const mapSettingsAtom = atom({ - key: 'map-settings', - default: { - basemap: 'basemap-satellite', - labels: 'labels-none', - boundaries: false, - roads: false, - }, - effects: [ - urlSyncEffect({ - refine: object({ - basemap: string(), - labels: string(), - boundaries: bool(), - roads: bool(), - }), - }), - ], -}); - -// Map viewport -export const bboxAtom = atom({ - key: 'bbox', - default: null, - effects: [ - urlSyncEffect({ - refine: nullable(tuple(number(), number(), number(), number())), - }), - ], -}); - -export const tmpBboxAtom = atom({ - key: 'tmp-bbox', - default: undefined, -}); - -// Sidebar and menus -export const sidebarOpenAtom = atom({ - key: 'sidebar-open', - default: true, -}); - -// Map layers -export const layersAtom = atom({ - key: 'layers', - default: [], - effects: [ - urlSyncEffect({ - refine: array(number()), - }), - ], -}); - -export const layersSettingsAtom = atom({ - key: 'layers-settings', - default: {}, - effects: [ - urlSyncEffect({ - refine: writableDict(writableDict(mixed())), - }), - ], -}); - -export const layersInteractiveAtom = atom({ - key: 'layers-interactive', - default: [], -}); - -export const layersInteractiveIdsAtom = atom({ - key: 'layers-interactive-ids', - default: ['story-markers-cluster', 'story-markers-cluster-count', 'story-markers-unclustered'], -}); - -export const popupAtom = atom({ - key: 'point', - default: null, - dangerouslyAllowMutability: true, -}); - -export const markerAtom = atom({ - key: 'marker', - default: null, -}); - -export const isFlyingBackAtom = atom({ - key: 'is-flying-back', - default: false, -}); - -export const DEFAULT_SETTINGS = { - expand: true, -}; - -export const timelineAtom = atom<{ [id: number]: { frame: number; layers: number[] } }>({ - key: 'timeline', - default: {}, -}); - -export function useSyncLayersAndSettings() { - const layers = useRecoilValue(layersAtom); - - const setPopup = useSetRecoilState(popupAtom); - - const syncAtoms = useRecoilCallback( - ({ snapshot, set }) => - async () => { - const lys = await snapshot.getPromise(layersAtom); - const lysSettings = await snapshot.getPromise(layersSettingsAtom); - const lysInteractive = await snapshot.getPromise(layersInteractiveAtom); - - // Reset layersettings that are not in layers - Object.keys(lysSettings).forEach((ly) => { - if (!lys.includes(parseInt(ly))) { - setTimeout(async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [ly]: _, ...rest } = lysSettings; - set(layersSettingsAtom, rest); - }, 0); - } - }); - - // Reset interactive layers - // If I don't use setTimeout, the url will not be updated - // setTimeout is needed to put this function to the end of the js queue - setTimeout(() => { - const newLysInteractive = lysInteractive.filter((ly) => lys.includes(ly)); - set(layersInteractiveAtom, newLysInteractive); - - if (!newLysInteractive.length) { - setPopup(null); - } - }, 0); - }, - [] - ); - - // Sync layersettings when layers change - useEffect(() => { - syncAtoms(); - }, [layers.length, syncAtoms]); - - return true; -} diff --git a/client/src/store/map.ts b/client/src/store/map.ts new file mode 100644 index 0000000..ac212cd --- /dev/null +++ b/client/src/store/map.ts @@ -0,0 +1,57 @@ +import { MapLayerMouseEvent } from 'react-map-gl'; + +import { atom } from 'jotai'; +import { MapboxGeoJSONFeature } from 'mapbox-gl'; + +export type TmpBbox = { + bbox: readonly [number, number, number, number]; + options: { + zoom: number; + pitch: number; + bearing: number; + longitude: number; + latitude: number; + }; +}; + +// Map settings +export const mapSettingsAtom = atom({ + basemap: 'basemap-satellite', + labels: 'labels-none', + boundaries: false, + roads: false, +}); + +// Map viewport +export const bboxAtom = atom(null); + +export const tmpBboxAtom = atom(undefined); + +// Sidebar and menus +export const sidebarOpenAtom = atom(true); + +// Map layers +export const layersAtom = atom([]); + +export type LayersSettingsAtom = Record>; +export const layersSettingsAtom = atom({}); + +export const layersInteractiveAtom = atom([]); + +export const layersInteractiveIdsAtom = atom([ + 'story-markers-cluster', + 'story-markers-cluster-count', + 'story-markers-unclustered', +]); + +export const popupAtom = atom(null); + +export const markerAtom = atom(null); + +export const isFlyingBackAtom = atom(false); + +export const DEFAULT_SETTINGS = { + expand: true, +}; + +export const timelineAtom = atom<{ [id: number]: { frame: number; layers: number[] } }>({}); diff --git a/client/src/store/stories.ts b/client/src/store/stories.ts index c1fe390..5e98fbd 100644 --- a/client/src/store/stories.ts +++ b/client/src/store/stories.ts @@ -1,13 +1,10 @@ -import { number } from '@recoiljs/refine'; -import { atom } from 'recoil'; -import { urlSyncEffect } from 'recoil-sync'; +'use client'; +import { parseAsInteger, useQueryState } from 'nuqs'; -export const stepAtom = atom({ - key: 'step', - default: 0, - effects: [ - urlSyncEffect({ - refine: number(), - }), - ], -}); +const DEFAULT_STEP = 1; + +export const useSyncStep = () => { + const [step, setStep] = useQueryState('step', parseAsInteger.withDefault(DEFAULT_STEP)); + const removeStep = () => setStep(null); + return { step, setStep, removeStep }; +}; diff --git a/yarn.lock b/yarn.lock index 7547df0..34a5e9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2858,9 +2858,11 @@ __metadata: eslint-plugin-import: 2.27.5 eslint-plugin-prettier: ^4.2.1 framer-motion: ^10.16.4 + jotai: ^2.6.4 lucide-react: ^0.252.0 mapbox-gl: ^3.0.1 next: 13.4.5 + nuqs: ^1.16.1 postcss: 8.4.24 prettier: ^2.8.8 prettier-plugin-tailwindcss: ^0.3.0 @@ -2870,8 +2872,6 @@ __metadata: react-hook-form: ^7.45.0 react-map-gl: 7.1.5 react-markdown: 8.0.7 - recoil: ^0.7.7 - recoil-sync: ^0.2.0 rooks: 7.14.1 tailwind-merge: ^1.13.2 tailwindcss: 3.3.2 @@ -13313,13 +13313,6 @@ __metadata: languageName: node linkType: hard -"hamt_plus@npm:1.0.2": - version: 1.0.2 - resolution: "hamt_plus@npm:1.0.2" - checksum: af26ea32db03009019cc83dfa9411521a2fa16079443de1a502c9be46d8b3c975acda8ed93fc5750ef08d3186d35901e2d8cfe717dd54bea67b358601fa74e4c - languageName: node - linkType: hard - "handle-thing@npm:^2.0.0": version: 2.0.1 resolution: "handle-thing@npm:2.0.1" @@ -14748,6 +14741,21 @@ __metadata: languageName: node linkType: hard +"jotai@npm:^2.6.4": + version: 2.6.4 + resolution: "jotai@npm:2.6.4" + peerDependencies: + "@types/react": ">=17.0.0" + react: ">=17.0.0" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 1a27de808e13bce97978d33a5f368e00acdfb472bef1d068dffabe036dad3b43bfba1595205a1a47719c9ca7149458ce2b198751040be7f3ef255cb51388675e + languageName: node + linkType: hard + "joycon@npm:^3.0.1": version: 3.1.1 resolution: "joycon@npm:3.1.1" @@ -16582,6 +16590,13 @@ __metadata: languageName: node linkType: hard +"mitt@npm:^3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 + languageName: node + linkType: hard + "mixin-deep@npm:^1.2.0": version: 1.3.2 resolution: "mixin-deep@npm:1.3.2" @@ -17149,6 +17164,17 @@ __metadata: languageName: node linkType: hard +"nuqs@npm:^1.16.1": + version: 1.16.1 + resolution: "nuqs@npm:1.16.1" + dependencies: + mitt: ^3.0.1 + peerDependencies: + next: ">=13.4 <14.0.2 || ^14.0.3" + checksum: 210df31257117842fbec2b5f7fc2a200b283579dee41f58526064e9355c270e8dc00d2149d7631cccb3ce1393a75938556452abcd176267dbd33e138bc29badc + languageName: node + linkType: hard + "oas-kit-common@npm:^1.0.8": version: 1.0.8 resolution: "oas-kit-common@npm:1.0.8" @@ -19244,34 +19270,6 @@ __metadata: languageName: node linkType: hard -"recoil-sync@npm:^0.2.0": - version: 0.2.0 - resolution: "recoil-sync@npm:0.2.0" - dependencies: - "@recoiljs/refine": ^0.1.1 - transit-js: ^0.8.874 - peerDependencies: - recoil: ">=0.7.3" - checksum: a0bd98acbc92ae58099a283056cb0c3cfcc08312bbe8bccd2662ecb3410b856fe3db33df765c7f5e9f030f73c44630e2c0f39266123eb1fcb7285d0f4b53c52a - languageName: node - linkType: hard - -"recoil@npm:^0.7.7": - version: 0.7.7 - resolution: "recoil@npm:0.7.7" - dependencies: - hamt_plus: 1.0.2 - peerDependencies: - react: ">=16.13.1" - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - checksum: 65edecbcb8d2cde89bfd61ec679c200483472a6cd343c33e4e9142b6ce524fb17d1fecc2bfd8c392926aaa8178c81457f165b32abce2a9662f51f98822c0a9cc - languageName: node - linkType: hard - "redux@npm:^4.1.2, redux@npm:^4.2.1": version: 4.2.1 resolution: "redux@npm:4.2.1" @@ -21480,13 +21478,6 @@ __metadata: languageName: node linkType: hard -"transit-js@npm:^0.8.874": - version: 0.8.874 - resolution: "transit-js@npm:0.8.874" - checksum: a1d3a78a0ce926320ba32cdd59a74104e1b440855497753b91f8e71831302b23adfb21416cdba30153305cf41cf96b75421a607a1799b676572fe6072ee04798 - languageName: node - linkType: hard - "trim-lines@npm:^3.0.0": version: 3.0.1 resolution: "trim-lines@npm:3.0.1"