From 00d386b5eb75beedc3625bf82c31425b35ff9491 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Sun, 9 Jun 2024 10:23:57 +0800 Subject: [PATCH] add hover menu --- .../networking/queries/useGetHighlights.tsx | 1 + packages/web/pages/highlights.tsx | 274 ++++++++++++++---- 2 files changed, 214 insertions(+), 61 deletions(-) diff --git a/packages/web/lib/networking/queries/useGetHighlights.tsx b/packages/web/lib/networking/queries/useGetHighlights.tsx index 505bf4e28..09edb9a33 100644 --- a/packages/web/lib/networking/queries/useGetHighlights.tsx +++ b/packages/web/lib/networking/queries/useGetHighlights.tsx @@ -48,6 +48,7 @@ export const useGetHighlights = ( id title author + slug } } cursor diff --git a/packages/web/pages/highlights.tsx b/packages/web/pages/highlights.tsx index cf62a1f0d..5c760e3c9 100644 --- a/packages/web/pages/highlights.tsx +++ b/packages/web/pages/highlights.tsx @@ -1,31 +1,51 @@ -import { useRouter } from 'next/router' +import { + autoUpdate, + offset, + size, + useFloating, + useHover, + useInteractions, +} from '@floating-ui/react' +import { NextRouter, useRouter } from 'next/router' import { useCallback, useMemo, useState } from 'react' +import { Toaster } from 'react-hot-toast' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' +import { TrashIcon } from '../components/elements/icons/TrashIcon' import { LabelChip } from '../components/elements/LabelChip' import { Box, HStack, VStack } from '../components/elements/LayoutPrimitives' +import { ConfirmationModal } from '../components/patterns/ConfirmationModal' +import { HighlightHoverActions } from '../components/patterns/HighlightHoverActions' import { HighlightViewNote } from '../components/patterns/HighlightNotes' +import { SetHighlightLabelsModalPresenter } from '../components/templates/article/SetLabelsModalPresenter' import { EmptyHighlights } from '../components/templates/homeFeed/EmptyHighlights' import { LibraryFilterMenu } from '../components/templates/navMenu/LibraryMenu' +import { theme } from '../components/tokens/stitches.config' import { useApplyLocalTheme } from '../lib/hooks/useApplyLocalTheme' import { useFetchMore } from '../lib/hooks/useFetchMoreScroll' import { Highlight } from '../lib/networking/fragments/highlightFragment' +import { deleteHighlightMutation } from '../lib/networking/mutations/deleteHighlightMutation' import { useGetHighlights } from '../lib/networking/queries/useGetHighlights' +import { + useGetViewerQuery, + UserBasicData, +} from '../lib/networking/queries/useGetViewerQuery' import { highlightColor } from '../lib/themeUpdater' +import { showErrorToast, showSuccessToast } from '../lib/toastHelpers' const PAGE_SIZE = 10 export default function HighlightsPage(): JSX.Element { const router = useRouter() + const viewer = useGetViewerQuery() const [showFilterMenu, setShowFilterMenu] = useState(false) const [_, setShowAddLinkModal] = useState(false) useApplyLocalTheme() - const { isLoading, isValidating, mutate, setSize, size, data, error } = - useGetHighlights({ - first: PAGE_SIZE, - }) + const { isLoading, setSize, size, data, mutate } = useGetHighlights({ + first: PAGE_SIZE, + }) const hasMore = useMemo(() => { if (!data) { @@ -65,6 +85,8 @@ export default function HighlightsPage(): JSX.Element { return ( + + {highlights.map((highlight) => { - return + return ( + viewer.viewerData?.me && ( + + ) + ) })} @@ -91,11 +123,18 @@ export default function HighlightsPage(): JSX.Element { type HighlightCardProps = { highlight: Highlight + viewer: UserBasicData + router: NextRouter + mutate: () => void } -type HighlightAnnotationProps = HighlightCardProps +type HighlightAnnotationProps = { + highlight: Highlight +} -function HighlightAnnotation({ highlight }: HighlightAnnotationProps): JSX.Element { +function HighlightAnnotation({ + highlight, +}: HighlightAnnotationProps): JSX.Element { const [noteMode, setNoteMode] = useState<'edit' | 'preview'>('preview') const [annotation, setAnnotation] = useState(highlight.annotation) @@ -115,8 +154,63 @@ function HighlightAnnotation({ highlight }: HighlightAnnotationProps): JSX.Eleme } function HighlightCard(props: HighlightCardProps): JSX.Element { + const [isOpen, setIsOpen] = useState(false) + const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = + useState(undefined) + const [labelsTarget, setLabelsTarget] = useState( + undefined + ) + + const viewInReader = useCallback( + (highlightId: string) => { + const router = props.router + const viewer = props.viewer + const item = props.highlight.libraryItem + + if (!router || !router.isReady || !viewer || !item) { + showErrorToast('Error navigating to highlight') + return + } + + router.push( + { + pathname: '/[username]/[slug]', + query: { + username: viewer.profile.username, + slug: item.slug, + }, + hash: highlightId, + }, + `${viewer.profile.username}/${item.slug}#${highlightId}`, + { + scroll: false, + } + ) + }, + [props.highlight.libraryItem, props.viewer, props.router] + ) + + const { refs, floatingStyles, context } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + middleware: [ + offset({ + mainAxis: -25, + }), + size(), + ], + placement: 'top-end', + whileElementsMounted: autoUpdate, + }) + + const hover = useHover(context) + + const { getReferenceProps, getFloatingProps } = useInteractions([hover]) + return ( - - - - {props.highlight.quote && ( - - {props.highlight.quote} - - )} - - {props.highlight.labels && ( - - {props.highlight.labels.map((label) => { - return ( - - ) - })} - - )} - + + {props.highlight.quote && ( + + {props.highlight.quote} + + )} + + {props.highlight.labels && ( + - {props.highlight.libraryItem?.title} - - { + return ( + + ) + })} + + )} + + {props.highlight.libraryItem?.title} + + + {props.highlight.libraryItem?.author} + + {showConfirmDeleteHighlightId && ( + { + ;(async () => { + const highlightId = showConfirmDeleteHighlightId + const success = await deleteHighlightMutation( + props.highlight.libraryItem?.id || '', + showConfirmDeleteHighlightId + ) + props.mutate() + if (success) { + showSuccessToast('Highlight deleted.', { + position: 'bottom-right', + }) + const event = new CustomEvent('deleteHighlightbyId', { + detail: highlightId, + }) + document.dispatchEvent(event) + } else { + showErrorToast('Error deleting highlight', { + position: 'bottom-right', + }) + } + })() + setShowConfirmDeleteHighlightId(undefined) }} - > - {props.highlight.libraryItem?.author} - - - + onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)} + icon={ + + } + /> + )} + {labelsTarget && ( + { + // Don't actually need to do something here + console.log('update highlight: ', highlight) + }} + onOpenChange={() => { + props.mutate() + setLabelsTarget(undefined) + }} + /> + )} + ) }