From c992fe89330b4cf37f602cb2371fc152424cc7c3 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sun, 2 May 2021 17:28:11 +0200 Subject: [PATCH] Add gallery reducer for timeline view as well --- ui/.eslintrc.js | 1 + .../components/albumGallery/AlbumGallery.tsx | 63 +++-- .../components/photoGallery/PhotoGallery.tsx | 59 ++--- .../photoGallery/photoGalleryMutations.ts | 43 ++++ .../photoGallery/photoGalleryReducer.tsx | 95 ++++++-- .../presentView/PresentNavigationOverlay.tsx | 8 +- .../photoGallery/presentView/PresentView.tsx | 18 +- .../timelineGallery/TimelineGallery.tsx | 192 +++++---------- .../timelineGallery/TimelineGroupAlbum.tsx | 76 +++--- .../timelineGallery/TimelineGroupDate.tsx | 42 ++-- .../timelineGalleryReducer.tsx | 226 ++++++++++++++++++ 11 files changed, 528 insertions(+), 295 deletions(-) create mode 100644 ui/src/components/photoGallery/photoGalleryMutations.ts create mode 100644 ui/src/components/timelineGallery/timelineGalleryReducer.tsx diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 8f6d5706..f37f7f62 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -34,6 +34,7 @@ module.exports = { 'react/display-name': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', }, settings: { react: { diff --git a/ui/src/components/albumGallery/AlbumGallery.tsx b/ui/src/components/albumGallery/AlbumGallery.tsx index c916ba16..a9015501 100644 --- a/ui/src/components/albumGallery/AlbumGallery.tsx +++ b/ui/src/components/albumGallery/AlbumGallery.tsx @@ -1,11 +1,16 @@ -import React, { useReducer } from 'react' +import React, { useContext, useEffect, useReducer } from 'react' import AlbumTitle from '../AlbumTitle' import PhotoGallery from '../photoGallery/PhotoGallery' import AlbumBoxes from './AlbumBoxes' import AlbumFilter from '../AlbumFilter' import { albumQuery_album } from '../../Pages/AlbumPage/__generated__/albumQuery' import { OrderDirection } from '../../../__generated__/globalTypes' -import { photoGalleryReducer } from '../photoGallery/photoGalleryReducer' +import { + photoGalleryReducer, + urlPresentModeSetupHook, +} from '../photoGallery/photoGalleryReducer' +import { SidebarContext } from '../sidebar/Sidebar' +import MediaSidebar from '../sidebar/MediaSidebar' type AlbumGalleryProps = { album?: albumQuery_album @@ -33,10 +38,7 @@ const AlbumGallery = React.forwardRef( }: AlbumGalleryProps, ref: React.ForwardedRef ) => { - // const [imageState, setImageState] = useState({ - // activeImage: -1, - // presenting: false, - // }) + const { updateSidebar } = useContext(SidebarContext) const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, { presenting: false, @@ -44,38 +46,31 @@ const AlbumGallery = React.forwardRef( media: album?.media || [], }) - // const setPresentingWithHistory = (presenting: boolean) => { - // setPresenting(presenting) - // if (presenting) { - // history.pushState({ imageState }, '') - // } else { - // history.back() - // } - // } + useEffect(() => { + dispatchMedia({ type: 'replaceMedia', media: album?.media || [] }) + }, [album?.media]) - // const updateHistory = (imageState: ImageStateType) => { - // history.replaceState({ imageState }, '') - // return imageState - // } + useEffect(() => { + if (mediaState.activeIndex != -1) { + updateSidebar( + + ) + } else { + updateSidebar(null) + } + }, [mediaState.activeIndex]) - // useEffect(() => { - // const updateImageState = (event: PopStateEvent) => { - // setImageState(event.state.imageState) - // } - - // window.addEventListener('popstate', updateImageState) - - // return () => { - // window.removeEventListener('popstate', updateImageState) - // } - // }, [imageState]) - - // useEffect(() => { - // setActiveImage(-1) - // }, [album]) + urlPresentModeSetupHook({ + dispatchMedia, + openPresentMode: event => { + dispatchMedia({ + type: 'openPresentMode', + activeIndex: event.state.activeIndex, + }) + }, + }) let subAlbumElement = null - if (album) { if (album.subAlbums.length > 0) { subAlbumElement = ( diff --git a/ui/src/components/photoGallery/PhotoGallery.tsx b/ui/src/components/photoGallery/PhotoGallery.tsx index 6773c5ab..a0dbdbe5 100644 --- a/ui/src/components/photoGallery/PhotoGallery.tsx +++ b/ui/src/components/photoGallery/PhotoGallery.tsx @@ -1,31 +1,20 @@ -import React, { useContext } from 'react' +import React from 'react' import styled from 'styled-components' import { Loader } from 'semantic-ui-react' import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail' import PresentView from './presentView/PresentView' -import { SidebarContext } from '../sidebar/Sidebar' import { useTranslation } from 'react-i18next' import { PresentMediaProps_Media } from './presentView/PresentMedia' import { sidebarPhoto_media_thumbnail } from '../sidebar/__generated__/sidebarPhoto' import { + openPresentModeAction, PhotoGalleryAction, PhotoGalleryState, - selectImageAction, } from './photoGalleryReducer' -import { gql, useMutation } from '@apollo/client' import { - markMediaFavorite, - markMediaFavoriteVariables, -} from './__generated__/markMediaFavorite' - -const markFavoriteMutation = gql` - mutation markMediaFavorite($mediaId: ID!, $favorite: Boolean!) { - favoriteMedia(mediaId: $mediaId, favorite: $favorite) { - id - favorite - } - } -` + toggleFavoriteAction, + useMarkFavoriteMutation, +} from './photoGalleryMutations' const Gallery = styled.div` display: flex; @@ -68,12 +57,7 @@ const PhotoGallery = ({ }: PhotoGalleryProps) => { const { t } = useTranslation() - const { updateSidebar } = useContext(SidebarContext) - - const [markFavorite] = useMutation< - markMediaFavorite, - markMediaFavoriteVariables - >(markFavoriteMutation) + const [markFavorite] = useMarkFavoriteMutation() const { media, activeIndex, presenting } = mediaState @@ -88,33 +72,19 @@ const PhotoGallery = ({ media={media} active={active} selectImage={() => { - selectImageAction({ + dispatchMedia({ + type: 'selectImage', index, - mediaState, - dispatchMedia, - updateSidebar, }) }} clickFavorite={() => { - markFavorite({ - variables: { - mediaId: media.id, - favorite: !media.favorite, - }, - optimisticResponse: { - favoriteMedia: { - id: media.id, - favorite: !media.favorite, - __typename: 'Media', - }, - }, + toggleFavoriteAction({ + media, + markFavorite, }) }} clickPresent={() => { - dispatchMedia({ - type: 'setPresenting', - presenting: true, - }) + openPresentModeAction({ dispatchMedia, activeIndex: index }) }} /> ) @@ -135,7 +105,10 @@ const PhotoGallery = ({ {presenting && ( - + )} ) diff --git a/ui/src/components/photoGallery/photoGalleryMutations.ts b/ui/src/components/photoGallery/photoGalleryMutations.ts new file mode 100644 index 00000000..ee1b14b2 --- /dev/null +++ b/ui/src/components/photoGallery/photoGalleryMutations.ts @@ -0,0 +1,43 @@ +import { gql, MutationFunction, useMutation } from '@apollo/client' +import { PhotoGalleryProps_Media } from './PhotoGallery' +import { + markMediaFavorite, + markMediaFavoriteVariables, +} from './__generated__/markMediaFavorite' + +const markFavoriteMutation = gql` + mutation markMediaFavorite($mediaId: ID!, $favorite: Boolean!) { + favoriteMedia(mediaId: $mediaId, favorite: $favorite) { + id + favorite + } + } +` + +export const useMarkFavoriteMutation = () => { + return useMutation( + markFavoriteMutation + ) +} + +export const toggleFavoriteAction = ({ + media, + markFavorite, +}: { + media: PhotoGalleryProps_Media + markFavorite: MutationFunction +}) => { + markFavorite({ + variables: { + mediaId: media.id, + favorite: !media.favorite, + }, + optimisticResponse: { + favoriteMedia: { + id: media.id, + favorite: !media.favorite, + __typename: 'Media', + }, + }, + }) +} diff --git a/ui/src/components/photoGallery/photoGalleryReducer.tsx b/ui/src/components/photoGallery/photoGalleryReducer.tsx index 22f393e3..21aff5ba 100644 --- a/ui/src/components/photoGallery/photoGalleryReducer.tsx +++ b/ui/src/components/photoGallery/photoGalleryReducer.tsx @@ -1,24 +1,28 @@ -import React from 'react' -import { UpdateSidebarFn } from '../sidebar/Sidebar' +import React, { useEffect } from 'react' import { PhotoGalleryProps_Media } from './PhotoGallery' -import MediaSidebar from '../sidebar/MediaSidebar' -export type PhotoGalleryState = { +export interface PhotoGalleryState { presenting: boolean activeIndex: number media: PhotoGalleryProps_Media[] } -export type PhotoGalleryAction = +export type GalleryAction = | { type: 'nextImage' } | { type: 'previousImage' } - | { type: 'setPresenting'; presenting: boolean } + | { type: 'closePresentMode' } + +export type PhotoGalleryAction = + | GalleryAction + | { type: 'openPresentMode'; activeIndex: number } | { type: 'selectImage'; index: number } + | { type: 'replaceMedia'; media: PhotoGalleryProps_Media[] } export function photoGalleryReducer( state: PhotoGalleryState, action: PhotoGalleryAction ): PhotoGalleryState { + console.log('photo gallery reducer:', state, action) switch (action.type) { case 'nextImage': return { @@ -37,10 +41,16 @@ export function photoGalleryReducer( activeIndex: state.activeIndex - 1, } } - case 'setPresenting': + case 'openPresentMode': return { ...state, - presenting: action.presenting, + presenting: true, + activeIndex: action.activeIndex, + } + case 'closePresentMode': + return { + ...state, + presenting: false, } case 'selectImage': return { @@ -50,25 +60,64 @@ export function photoGalleryReducer( Math.min(state.media.length - 1, action.index) ), } + case 'replaceMedia': + return { + ...state, + media: action.media, + activeIndex: -1, + } } } -export const selectImageAction = ({ - index, - mediaState, +export const urlPresentModeSetupHook = ({ dispatchMedia, - updateSidebar, + openPresentMode, }: { - index: number - mediaState: PhotoGalleryState - dispatchMedia: React.Dispatch - updateSidebar: UpdateSidebarFn + dispatchMedia: React.Dispatch + openPresentMode: (event: PopStateEvent) => void }) => { - updateSidebar( - - ) - dispatchMedia({ - type: 'selectImage', - index, - }) + useEffect(() => { + const urlChangeListener = (event: PopStateEvent) => { + if (event.state.presenting === true) { + openPresentMode(event) + } else { + dispatchMedia({ type: 'closePresentMode' }) + } + } + + window.addEventListener('popstate', urlChangeListener) + + history.replaceState({ presenting: false }, '') + + return () => { + window.removeEventListener('popstate', urlChangeListener) + } + }, []) +} + +export const openPresentModeAction = ({ + dispatchMedia, + activeIndex, +}: { + dispatchMedia: React.Dispatch + activeIndex: number +}) => { + dispatchMedia({ + type: 'openPresentMode', + activeIndex: activeIndex, + }) + + history.pushState({ presenting: true, activeIndex }, '') +} + +export const closePresentModeAction = ({ + dispatchMedia, +}: { + dispatchMedia: React.Dispatch +}) => { + dispatchMedia({ + type: 'closePresentMode', + }) + + history.back() } diff --git a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx index ff6da1e2..6c6da07f 100644 --- a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx +++ b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from 'react' import styled from 'styled-components' import { debounce, DebouncedFn } from '../../../helpers/utils' -import { PhotoGalleryAction } from '../photoGalleryReducer' +import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer' import ExitIcon from './icons/Exit' import NextIcon from './icons/Next' @@ -66,7 +66,7 @@ const NavigationButton = styled(OverlayButton)<{ float: 'left' | 'right' }>` type PresentNavigationOverlayProps = { children?: React.ReactChild - dispatchMedia: React.Dispatch + dispatchMedia: React.Dispatch } const PresentNavigationOverlay = ({ @@ -113,9 +113,7 @@ const PresentNavigationOverlay = ({ - dispatchMedia({ type: 'setPresenting', presenting: false }) - } + onClick={() => closePresentModeAction({ dispatchMedia })} > diff --git a/ui/src/components/photoGallery/presentView/PresentView.tsx b/ui/src/components/photoGallery/presentView/PresentView.tsx index beb9a11f..9671f2a1 100644 --- a/ui/src/components/photoGallery/presentView/PresentView.tsx +++ b/ui/src/components/photoGallery/presentView/PresentView.tsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react' import styled, { createGlobalStyle } from 'styled-components' import PresentNavigationOverlay from './PresentNavigationOverlay' -import PresentMedia from './PresentMedia' -import { PhotoGalleryAction, PhotoGalleryState } from '../photoGalleryReducer' +import PresentMedia, { PresentMediaProps_Media } from './PresentMedia' +import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer' const StyledContainer = styled.div` position: fixed; @@ -24,14 +24,14 @@ const PreventScroll = createGlobalStyle` type PresentViewProps = { className?: string imageLoaded?(): void - mediaState: PhotoGalleryState - dispatchMedia: React.Dispatch + activeMedia: PresentMediaProps_Media + dispatchMedia: React.Dispatch } const PresentView = ({ className, imageLoaded, - mediaState, + activeMedia, dispatchMedia, }: PresentViewProps) => { useEffect(() => { @@ -48,7 +48,8 @@ const PresentView = ({ if (e.key == 'Escape') { e.stopPropagation() - dispatchMedia({ type: 'setPresenting', presenting: false }) + // dispatchMedia({ type: 'setPresenting', presenting: false }) + closePresentModeAction({ dispatchMedia }) } } @@ -63,10 +64,7 @@ const PresentView = ({ - + ) diff --git a/ui/src/components/timelineGallery/TimelineGallery.tsx b/ui/src/components/timelineGallery/TimelineGallery.tsx index d1639500..a7bb2273 100644 --- a/ui/src/components/timelineGallery/TimelineGallery.tsx +++ b/ui/src/components/timelineGallery/TimelineGallery.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useRef, useEffect } from 'react' +import React, { useRef, useEffect, useReducer, useContext } from 'react' import PropTypes from 'prop-types' import { useQuery, gql } from '@apollo/client' import TimelineGroupDate from './TimelineGroupDate' @@ -16,6 +16,13 @@ import { myTimelineVariables, myTimeline_myTimeline, } from './__generated__/myTimeline' +import { + getActiveTimelineImage as getActiveTimelineMedia, + timelineGalleryReducer, +} from './timelineGalleryReducer' +import MediaSidebar from '../sidebar/MediaSidebar' +import { SidebarContext } from '../sidebar/Sidebar' +import { urlPresentModeSetupHook } from '../photoGallery/photoGalleryReducer' const MY_TIMELINE_QUERY = gql` query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) { @@ -64,14 +71,14 @@ export type TimelineActiveIndex = { media: number } +export type TimelineGroup = { + date: string + groups: myTimeline_myTimeline[] +} + const TimelineGallery = () => { const { t } = useTranslation() - const [activeIndex, setActiveIndex] = useState({ - dateGroup: -1, - albumGroup: -1, - media: -1, - }) - const [presenting, setPresenting] = useState(false) + const { updateSidebar } = useContext(SidebarContext) const { getParam, setParam } = useURLParameters() @@ -81,68 +88,15 @@ const TimelineGallery = () => { const favoritesNeedsRefresh = useRef(false) - const nextMedia = useCallback(() => { - setActiveIndex(activeIndex => { - const albumGroups = dateGroupedAlbums[activeIndex.dateGroup].groups - const albumMedia = albumGroups[activeIndex.albumGroup].media - - if (activeIndex.media < albumMedia.length - 1) { - return { - ...activeIndex, - media: activeIndex.media + 1, - } - } - - if (activeIndex.albumGroup < albumGroups.length - 1) { - return { - ...activeIndex, - albumGroup: activeIndex.albumGroup + 1, - media: 0, - } - } - - if (activeIndex.dateGroup < dateGroupedAlbums.length - 1) { - return { - dateGroup: activeIndex.dateGroup + 1, - albumGroup: 0, - media: 0, - } - } - - // reached the end - return activeIndex - }) - }, [activeIndex]) - - const previousMedia = useCallback(() => { - setActiveIndex(activeIndex => { - if (activeIndex.media > 0) { - return { - ...activeIndex, - media: activeIndex.media - 1, - } - } - - if (activeIndex.albumGroup > 0) { - return { - ...activeIndex, - albumGroup: activeIndex.albumGroup - 1, - media: 0, - } - } - - if (activeIndex.dateGroup > 0) { - return { - dateGroup: activeIndex.dateGroup - 1, - albumGroup: 0, - media: 0, - } - } - - // reached the start - return activeIndex - }) - }, [activeIndex]) + const [mediaState, dispatchMedia] = useReducer(timelineGalleryReducer, { + presenting: false, + timelineGroups: [], + activeIndex: { + media: -1, + album: -1, + date: -1, + }, + }) const { data, error, loading, refetch, fetchMore } = useQuery< myTimeline, @@ -166,12 +120,36 @@ const TimelineGallery = () => { }) useEffect(() => { - if (favoritesNeedsRefresh) { - favoritesNeedsRefresh.current = false - refetch({ - onlyFavorites: onlyFavorites, - }) + dispatchMedia({ + type: 'replaceTimelineGroups', + timeline: data?.myTimeline || [], + }) + }, [data]) + + useEffect(() => { + const activeMedia = getActiveTimelineMedia({ mediaState }) + if (activeMedia) { + updateSidebar() + } else { + updateSidebar(null) } + }, [mediaState.activeIndex]) + + urlPresentModeSetupHook({ + dispatchMedia, + openPresentMode: event => { + dispatchMedia({ + type: 'openPresentMode', + activeIndex: event.state.activeIndex, + }) + }, + }) + + useEffect(() => { + favoritesNeedsRefresh.current = false + refetch({ + onlyFavorites: onlyFavorites, + }) }, [onlyFavorites]) useEffect(() => { @@ -186,51 +164,14 @@ const TimelineGallery = () => { return
{error.message}
} - type TimelineGroup = { - date: string - groups: myTimeline_myTimeline[] - } - - let timelineGroups = null - let dateGroupedAlbums: TimelineGroup[] = [] - if (data?.myTimeline) { - dateGroupedAlbums = data.myTimeline.reduce((acc, val) => { - if (acc.length == 0 || acc[acc.length - 1].date != val.date) { - acc.push({ - date: val.date, - groups: [val], - }) - } else { - acc[acc.length - 1].groups.push(val) - } - - return acc - }, [] as TimelineGroup[]) - - timelineGroups = dateGroupedAlbums.map(({ date, groups }, i) => ( - { - setActiveIndex({ - media, - albumGroup, - dateGroup: i, - }) - }} - onFavorite={() => { - favoritesNeedsRefresh.current = true - }} - /> - )) - } + const timelineGroups = mediaState.timelineGroups.map((_, i) => ( + + )) return ( <> @@ -246,17 +187,10 @@ const TimelineGallery = () => { active={!finishedLoadingMore && !loading} text={t('general.loading.paginate.media', 'Loading more media')} /> - {presenting && ( + {mediaState.presenting && ( )} diff --git a/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx b/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx index 49916fce..b97b80b9 100644 --- a/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx +++ b/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx @@ -2,12 +2,17 @@ import React, { useContext } from 'react' import { Link } from 'react-router-dom' import styled from 'styled-components' import { MediaThumbnail } from '../photoGallery/MediaThumbnail' -import MediaSidebar from '../sidebar/MediaSidebar' +import { + toggleFavoriteAction, + useMarkFavoriteMutation, +} from '../photoGallery/photoGalleryMutations' import { SidebarContext } from '../sidebar/Sidebar' import { - myTimeline_myTimeline_album, - myTimeline_myTimeline_media, -} from './__generated__/myTimeline' + getActiveTimelineImage, + openTimelinePresentMode, + TimelineGalleryAction, + TimelineGalleryState, +} from './timelineGalleryReducer' const MediaWrapper = styled.div` display: flex; @@ -55,38 +60,55 @@ const TotalItemsBubble = styled(Link)` ` type TimelineGroupAlbumProps = { - group: { - album: myTimeline_myTimeline_album - media: myTimeline_myTimeline_media[] - mediaTotal: number - } - onSelectMedia(index: number): void - setPresenting: React.Dispatch> - activeIndex: number - onFavorite(): void + dateIndex: number + albumIndex: number + mediaState: TimelineGalleryState + dispatchMedia: React.Dispatch } const TimelineGroupAlbum = ({ - group: { album, media, mediaTotal }, - onSelectMedia, - setPresenting, - activeIndex, - onFavorite, + dateIndex, + albumIndex, + mediaState, + dispatchMedia, }: TimelineGroupAlbumProps) => { - const { updateSidebar } = useContext(SidebarContext) + const { media, mediaTotal, album } = mediaState.timelineGroups[ + dateIndex + ].groups[albumIndex] - const mediaElms = media.map((media, i) => ( + const [markFavorite] = useMarkFavoriteMutation() + + const mediaElms = media.map((media, index) => ( { - onSelectMedia(index) - updateSidebar() + selectImage={() => { + dispatchMedia({ + type: 'selectImage', + index: { + album: albumIndex, + date: dateIndex, + media: index, + }, + }) }} - setPresenting={setPresenting} - onFavorite={onFavorite} - index={i} - active={activeIndex == i} + clickPresent={() => { + openTimelinePresentMode({ + dispatchMedia, + activeIndex: { + album: albumIndex, + date: dateIndex, + media: index, + }, + }) + }} + clickFavorite={() => { + toggleFavoriteAction({ + media, + markFavorite, + }) + }} + active={media.id === getActiveTimelineImage({ mediaState })?.id} /> )) diff --git a/ui/src/components/timelineGallery/TimelineGroupDate.tsx b/ui/src/components/timelineGallery/TimelineGroupDate.tsx index a3ce73c2..a785d68c 100644 --- a/ui/src/components/timelineGallery/TimelineGroupDate.tsx +++ b/ui/src/components/timelineGallery/TimelineGroupDate.tsx @@ -4,6 +4,10 @@ import styled from 'styled-components' import { myTimeline_myTimeline } from './__generated__/myTimeline' import { TimelineActiveIndex } from './TimelineGallery' import { useTranslation } from 'react-i18next' +import { + TimelineGalleryAction, + TimelineGalleryState, +} from './timelineGalleryReducer' const dateFormatterOptions: Intl.DateTimeFormatOptions = { year: 'numeric', @@ -26,37 +30,27 @@ const GroupAlbumWrapper = styled.div` ` type TimelineGroupDateProps = { - date: string - groups: myTimeline_myTimeline[] - onSelectDateGroup(args: { media: number; albumGroup: number }): void - activeIndex: TimelineActiveIndex - setPresenting: React.Dispatch> - onFavorite(): void + groupIndex: number + mediaState: TimelineGalleryState + dispatchMedia: React.Dispatch } const TimelineGroupDate = ({ - date, - groups, - onSelectDateGroup, - activeIndex, - setPresenting, - onFavorite, + groupIndex, + mediaState, + dispatchMedia, }: TimelineGroupDateProps) => { const { i18n } = useTranslation() - const albumGroupElms = groups.map((group, i) => ( + const group = mediaState.timelineGroups[groupIndex] + + const albumGroupElms = group.groups.map((group, i) => ( { - onSelectDateGroup({ - media: mediaIndex, - albumGroup: i, - }) - }} - activeIndex={activeIndex.albumGroup == i ? activeIndex.media : -1} - setPresenting={setPresenting} - onFavorite={onFavorite} + dateIndex={groupIndex} + albumIndex={i} + mediaState={mediaState} + dispatchMedia={dispatchMedia} /> )) @@ -65,7 +59,7 @@ const TimelineGroupDate = ({ dateFormatterOptions ) - const formattedDate = dateFormatter.format(new Date(date)) + const formattedDate = dateFormatter.format(new Date(group.date)) return ( diff --git a/ui/src/components/timelineGallery/timelineGalleryReducer.tsx b/ui/src/components/timelineGallery/timelineGalleryReducer.tsx new file mode 100644 index 00000000..ce097659 --- /dev/null +++ b/ui/src/components/timelineGallery/timelineGalleryReducer.tsx @@ -0,0 +1,226 @@ +import React from 'react' +import { + myTimeline_myTimeline, + myTimeline_myTimeline_media, +} from './__generated__/myTimeline' +import { TimelineGroup } from './TimelineGallery' +import { UpdateSidebarFn } from '../sidebar/Sidebar' +import MediaSidebar from '../sidebar/MediaSidebar' +import { GalleryAction } from '../photoGallery/photoGalleryReducer' + +export interface TimelineMediaIndex { + date: number + album: number + media: number +} + +export interface TimelineGalleryState { + presenting: boolean + timelineGroups: TimelineGroup[] + activeIndex: TimelineMediaIndex +} + +export type TimelineGalleryAction = + | GalleryAction + | { type: 'replaceTimelineGroups'; timeline: myTimeline_myTimeline[] } + | { type: 'selectImage'; index: TimelineMediaIndex } + | { type: 'openPresentMode'; activeIndex: TimelineMediaIndex } + +export function timelineGalleryReducer( + state: TimelineGalleryState, + action: TimelineGalleryAction +): TimelineGalleryState { + console.log('timeline gallery reducer', state, action) + switch (action.type) { + case 'replaceTimelineGroups': { + const dateGroupedAlbums = action.timeline.reduce((acc, val) => { + if (acc.length == 0 || acc[acc.length - 1].date != val.date) { + acc.push({ + date: val.date, + groups: [val], + }) + } else { + acc[acc.length - 1].groups.push(val) + } + + return acc + }, [] as TimelineGroup[]) + + return { + ...state, + activeIndex: { + album: -1, + date: -1, + media: -1, + }, + timelineGroups: dateGroupedAlbums, + } + } + case 'nextImage': { + const { activeIndex, timelineGroups } = state + + const albumGroups = timelineGroups[activeIndex.date].groups + const albumMedia = albumGroups[activeIndex.album].media + + if (activeIndex.media < albumMedia.length - 1) { + return { + ...state, + activeIndex: { + ...state.activeIndex, + media: activeIndex.media + 1, + }, + } + } + + if (activeIndex.album < albumGroups.length - 1) { + return { + ...state, + activeIndex: { + ...state.activeIndex, + album: activeIndex.album + 1, + media: 0, + }, + } + } + + if (activeIndex.date < timelineGroups.length - 1) { + return { + ...state, + activeIndex: { + date: activeIndex.date + 1, + album: 0, + media: 0, + }, + } + } + + // reached the end + return state + } + case 'previousImage': { + const { activeIndex } = state + + if (activeIndex.media > 0) { + return { + ...state, + activeIndex: { + ...activeIndex, + media: activeIndex.media - 1, + }, + } + } + + if (activeIndex.album > 0) { + const albumGroups = state.timelineGroups[activeIndex.date].groups + const albumMedia = albumGroups[activeIndex.album - 1].media + + return { + ...state, + activeIndex: { + ...activeIndex, + album: activeIndex.album - 1, + media: albumMedia.length - 1, + }, + } + } + + if (activeIndex.date > 0) { + const albumGroups = state.timelineGroups[activeIndex.date - 1].groups + const albumMedia = albumGroups[activeIndex.album - 1].media + + return { + ...state, + activeIndex: { + date: activeIndex.date - 1, + album: albumGroups.length - 1, + media: albumMedia.length - 1, + }, + } + } + + // reached the start + return state + } + case 'selectImage': { + return { + ...state, + activeIndex: action.index, + } + } + case 'openPresentMode': + return { + ...state, + presenting: true, + activeIndex: action.activeIndex, + } + case 'closePresentMode': { + return { + ...state, + presenting: false, + } + } + } +} + +export const getTimelineImage = ({ + mediaState, + index, +}: { + mediaState: TimelineGalleryState + index: TimelineMediaIndex +}): myTimeline_myTimeline_media => { + const { date, album, media } = index + return mediaState.timelineGroups[date].groups[album].media[media] +} + +export const getActiveTimelineImage = ({ + mediaState, +}: { + mediaState: TimelineGalleryState +}) => { + if ( + Object.values(mediaState.activeIndex).reduce( + (acc, next) => next == -1 || acc, + false + ) + ) { + return undefined + } + + return getTimelineImage({ mediaState, index: mediaState.activeIndex }) +} + +// export const selectTimelineImageAction = ({ +// index, +// mediaState, +// dispatchMedia, +// updateSidebar, +// }: { +// index: TimelineMediaIndex +// mediaState: TimelineGalleryState +// dispatchMedia: React.Dispatch +// updateSidebar: UpdateSidebarFn +// }) => { +// updateSidebar( +// +// ) +// dispatchMedia({ +// type: 'selectImage', +// index, +// }) +// } + +export const openTimelinePresentMode = ({ + dispatchMedia, + activeIndex, +}: { + dispatchMedia: React.Dispatch + activeIndex: TimelineMediaIndex +}) => { + dispatchMedia({ + type: 'openPresentMode', + activeIndex, + }) + + history.pushState({ presenting: true, activeIndex: activeIndex }, '') +}