diff --git a/projects/bp-gallery/cypress/e2e/picture-grid.cy.ts b/projects/bp-gallery/cypress/e2e/picture-grid.cy.ts index 95844c4c0..a5d1e642f 100644 --- a/projects/bp-gallery/cypress/e2e/picture-grid.cy.ts +++ b/projects/bp-gallery/cypress/e2e/picture-grid.cy.ts @@ -17,8 +17,8 @@ describe('picture grid', () => { cy.visit('/archives/1'); cy.get('.overview-container .picture-preview').first().click(); cy.url().should('contain', '/picture/'); - cy.get('.picture-navigation-buttons [data-testid="ChevronRightIcon"]').click(); - cy.get('.picture-navigation-buttons [data-testid="ChevronRightIcon"]').click(); + cy.get('.picture-navigation-buttons [data-testid="next"]').click(); + cy.get('.picture-navigation-buttons [data-testid="next"]').click(); cy.contains('Zurück').click(); urlIs('/archives/1'); }); diff --git a/projects/bp-gallery/src/components/views/picture/PictureView.tsx b/projects/bp-gallery/src/components/views/picture/PictureView.tsx index 61070d559..26bfb7e8a 100644 --- a/projects/bp-gallery/src/components/views/picture/PictureView.tsx +++ b/projects/bp-gallery/src/components/views/picture/PictureView.tsx @@ -33,6 +33,8 @@ export interface PictureViewContextFields { navigatePicture?: (target: PictureNavigationTarget) => void; hasNext?: boolean; hasPrevious?: boolean; + hasNextInSequence?: boolean; + hasPreviousInSequence?: boolean; sideBarOpen?: boolean; noDistractionMode?: boolean; setSideBarOpen?: Dispatch>; @@ -45,6 +47,11 @@ export const PictureViewContext = createContext({ img: // Used for the sidebar (in px) --> same as in shared.scss const MOBILE_BREAKPOINT = 750; +export type PictureIds = { + pictureInSiblingsId: string; + pictureInSequenceId: string; +}; + const PictureView = ({ initialPictureId, siblingIds, @@ -60,7 +67,9 @@ const PictureView = ({ const containerRef = useRef(null); - const [pictureId, setPictureId] = useState(initialPictureId); + const [pictureInSiblingsId, setPictureInSiblingsId] = useState(initialPictureId); + const [pictureInSequenceId, setPictureInSequenceId] = useState(initialPictureId); + const [sideBarOpen, setSideBarOpen] = useState(false); useEffect(() => { @@ -70,10 +79,14 @@ const PictureView = ({ }, []); useEffect(() => { - if (fetchMore && siblingIds && siblingIds.indexOf(pictureId) === siblingIds.length - 1) { - fetchMore(pictureId); + if ( + fetchMore && + siblingIds && + siblingIds.indexOf(pictureInSiblingsId) === siblingIds.length - 1 + ) { + fetchMore(pictureInSiblingsId); } - }, [fetchMore, siblingIds, pictureId]); + }, [fetchMore, siblingIds, pictureInSiblingsId]); const search = window.location.search; const [sessionId, isPresentationMode] = useMemo((): [string, boolean] => { @@ -85,39 +98,75 @@ const PictureView = ({ ]; }, [search]); - const [hasPrevious, hasNext] = useMemo(() => { - return [ - Boolean(getPreviousPictureId(pictureId, siblingIds)), - Boolean(getNextPictureId(pictureId, siblingIds)), - ]; - }, [pictureId, siblingIds]); - - // Api connection - usePrefetchPictureHook(pictureId, siblingIds); - - const { data, loading, error } = useGetPictureInfoQuery({ variables: { pictureId } }); + const { data, loading, error } = useGetPictureInfoQuery({ + variables: { pictureId: pictureInSequenceId }, + }); const picture: FlatPicture | undefined = useSimplifiedQueryResponseData(data)?.picture; const pictureLink = asUploadPath(picture?.media); + const pictureSequenceIds = useMemo( + () => picture?.picture_sequence?.pictures?.map(picture => picture.id), + [picture] + ); - const onNavigateMessage = useCallback((pictureId: string) => { - replaceHistoryWithoutRouter(`/picture/${pictureId}${window.location.search}`); - setPictureId(pictureId); - }, []); + usePrefetchPictureHook( + { pictureInSiblingsId, pictureInSequenceId }, + siblingIds, + pictureSequenceIds + ); + + const [hasPrevious, hasNext, hasPreviousInSequence, hasNextInSequence] = useMemo( + () => [ + Boolean(getPreviousPictureId(pictureInSiblingsId, siblingIds)), + Boolean(getNextPictureId(pictureInSiblingsId, siblingIds)), + Boolean(getPreviousPictureId(pictureInSequenceId, pictureSequenceIds)), + Boolean(getNextPictureId(pictureInSequenceId, pictureSequenceIds)), + ], + [pictureInSequenceId, pictureInSiblingsId, pictureSequenceIds, siblingIds] + ); + + const onNavigateMessage = useCallback( + ({ pictureInSiblingsId, pictureInSequenceId }: PictureIds) => { + replaceHistoryWithoutRouter(`/picture/${pictureInSequenceId}${window.location.search}`); + setPictureInSiblingsId(pictureInSiblingsId); + setPictureInSequenceId(pictureInSequenceId); + }, + [] + ); const navigateToPicture = usePresentationChannel(sessionId, onNavigateMessage); // Call the previous or next picture const navigatePicture = useCallback( (target: PictureNavigationTarget) => { - const targetId = - target === PictureNavigationTarget.NEXT - ? getNextPictureId(pictureId, siblingIds) - : getPreviousPictureId(pictureId, siblingIds); - if (targetId) { - navigateToPicture(targetId); + const targetIds: Partial = { + pictureInSequenceId, + pictureInSiblingsId, + }; + switch (target) { + case PictureNavigationTarget.NEXT: + targetIds.pictureInSiblingsId = getNextPictureId(pictureInSiblingsId, siblingIds); + break; + case PictureNavigationTarget.PREVIOUS: + targetIds.pictureInSiblingsId = getPreviousPictureId(pictureInSiblingsId, siblingIds); + break; + case PictureNavigationTarget.NEXT_IN_SEQUENCE: + targetIds.pictureInSequenceId = getNextPictureId(pictureInSequenceId, pictureSequenceIds); + break; + case PictureNavigationTarget.PREVIOUS_IN_SEQUENCE: + targetIds.pictureInSequenceId = getPreviousPictureId( + pictureInSequenceId, + pictureSequenceIds + ); + break; + } + if (targetIds.pictureInSiblingsId && targetIds.pictureInSequenceId) { + if (targetIds.pictureInSiblingsId !== pictureInSiblingsId) { + targetIds.pictureInSequenceId = targetIds.pictureInSiblingsId; + } + navigateToPicture(targetIds as PictureIds); } }, - [pictureId, siblingIds, navigateToPicture] + [pictureInSequenceId, pictureInSiblingsId, siblingIds, pictureSequenceIds, navigateToPicture] ); const [img, setImg] = useState(null); @@ -131,6 +180,8 @@ const PictureView = ({ navigatePicture, hasNext, hasPrevious, + hasNextInSequence, + hasPreviousInSequence, sideBarOpen, noDistractionMode, setSideBarOpen, @@ -144,20 +195,20 @@ const PictureView = ({ const unblock = history.block(() => { setSideBarOpen(false); if (onBack) { - onBack(pictureId); + onBack(pictureInSiblingsId); } }); return () => { unblock(); }; - }, [history, pictureId, onBack]); + }, [history, pictureInSiblingsId, onBack]); - const onImageContextMenu = useBlockImageContextMenuByPictureId(pictureId); + const onImageContextMenu = useBlockImageContextMenuByPictureId(pictureInSequenceId); return (
- +
diff --git a/projects/bp-gallery/src/components/views/picture/overlay/PictureNavigationButtons.tsx b/projects/bp-gallery/src/components/views/picture/overlay/PictureNavigationButtons.tsx index 7ceddec59..f112f3056 100644 --- a/projects/bp-gallery/src/components/views/picture/overlay/PictureNavigationButtons.tsx +++ b/projects/bp-gallery/src/components/views/picture/overlay/PictureNavigationButtons.tsx @@ -1,4 +1,4 @@ -import { ChevronLeft, ChevronRight } from '@mui/icons-material'; +import { ChevronLeft, ChevronRight, Filter } from '@mui/icons-material'; import { IconButton } from '@mui/material'; import { useContext, useEffect } from 'react'; import { PictureViewContext } from '../PictureView'; @@ -7,10 +7,13 @@ import { useNoDistractionModeStyle } from '../helpers/no-distraction-mode-style' export enum PictureNavigationTarget { NEXT, PREVIOUS, + NEXT_IN_SEQUENCE, + PREVIOUS_IN_SEQUENCE, } const PictureNavigationButtons = () => { - const { navigatePicture, hasNext, hasPrevious } = useContext(PictureViewContext); + const { navigatePicture, hasNext, hasPrevious, hasNextInSequence, hasPreviousInSequence } = + useContext(PictureViewContext); useEffect(() => { const navigateKeyboardAction = (event: KeyboardEvent) => { @@ -29,13 +32,14 @@ const PictureNavigationButtons = () => { const noDistractionModeStyle = useNoDistractionModeStyle(); return ( -
+
navigatePicture(PictureNavigationTarget.PREVIOUS) : undefined } size='large' + data-testid='previous' > @@ -43,9 +47,38 @@ const PictureNavigationButtons = () => { style={{ visibility: hasNext ? 'visible' : 'hidden' }} onClick={navigatePicture ? () => navigatePicture(PictureNavigationTarget.NEXT) : undefined} size='large' + data-testid='next' > +
+ navigatePicture(PictureNavigationTarget.PREVIOUS_IN_SEQUENCE) + : undefined + } + size='large' + data-testid='previous-in-sequence' + > + + + + navigatePicture(PictureNavigationTarget.NEXT_IN_SEQUENCE) + : undefined + } + size='large' + data-testid='next-in-sequence' + > + + + +
); }; diff --git a/projects/bp-gallery/src/hooks/prefetch.hook.ts b/projects/bp-gallery/src/hooks/prefetch.hook.ts index 0f41f04f5..4af4b61f3 100644 --- a/projects/bp-gallery/src/hooks/prefetch.hook.ts +++ b/projects/bp-gallery/src/hooks/prefetch.hook.ts @@ -1,34 +1,68 @@ import { useEffect } from 'react'; -import { useGetPictureInfoLazyQuery } from '../graphql/APIConnector'; +import { PictureIds } from '../components/views/picture/PictureView'; import { getNextPictureId, getPreviousPictureId, } from '../components/views/picture/helpers/next-prev-picture'; +import { useGetPictureInfoLazyQuery } from '../graphql/APIConnector'; -const usePrefetchPictureHook = (id: string, siblings?: string[]) => { - const [previousQuery] = useGetPictureInfoLazyQuery(); - const [nextQuery] = useGetPictureInfoLazyQuery(); +const usePrefetchPictureHook = ( + { pictureInSiblingsId, pictureInSequenceId }: PictureIds, + siblings?: string[], + pictureSequenceIds?: string[] +) => { + const [previousInSiblingsQuery] = useGetPictureInfoLazyQuery(); + const [nextInSiblingsQuery] = useGetPictureInfoLazyQuery(); + const [previousInSequenceQuery] = useGetPictureInfoLazyQuery(); + const [nextInSequenceQuery] = useGetPictureInfoLazyQuery(); useEffect(() => { - if (siblings?.includes(id)) { - const previousId = getPreviousPictureId(id, siblings); + if (siblings?.includes(pictureInSiblingsId)) { + const previousId = getPreviousPictureId(pictureInSiblingsId, siblings); + if (previousId) { + previousInSiblingsQuery({ + variables: { + pictureId: previousId, + }, + }); + } + const nextId = getNextPictureId(pictureInSiblingsId, siblings); + if (nextId) { + nextInSiblingsQuery({ + variables: { + pictureId: nextId, + }, + }); + } + } + if (pictureSequenceIds?.includes(pictureInSequenceId)) { + const previousId = getPreviousPictureId(pictureInSequenceId, pictureSequenceIds); if (previousId) { - previousQuery({ + previousInSequenceQuery({ variables: { pictureId: previousId, }, }); } - const nextId = getNextPictureId(id, siblings); + const nextId = getNextPictureId(pictureInSequenceId, pictureSequenceIds); if (nextId) { - nextQuery({ + nextInSequenceQuery({ variables: { pictureId: nextId, }, }); } } - }, [id, siblings, previousQuery, nextQuery]); + }, [ + pictureInSiblingsId, + siblings, + previousInSiblingsQuery, + nextInSiblingsQuery, + pictureSequenceIds, + pictureInSequenceId, + previousInSequenceQuery, + nextInSequenceQuery, + ]); }; export default usePrefetchPictureHook; diff --git a/projects/bp-gallery/src/hooks/presentation-channel.hook.ts b/projects/bp-gallery/src/hooks/presentation-channel.hook.ts index bf83fc35d..654a937c9 100644 --- a/projects/bp-gallery/src/hooks/presentation-channel.hook.ts +++ b/projects/bp-gallery/src/hooks/presentation-channel.hook.ts @@ -1,14 +1,15 @@ import { useCallback, useEffect, useRef } from 'react'; +import { PictureIds } from '../components/views/picture/PictureView'; import { FallbackChannel, channelFactory } from '../helpers/channel-helpers'; -const usePresentationChannel = (id: string, onNavigate: (pictureId: string) => void) => { +const usePresentationChannel = (id: string, onNavigate: (ids: PictureIds) => void) => { const producer = useRef(null); useEffect(() => { const consumer = channelFactory(id); producer.current = channelFactory(id); - consumer.onmessage = (event: MessageEvent<{ pictureId: string }>) => { - onNavigate(event.data.pictureId); + consumer.onmessage = (event: MessageEvent) => { + onNavigate(event.data); }; return () => { @@ -19,10 +20,8 @@ const usePresentationChannel = (id: string, onNavigate: (pictureId: string) => v }, [id, onNavigate]); return useCallback( - (targetId: string) => { - producer.current?.postMessage({ - pictureId: targetId, - }); + (ids: PictureIds) => { + producer.current?.postMessage(ids); }, [producer] );