From 861906d29b2c77777bfc015b71a5315b5683d857 Mon Sep 17 00:00:00 2001 From: Sonny Date: Wed, 6 Nov 2024 23:06:21 +0100 Subject: [PATCH] refactor: recreate collection and global hotkey contexts as store (using zustand) --- inertia/components/common/modal/modal.tsx | 4 +- .../collection/collection_container.tsx | 2 +- .../header/collection_description.tsx | 2 +- .../collection/header/collection_header.tsx | 2 +- .../collection/list/collection_item.tsx | 2 +- .../collection/list/collection_list.tsx | 3 +- .../dashboard/link/link_controls.tsx | 31 +--- inertia/components/dashboard/link/no_item.tsx | 2 +- .../dashboard/search/search_modal.tsx | 3 +- .../dashboard/search/search_result_item.tsx | 2 +- .../favorite/favorite_dropdown_item.tsx | 31 +--- .../side_nav/favorite/favorite_list.tsx | 2 +- .../dashboard/side_nav/side_navigation.tsx | 2 +- inertia/hooks/mantine/use_disable_overflow.ts | 9 ++ inertia/hooks/use_active_collection.tsx | 5 - inertia/hooks/use_collections.tsx | 5 - inertia/hooks/use_shortcut.tsx | 4 +- .../collection/item/collection_item.tsx | 2 +- .../collection/list/collection_list.tsx | 3 +- .../components/dashboard/dashboard_header.tsx | 2 +- .../dashboard/favorite/favorite_list.tsx | 2 +- .../components/dashboard/link/no_link.tsx | 2 +- inertia/pages/mantine_dashboard.tsx | 140 +++++++++++------- inertia/store/collection_store.ts | 91 ++++++++++++ inertia/store/global_hotkeys_store.ts | 11 ++ package.json | 5 +- pnpm-lock.yaml | 26 ++++ 27 files changed, 247 insertions(+), 148 deletions(-) create mode 100644 inertia/hooks/mantine/use_disable_overflow.ts delete mode 100644 inertia/hooks/use_active_collection.tsx delete mode 100644 inertia/hooks/use_collections.tsx create mode 100644 inertia/store/collection_store.ts create mode 100644 inertia/store/global_hotkeys_store.ts diff --git a/inertia/components/common/modal/modal.tsx b/inertia/components/common/modal/modal.tsx index d97c49e..686c2da 100644 --- a/inertia/components/common/modal/modal.tsx +++ b/inertia/components/common/modal/modal.tsx @@ -10,8 +10,8 @@ import { import ModalWrapper from '~/components/common/modal/_modal_wrapper'; import TextEllipsis from '~/components/common/text_ellipsis'; import useClickOutside from '~/hooks/use_click_outside'; -import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; import useShortcut from '~/hooks/use_shortcut'; +import { useGlobalHotkeysStore } from '~/store/global_hotkeys_store'; interface ModalProps { title?: string; @@ -32,7 +32,7 @@ export default function Modal({ close, }: ModalProps) { const modalRef = useRef(null); - const { setGlobalHotkeysEnabled } = useGlobalHotkeys(); + const { setGlobalHotkeysEnabled } = useGlobalHotkeysStore(); useClickOutside(modalRef, close); useShortcut('ESCAPE_KEY', close, { disableGlobalCheck: true }); diff --git a/inertia/components/dashboard/collection/collection_container.tsx b/inertia/components/dashboard/collection/collection_container.tsx index 952755d..9fdbe3e 100644 --- a/inertia/components/dashboard/collection/collection_container.tsx +++ b/inertia/components/dashboard/collection/collection_container.tsx @@ -4,7 +4,7 @@ import CollectionHeader from '~/components/dashboard/collection/header/collectio import LinkList from '~/components/dashboard/link/link_list'; import { NoCollection } from '~/components/dashboard/link/no_item'; import Footer from '~/components/footer/footer'; -import useActiveCollection from '~/hooks/use_active_collection'; +import { useActiveCollection } from '~/store/collection_store'; export interface CollectionHeaderProps { showButtons: boolean; diff --git a/inertia/components/dashboard/collection/header/collection_description.tsx b/inertia/components/dashboard/collection/header/collection_description.tsx index 7f3c701..0e799cd 100644 --- a/inertia/components/dashboard/collection/header/collection_description.tsx +++ b/inertia/components/dashboard/collection/header/collection_description.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; import TextEllipsis from '~/components/common/text_ellipsis'; -import useActiveCollection from '~/hooks/use_active_collection'; +import { useActiveCollection } from '~/store/collection_store'; const CollectionDescriptionStyle = styled.div({ width: '100%', diff --git a/inertia/components/dashboard/collection/header/collection_header.tsx b/inertia/components/dashboard/collection/header/collection_header.tsx index 6954160..6c48a51 100644 --- a/inertia/components/dashboard/collection/header/collection_header.tsx +++ b/inertia/components/dashboard/collection/header/collection_header.tsx @@ -6,7 +6,7 @@ import { CollectionHeaderProps } from '~/components/dashboard/collection/collect import CollectionControls from '~/components/dashboard/collection/header/collection_controls'; import CollectionDescription from '~/components/dashboard/collection/header/collection_description'; import VisibilityBadge from '~/components/visibilty/visibilty'; -import useActiveCollection from '~/hooks/use_active_collection'; +import { useActiveCollection } from '~/store/collection_store'; const paddingLeft = '1.25em'; const paddingRight = '1.65em'; diff --git a/inertia/components/dashboard/collection/list/collection_item.tsx b/inertia/components/dashboard/collection/list/collection_item.tsx index 67913a0..b6d31e0 100644 --- a/inertia/components/dashboard/collection/list/collection_item.tsx +++ b/inertia/components/dashboard/collection/list/collection_item.tsx @@ -5,8 +5,8 @@ import { useEffect, useRef } from 'react'; import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai'; import TextEllipsis from '~/components/common/text_ellipsis'; import { Item } from '~/components/dashboard/side_nav/nav_item'; -import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; import { CollectionWithLinks } from '~/types/app'; const CollectionItemStyle = styled(Item, { diff --git a/inertia/components/dashboard/collection/list/collection_list.tsx b/inertia/components/dashboard/collection/list/collection_list.tsx index b26fdd1..760f593 100644 --- a/inertia/components/dashboard/collection/list/collection_list.tsx +++ b/inertia/components/dashboard/collection/list/collection_list.tsx @@ -2,9 +2,8 @@ import styled from '@emotion/styled'; import { useTranslation } from 'react-i18next'; import CollectionItem from '~/components/dashboard/collection/list/collection_item'; import CollectionListContainer from '~/components/dashboard/collection/list/collection_list_container'; -import useActiveCollection from '~/hooks/use_active_collection'; -import useCollections from '~/hooks/use_collections'; import useShortcut from '~/hooks/use_shortcut'; +import { useActiveCollection, useCollections } from '~/store/collection_store'; const SideMenu = styled.nav(({ theme }) => ({ height: '100%', diff --git a/inertia/components/dashboard/link/link_controls.tsx b/inertia/components/dashboard/link/link_controls.tsx index a4b4c95..7d4ab62 100644 --- a/inertia/components/dashboard/link/link_controls.tsx +++ b/inertia/components/dashboard/link/link_controls.tsx @@ -1,17 +1,15 @@ import { Link as InertiaLink } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { ActionIcon, Menu } from '@mantine/core'; -import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { BsThreeDotsVertical } from 'react-icons/bs'; import { FaRegEye } from 'react-icons/fa'; import { GoPencil } from 'react-icons/go'; import { IoTrashOutline } from 'react-icons/io5'; import { MdFavorite, MdFavoriteBorder } from 'react-icons/md'; -import useActiveCollection from '~/hooks/use_active_collection'; -import useCollections from '~/hooks/use_collections'; import { onFavorite } from '~/lib/favorite'; import { appendCollectionId, appendLinkId } from '~/lib/navigation'; +import { useFavorites } from '~/store/collection_store'; import { Link } from '~/types/app'; interface LinksControlsProps { @@ -22,34 +20,9 @@ export default function LinkControls({ link, showGoToCollection = false, }: LinksControlsProps) { - const { collections, setCollections } = useCollections(); - const { setActiveCollection } = useActiveCollection(); + const { toggleFavorite } = useFavorites(); const { t } = useTranslation('common'); - const toggleFavorite = useCallback( - (linkId: Link['id']) => { - let linkIndex = 0; - const collectionIndex = collections.findIndex(({ links }) => { - const lIndex = links.findIndex((l) => l.id === linkId); - if (lIndex !== -1) { - linkIndex = lIndex; - } - return lIndex !== -1; - }); - - const collectionLink = collections[collectionIndex].links[linkIndex]; - const collectionsCopy = [...collections]; - collectionsCopy[collectionIndex].links[linkIndex] = { - ...collectionLink, - favorite: !collectionLink.favorite, - }; - - setCollections(collectionsCopy); - setActiveCollection(collectionsCopy[collectionIndex]); - }, - [collections, setCollections] - ); - const onFavoriteCallback = () => toggleFavorite(link.id); return ( diff --git a/inertia/components/dashboard/link/no_item.tsx b/inertia/components/dashboard/link/no_item.tsx index 31cd2bd..8d8508e 100644 --- a/inertia/components/dashboard/link/no_item.tsx +++ b/inertia/components/dashboard/link/no_item.tsx @@ -2,8 +2,8 @@ import styled from '@emotion/styled'; import { Link } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useTranslation } from 'react-i18next'; -import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; import { fadeIn } from '~/styles/keyframes'; const NoCollectionStyle = styled.div({ diff --git a/inertia/components/dashboard/search/search_modal.tsx b/inertia/components/dashboard/search/search_modal.tsx index c90956e..7c7359a 100644 --- a/inertia/components/dashboard/search/search_modal.tsx +++ b/inertia/components/dashboard/search/search_modal.tsx @@ -7,11 +7,10 @@ import Modal from '~/components/common/modal/modal'; import NoSearchResult from '~/components/dashboard/search/no_search_result'; import SearchResultList from '~/components/dashboard/search/search_result_list'; import { GOOGLE_SEARCH_URL } from '~/constants'; -import useActiveCollection from '~/hooks/use_active_collection'; -import useCollections from '~/hooks/use_collections'; import useToggle from '~/hooks/use_modal'; import useShortcut from '~/hooks/use_shortcut'; import { makeRequest } from '~/lib/request'; +import { useActiveCollection, useCollections } from '~/store/collection_store'; import { SearchResult } from '~/types/search'; const SearchInput = styled.input(({ theme }) => ({ diff --git a/inertia/components/dashboard/search/search_result_item.tsx b/inertia/components/dashboard/search/search_result_item.tsx index 2211878..4c55fcd 100644 --- a/inertia/components/dashboard/search/search_result_item.tsx +++ b/inertia/components/dashboard/search/search_result_item.tsx @@ -4,7 +4,7 @@ import { AiOutlineFolder } from 'react-icons/ai'; import Legend from '~/components/common/legend'; import TextEllipsis from '~/components/common/text_ellipsis'; import LinkFavicon from '~/components/dashboard/link/link_favicon'; -import useCollections from '~/hooks/use_collections'; +import { useCollections } from '~/store/collection_store'; import { SearchResult, SearchResultCollection, diff --git a/inertia/components/dashboard/side_nav/favorite/favorite_dropdown_item.tsx b/inertia/components/dashboard/side_nav/favorite/favorite_dropdown_item.tsx index d8e0da8..712c657 100644 --- a/inertia/components/dashboard/side_nav/favorite/favorite_dropdown_item.tsx +++ b/inertia/components/dashboard/side_nav/favorite/favorite_dropdown_item.tsx @@ -1,11 +1,9 @@ import styled from '@emotion/styled'; -import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; import { DropdownItemButton } from '~/components/common/dropdown/dropdown_item'; -import useActiveCollection from '~/hooks/use_active_collection'; -import useCollections from '~/hooks/use_collections'; import { onFavorite } from '~/lib/favorite'; +import { useFavorites } from '~/store/collection_store'; import { Link } from '~/types/app'; const StarItem = styled(DropdownItemButton)(({ theme }) => ({ @@ -13,34 +11,9 @@ const StarItem = styled(DropdownItemButton)(({ theme }) => ({ })); export default function FavoriteDropdownItem({ link }: { link: Link }) { - const { collections, setCollections } = useCollections(); - const { setActiveCollection } = useActiveCollection(); + const { toggleFavorite } = useFavorites(); const { t } = useTranslation(); - const toggleFavorite = useCallback( - (linkId: Link['id']) => { - let linkIndex = 0; - const collectionIndex = collections.findIndex(({ links }) => { - const lIndex = links.findIndex((l) => l.id === linkId); - if (lIndex !== -1) { - linkIndex = lIndex; - } - return lIndex !== -1; - }); - - const collectionLink = collections[collectionIndex].links[linkIndex]; - const collectionsCopy = [...collections]; - collectionsCopy[collectionIndex].links[linkIndex] = { - ...collectionLink, - favorite: !collectionLink.favorite, - }; - - setCollections(collectionsCopy); - setActiveCollection(collectionsCopy[collectionIndex]); - }, - [collections, setCollections] - ); - const onFavoriteCallback = () => toggleFavorite(link.id); return ( ({ color: theme.colors.grey, diff --git a/inertia/components/dashboard/side_nav/side_navigation.tsx b/inertia/components/dashboard/side_nav/side_navigation.tsx index 330159e..9dd3bb3 100644 --- a/inertia/components/dashboard/side_nav/side_navigation.tsx +++ b/inertia/components/dashboard/side_nav/side_navigation.tsx @@ -8,10 +8,10 @@ import FavoriteList from '~/components/dashboard/side_nav/favorite/favorite_list import { Item, ItemLink } from '~/components/dashboard/side_nav/nav_item'; import UserCard from '~/components/dashboard/side_nav/user_card'; import ModalSettings from '~/components/settings/settings_modal'; -import useActiveCollection from '~/hooks/use_active_collection'; import useUser from '~/hooks/use_user'; import { rgba } from '~/lib/color'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; const SideMenu = styled.nav(({ theme }) => ({ height: '100%', diff --git a/inertia/hooks/mantine/use_disable_overflow.ts b/inertia/hooks/mantine/use_disable_overflow.ts new file mode 100644 index 0000000..6fc7270 --- /dev/null +++ b/inertia/hooks/mantine/use_disable_overflow.ts @@ -0,0 +1,9 @@ +import { useEffect } from 'react'; + +export const useDisableOverflow = () => + useEffect(() => { + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = 'auto'; + }; + }, []); diff --git a/inertia/hooks/use_active_collection.tsx b/inertia/hooks/use_active_collection.tsx deleted file mode 100644 index dfb6710..0000000 --- a/inertia/hooks/use_active_collection.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { useContext } from 'react'; -import { ActiveCollectionContext } from '~/contexts/active_collection_context'; - -const useActiveCollection = () => useContext(ActiveCollectionContext); -export default useActiveCollection; diff --git a/inertia/hooks/use_collections.tsx b/inertia/hooks/use_collections.tsx deleted file mode 100644 index 4ce2215..0000000 --- a/inertia/hooks/use_collections.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { useContext } from 'react'; -import CollectionsContext from '~/contexts/collections_context'; - -const useCollections = () => useContext(CollectionsContext); -export default useCollections; diff --git a/inertia/hooks/use_shortcut.tsx b/inertia/hooks/use_shortcut.tsx index c95f825..d467af7 100644 --- a/inertia/hooks/use_shortcut.tsx +++ b/inertia/hooks/use_shortcut.tsx @@ -1,6 +1,6 @@ import KEYS from '#constants/keys'; import { useHotkeys } from 'react-hotkeys-hook'; -import useGlobalHotkeys from '~/hooks/use_global_hotkeys'; +import { useGlobalHotkeysStore } from '~/store/global_hotkeys_store'; type ShortcutOptions = { enabled?: boolean; @@ -15,7 +15,7 @@ export default function useShortcut( disableGlobalCheck: false, } ) { - const { globalHotkeysEnabled } = useGlobalHotkeys(); + const { globalHotkeysEnabled } = useGlobalHotkeysStore(); return useHotkeys( KEYS[key], (event) => { diff --git a/inertia/mantine/components/dashboard/collection/item/collection_item.tsx b/inertia/mantine/components/dashboard/collection/item/collection_item.tsx index c038c14..50abd15 100644 --- a/inertia/mantine/components/dashboard/collection/item/collection_item.tsx +++ b/inertia/mantine/components/dashboard/collection/item/collection_item.tsx @@ -3,8 +3,8 @@ import { route } from '@izzyjs/route/client'; import { Text } from '@mantine/core'; import { useEffect, useRef } from 'react'; import { AiFillFolderOpen, AiOutlineFolder } from 'react-icons/ai'; -import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; import { CollectionWithLinks } from '~/types/app'; import classes from './collection_item.module.css'; diff --git a/inertia/mantine/components/dashboard/collection/list/collection_list.tsx b/inertia/mantine/components/dashboard/collection/list/collection_list.tsx index 4dd8bf3..829bbca 100644 --- a/inertia/mantine/components/dashboard/collection/list/collection_list.tsx +++ b/inertia/mantine/components/dashboard/collection/list/collection_list.tsx @@ -1,9 +1,8 @@ import { Box, ScrollArea, Text } from '@mantine/core'; import { useTranslation } from 'react-i18next'; -import useActiveCollection from '~/hooks/use_active_collection'; -import useCollections from '~/hooks/use_collections'; import useShortcut from '~/hooks/use_shortcut'; import CollectionItem from '~/mantine/components/dashboard/collection/item/collection_item'; +import { useActiveCollection, useCollections } from '~/store/collection_store'; import styles from './collection_list.module.css'; export default function CollectionList() { diff --git a/inertia/mantine/components/dashboard/dashboard_header.tsx b/inertia/mantine/components/dashboard/dashboard_header.tsx index 5f04d93..9bf46b0 100644 --- a/inertia/mantine/components/dashboard/dashboard_header.tsx +++ b/inertia/mantine/components/dashboard/dashboard_header.tsx @@ -6,8 +6,8 @@ import { BsThreeDotsVertical } from 'react-icons/bs'; import { GoPencil } from 'react-icons/go'; import { IoIosAddCircleOutline } from 'react-icons/io'; import { IoTrashOutline } from 'react-icons/io5'; -import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; interface DashboardHeaderProps { navbar: { diff --git a/inertia/mantine/components/dashboard/favorite/favorite_list.tsx b/inertia/mantine/components/dashboard/favorite/favorite_list.tsx index 2f09d40..2b7dd7f 100644 --- a/inertia/mantine/components/dashboard/favorite/favorite_list.tsx +++ b/inertia/mantine/components/dashboard/favorite/favorite_list.tsx @@ -1,7 +1,7 @@ import { Box, Group, ScrollArea, Stack, Text } from '@mantine/core'; import { useTranslation } from 'react-i18next'; -import useFavorites from '~/hooks/use_favorites'; import { FavoriteItem } from '~/mantine/components/dashboard/favorite/item/favorite_item'; +import { useFavorites } from '~/store/collection_store'; import styles from './favorite_list.module.css'; export function FavoriteList() { diff --git a/inertia/mantine/components/dashboard/link/no_link.tsx b/inertia/mantine/components/dashboard/link/no_link.tsx index 0daf7d0..26c2314 100644 --- a/inertia/mantine/components/dashboard/link/no_link.tsx +++ b/inertia/mantine/components/dashboard/link/no_link.tsx @@ -2,8 +2,8 @@ import { Link } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { Anchor, Box, Text } from '@mantine/core'; import { useTranslation } from 'react-i18next'; -import useActiveCollection from '~/hooks/use_active_collection'; import { appendCollectionId } from '~/lib/navigation'; +import { useActiveCollection } from '~/store/collection_store'; import styles from './no_link.module.css'; export function NoLink() { diff --git a/inertia/pages/mantine_dashboard.tsx b/inertia/pages/mantine_dashboard.tsx index 7f11fac..9428960 100644 --- a/inertia/pages/mantine_dashboard.tsx +++ b/inertia/pages/mantine_dashboard.tsx @@ -1,15 +1,23 @@ +import { router } from '@inertiajs/react'; +import { route } from '@izzyjs/route/client'; import { AppShell, ScrollArea, Stack } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { useEffect } from 'react'; -import DashboardProviders from '~/components/dashboard/dashboard_provider'; import LinkItem from '~/components/dashboard/link/link_item'; import { MantineFooter } from '~/components/footer/mantine_footer'; +import { useDisableOverflow } from '~/hooks/mantine/use_disable_overflow'; import useShortcut from '~/hooks/use_shortcut'; +import { appendCollectionId } from '~/lib/navigation'; import { DashboardAside } from '~/mantine/components/dashboard/dashboard_aside'; import { DashboardHeader } from '~/mantine/components/dashboard/dashboard_header'; import { DashboardNavbar } from '~/mantine/components/dashboard/dashboard_navbar'; import { NoLink } from '~/mantine/components/dashboard/link/no_link'; import { MantineDashboardLayout } from '~/mantine/layouts/mantine_dashboard_layout'; +import { + useActiveCollection, + useCollectionsSetter, +} from '~/store/collection_store'; +import { useGlobalHotkeysStore } from '~/store/global_hotkeys_store'; import { CollectionWithLinks } from '~/types/app'; import classes from './dashboard.module.css'; @@ -24,71 +32,91 @@ export default function MantineDashboard(props: Readonly) { const [openedAside, { toggle: toggleAside, close: closeAside }] = useDisclosure(); + const { activeCollection } = useActiveCollection(); + const { _setCollections, setActiveCollection } = useCollectionsSetter(); + const { globalHotkeysEnabled } = useGlobalHotkeysStore(); + useShortcut('ESCAPE_KEY', () => { closeNavbar(); closeAside(); }); + useDisableOverflow(); + useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { - document.body.style.overflow = 'auto'; - }; + _setCollections(props.collections); + setActiveCollection(props.activeCollection); }, []); + useShortcut( + 'OPEN_CREATE_LINK_KEY', + () => + router.visit( + appendCollectionId(route('link.create-form').url, activeCollection?.id) + ), + { + enabled: globalHotkeysEnabled, + } + ); + useShortcut( + 'OPEN_CREATE_COLLECTION_KEY', + () => router.visit(route('collection.create-form').url), + { + enabled: globalHotkeysEnabled, + } + ); + return ( - -
- - - - - {props.activeCollection.links.length > 0 ? ( - - - {props.activeCollection.links.map((link) => ( - - ))} - - - ) : ( - - )} - - - - - - -
-
+
+ + + + + {activeCollection?.links && activeCollection.links.length > 0 ? ( + + + {activeCollection?.links.map((link) => ( + + ))} + + + ) : ( + + )} + + + + + + +
); } diff --git a/inertia/store/collection_store.ts b/inertia/store/collection_store.ts new file mode 100644 index 0000000..9654cf0 --- /dev/null +++ b/inertia/store/collection_store.ts @@ -0,0 +1,91 @@ +import { create } from 'zustand'; +import { useShallow } from 'zustand/react/shallow'; +import { CollectionWithLinks, Link, LinkWithCollection } from '~/types/app'; + +type Collections = CollectionWithLinks[]; + +interface CollectionStore { + collections: Collections; + _setCollections: (collections: Collections) => void; + + activeCollection: CollectionWithLinks | null; + setActiveCollection: (collection: CollectionWithLinks) => void; + + favorites: LinkWithCollection[]; + toggleFavorite: (link: Link['id']) => void; +} + +export const useCollectionStore = create((set, get) => ({ + collections: [], + _setCollections: (collections) => { + const favorites = getFavoriteLinks(collections); + set({ collections, favorites }); + }, + + activeCollection: null, + setActiveCollection: (collection) => set({ activeCollection: collection }), + + favorites: [], + toggleFavorite: (linkId) => { + const { collections } = get(); + let linkIndex = 0; + const collectionIndex = collections.findIndex(({ links }) => { + const lIndex = links.findIndex((l) => l.id === linkId); + if (lIndex !== -1) { + linkIndex = lIndex; + } + return lIndex !== -1; + }); + + const collectionLink = collections[collectionIndex].links[linkIndex]; + const collectionsCopy = [...collections]; + collectionsCopy[collectionIndex].links[linkIndex] = { + ...collectionLink, + favorite: !collectionLink.favorite, + }; + + set({ + collections: collectionsCopy, + activeCollection: collectionsCopy[collectionIndex], + favorites: getFavoriteLinks(collectionsCopy), + }); + }, +})); + +export const useActiveCollection = () => + useCollectionStore( + useShallow((state) => ({ + activeCollection: state.activeCollection, + setActiveCollection: state.setActiveCollection, + })) + ); + +export const useCollections = () => + useCollectionStore( + useShallow((state) => ({ collections: state.collections })) + ); + +export const useFavorites = () => + useCollectionStore( + useShallow((state) => ({ + favorites: state.favorites, + toggleFavorite: state.toggleFavorite, + })) + ); + +export function useCollectionsSetter() { + const { _setCollections, setActiveCollection } = useCollectionStore(); + return { _setCollections, setActiveCollection }; +} + +function getFavoriteLinks(collections: Collections) { + return collections.reduce((acc, collection) => { + collection.links.forEach((link) => { + if (link.favorite) { + const newLink: LinkWithCollection = { ...link, collection }; + acc.push(newLink); + } + }); + return acc; + }, [] as LinkWithCollection[]); +} diff --git a/inertia/store/global_hotkeys_store.ts b/inertia/store/global_hotkeys_store.ts new file mode 100644 index 0000000..d819baa --- /dev/null +++ b/inertia/store/global_hotkeys_store.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; + +interface GlobalHotkeysStore { + globalHotkeysEnabled: boolean; + setGlobalHotkeysEnabled: (value: boolean) => void; +} + +export const useGlobalHotkeysStore = create((set) => ({ + globalHotkeysEnabled: true, + setGlobalHotkeysEnabled: (value) => set({ globalHotkeysEnabled: value }), +})); diff --git a/package.json b/package.json index befdc14..a960701 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "node bin/server.js", "build": "node ace build", - "dev": "node ace serve --hmr", + "dev": "node ace serve --watch", "test": "node ace test", "lint": "eslint . --report-unused-disable-directives --max-warnings 0", "format": "prettier --write --parser typescript '**/*.{ts,tsx}'", @@ -106,7 +106,8 @@ "react-select": "^5.8.2", "react-swipeable": "^7.0.1", "react-toggle": "^4.1.3", - "reflect-metadata": "^0.2.2" + "reflect-metadata": "^0.2.2", + "zustand": "^5.0.1" }, "hotHook": { "boundaries": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f2eef0..4b08110 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: reflect-metadata: specifier: ^0.2.2 version: 0.2.2 + zustand: + specifier: ^5.0.1 + version: 5.0.1(@types/react@18.3.12)(react@18.3.1) devDependencies: '@adonisjs/assembler': specifier: ^7.8.2 @@ -5115,6 +5118,24 @@ packages: engines: {node: '>=8.0.0'} hasBin: true + zustand@5.0.1: + resolution: {integrity: sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@adonisjs/ace@13.3.0': @@ -10181,3 +10202,8 @@ snapshots: validator: 13.12.0 optionalDependencies: commander: 9.5.0 + + zustand@5.0.1(@types/react@18.3.12)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.12 + react: 18.3.1