mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 07:03:25 +00:00
refactor: recreate collection and global hotkey contexts as store (using zustand)
This commit is contained in:
@@ -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<HTMLDivElement>(null);
|
||||
const { setGlobalHotkeysEnabled } = useGlobalHotkeys();
|
||||
const { setGlobalHotkeysEnabled } = useGlobalHotkeysStore();
|
||||
|
||||
useClickOutside(modalRef, close);
|
||||
useShortcut('ESCAPE_KEY', close, { disableGlobalCheck: true });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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%',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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%',
|
||||
|
||||
@@ -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 (
|
||||
<Menu withinPortal shadow="md" width={200}>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 }) => ({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<StarItem
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FavoriteListContainer from '~/components/dashboard/side_nav/favorite/favorite_container';
|
||||
import FavoriteItem from '~/components/dashboard/side_nav/favorite/favorite_item';
|
||||
import useFavorites from '~/hooks/use_favorites';
|
||||
import { useFavorites } from '~/store/collection_store';
|
||||
|
||||
const FavoriteLabel = styled.p(({ theme }) => ({
|
||||
color: theme.colors.grey,
|
||||
|
||||
@@ -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%',
|
||||
|
||||
9
inertia/hooks/mantine/use_disable_overflow.ts
Normal file
9
inertia/hooks/mantine/use_disable_overflow.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useDisableOverflow = () =>
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
document.body.style.overflow = 'auto';
|
||||
};
|
||||
}, []);
|
||||
@@ -1,5 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import { ActiveCollectionContext } from '~/contexts/active_collection_context';
|
||||
|
||||
const useActiveCollection = () => useContext(ActiveCollectionContext);
|
||||
export default useActiveCollection;
|
||||
@@ -1,5 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import CollectionsContext from '~/contexts/collections_context';
|
||||
|
||||
const useCollections = () => useContext(CollectionsContext);
|
||||
export default useCollections;
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<DashboardPageProps>) {
|
||||
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 (
|
||||
<MantineDashboardLayout>
|
||||
<DashboardProviders {...props}>
|
||||
<div className={classes.app_wrapper}>
|
||||
<AppShell
|
||||
layout="alt"
|
||||
header={{ height: 50 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: 'sm',
|
||||
collapsed: { mobile: !openedNavbar },
|
||||
}}
|
||||
aside={{
|
||||
width: 300,
|
||||
breakpoint: 'md',
|
||||
collapsed: { mobile: !openedAside },
|
||||
}}
|
||||
classNames={{
|
||||
aside: classes.ml_custom_class,
|
||||
footer: classes.ml_custom_class,
|
||||
navbar: classes.ml_custom_class,
|
||||
header: classes.ml_custom_class,
|
||||
}}
|
||||
className={classes.app_shell}
|
||||
>
|
||||
<DashboardHeader
|
||||
navbar={{ opened: openedNavbar, toggle: toggleNavbar }}
|
||||
aside={{ opened: openedAside, toggle: toggleAside }}
|
||||
/>
|
||||
<DashboardNavbar isOpen={openedNavbar} toggle={toggleNavbar} />
|
||||
<AppShell.Main>
|
||||
{props.activeCollection.links.length > 0 ? (
|
||||
<ScrollArea
|
||||
h="calc(100vh - var(--app-shell-header-height, 0px) - var(--app-shell-footer-height, 0px))"
|
||||
p="md"
|
||||
>
|
||||
<Stack gap="xs">
|
||||
{props.activeCollection.links.map((link) => (
|
||||
<LinkItem key={link.id} link={link} showUserControls />
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<NoLink key={props.activeCollection.id} />
|
||||
)}
|
||||
</AppShell.Main>
|
||||
<DashboardAside isOpen={openedAside} toggle={toggleAside} />
|
||||
<AppShell.Footer pl="xs" pr="xs">
|
||||
<MantineFooter />
|
||||
</AppShell.Footer>
|
||||
</AppShell>
|
||||
</div>
|
||||
</DashboardProviders>
|
||||
<div className={classes.app_wrapper}>
|
||||
<AppShell
|
||||
layout="alt"
|
||||
header={{ height: 50 }}
|
||||
navbar={{
|
||||
width: 300,
|
||||
breakpoint: 'sm',
|
||||
collapsed: { mobile: !openedNavbar },
|
||||
}}
|
||||
aside={{
|
||||
width: 300,
|
||||
breakpoint: 'md',
|
||||
collapsed: { mobile: !openedAside },
|
||||
}}
|
||||
classNames={{
|
||||
aside: classes.ml_custom_class,
|
||||
footer: classes.ml_custom_class,
|
||||
navbar: classes.ml_custom_class,
|
||||
header: classes.ml_custom_class,
|
||||
}}
|
||||
className={classes.app_shell}
|
||||
>
|
||||
<DashboardHeader
|
||||
navbar={{ opened: openedNavbar, toggle: toggleNavbar }}
|
||||
aside={{ opened: openedAside, toggle: toggleAside }}
|
||||
/>
|
||||
<DashboardNavbar isOpen={openedNavbar} toggle={toggleNavbar} />
|
||||
<AppShell.Main>
|
||||
{activeCollection?.links && activeCollection.links.length > 0 ? (
|
||||
<ScrollArea
|
||||
h="calc(100vh - var(--app-shell-header-height, 0px) - var(--app-shell-footer-height, 0px))"
|
||||
p="md"
|
||||
>
|
||||
<Stack gap="xs">
|
||||
{activeCollection?.links.map((link) => (
|
||||
<LinkItem key={link.id} link={link} showUserControls />
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<NoLink key={activeCollection?.id} />
|
||||
)}
|
||||
</AppShell.Main>
|
||||
<DashboardAside isOpen={openedAside} toggle={toggleAside} />
|
||||
<AppShell.Footer pl="xs" pr="xs">
|
||||
<MantineFooter />
|
||||
</AppShell.Footer>
|
||||
</AppShell>
|
||||
</div>
|
||||
</MantineDashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
91
inertia/store/collection_store.ts
Normal file
91
inertia/store/collection_store.ts
Normal file
@@ -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<CollectionStore>((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[]);
|
||||
}
|
||||
11
inertia/store/global_hotkeys_store.ts
Normal file
11
inertia/store/global_hotkeys_store.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface GlobalHotkeysStore {
|
||||
globalHotkeysEnabled: boolean;
|
||||
setGlobalHotkeysEnabled: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const useGlobalHotkeysStore = create<GlobalHotkeysStore>((set) => ({
|
||||
globalHotkeysEnabled: true,
|
||||
setGlobalHotkeysEnabled: (value) => set({ globalHotkeysEnabled: value }),
|
||||
}));
|
||||
@@ -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": [
|
||||
|
||||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user