1
Fork 0

Add gallery reducer for timeline view as well

This commit is contained in:
viktorstrate 2021-05-02 17:28:11 +02:00
parent a069eff0b2
commit c992fe8933
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
11 changed files with 528 additions and 295 deletions

View File

@ -34,6 +34,7 @@ module.exports = {
'react/display-name': 'off', 'react/display-name': 'off',
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
}, },
settings: { settings: {
react: { react: {

View File

@ -1,11 +1,16 @@
import React, { useReducer } from 'react' import React, { useContext, useEffect, useReducer } from 'react'
import AlbumTitle from '../AlbumTitle' import AlbumTitle from '../AlbumTitle'
import PhotoGallery from '../photoGallery/PhotoGallery' import PhotoGallery from '../photoGallery/PhotoGallery'
import AlbumBoxes from './AlbumBoxes' import AlbumBoxes from './AlbumBoxes'
import AlbumFilter from '../AlbumFilter' import AlbumFilter from '../AlbumFilter'
import { albumQuery_album } from '../../Pages/AlbumPage/__generated__/albumQuery' import { albumQuery_album } from '../../Pages/AlbumPage/__generated__/albumQuery'
import { OrderDirection } from '../../../__generated__/globalTypes' 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 = { type AlbumGalleryProps = {
album?: albumQuery_album album?: albumQuery_album
@ -33,10 +38,7 @@ const AlbumGallery = React.forwardRef(
}: AlbumGalleryProps, }: AlbumGalleryProps,
ref: React.ForwardedRef<HTMLDivElement> ref: React.ForwardedRef<HTMLDivElement>
) => { ) => {
// const [imageState, setImageState] = useState<ImageStateType>({ const { updateSidebar } = useContext(SidebarContext)
// activeImage: -1,
// presenting: false,
// })
const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, { const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, {
presenting: false, presenting: false,
@ -44,38 +46,31 @@ const AlbumGallery = React.forwardRef(
media: album?.media || [], media: album?.media || [],
}) })
// const setPresentingWithHistory = (presenting: boolean) => { useEffect(() => {
// setPresenting(presenting) dispatchMedia({ type: 'replaceMedia', media: album?.media || [] })
// if (presenting) { }, [album?.media])
// history.pushState({ imageState }, '')
// } else {
// history.back()
// }
// }
// const updateHistory = (imageState: ImageStateType) => { useEffect(() => {
// history.replaceState({ imageState }, '') if (mediaState.activeIndex != -1) {
// return imageState updateSidebar(
// } <MediaSidebar media={mediaState.media[mediaState.activeIndex]} />
)
} else {
updateSidebar(null)
}
}, [mediaState.activeIndex])
// useEffect(() => { urlPresentModeSetupHook({
// const updateImageState = (event: PopStateEvent) => { dispatchMedia,
// setImageState(event.state.imageState) openPresentMode: event => {
// } dispatchMedia({
type: 'openPresentMode',
// window.addEventListener('popstate', updateImageState) activeIndex: event.state.activeIndex,
})
// return () => { },
// window.removeEventListener('popstate', updateImageState) })
// }
// }, [imageState])
// useEffect(() => {
// setActiveImage(-1)
// }, [album])
let subAlbumElement = null let subAlbumElement = null
if (album) { if (album) {
if (album.subAlbums.length > 0) { if (album.subAlbums.length > 0) {
subAlbumElement = ( subAlbumElement = (

View File

@ -1,31 +1,20 @@
import React, { useContext } from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { Loader } from 'semantic-ui-react' import { Loader } from 'semantic-ui-react'
import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail' import { MediaThumbnail, PhotoThumbnail } from './MediaThumbnail'
import PresentView from './presentView/PresentView' import PresentView from './presentView/PresentView'
import { SidebarContext } from '../sidebar/Sidebar'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { PresentMediaProps_Media } from './presentView/PresentMedia' import { PresentMediaProps_Media } from './presentView/PresentMedia'
import { sidebarPhoto_media_thumbnail } from '../sidebar/__generated__/sidebarPhoto' import { sidebarPhoto_media_thumbnail } from '../sidebar/__generated__/sidebarPhoto'
import { import {
openPresentModeAction,
PhotoGalleryAction, PhotoGalleryAction,
PhotoGalleryState, PhotoGalleryState,
selectImageAction,
} from './photoGalleryReducer' } from './photoGalleryReducer'
import { gql, useMutation } from '@apollo/client'
import { import {
markMediaFavorite, toggleFavoriteAction,
markMediaFavoriteVariables, useMarkFavoriteMutation,
} from './__generated__/markMediaFavorite' } from './photoGalleryMutations'
const markFavoriteMutation = gql`
mutation markMediaFavorite($mediaId: ID!, $favorite: Boolean!) {
favoriteMedia(mediaId: $mediaId, favorite: $favorite) {
id
favorite
}
}
`
const Gallery = styled.div` const Gallery = styled.div`
display: flex; display: flex;
@ -68,12 +57,7 @@ const PhotoGallery = ({
}: PhotoGalleryProps) => { }: PhotoGalleryProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { updateSidebar } = useContext(SidebarContext) const [markFavorite] = useMarkFavoriteMutation()
const [markFavorite] = useMutation<
markMediaFavorite,
markMediaFavoriteVariables
>(markFavoriteMutation)
const { media, activeIndex, presenting } = mediaState const { media, activeIndex, presenting } = mediaState
@ -88,33 +72,19 @@ const PhotoGallery = ({
media={media} media={media}
active={active} active={active}
selectImage={() => { selectImage={() => {
selectImageAction({ dispatchMedia({
type: 'selectImage',
index, index,
mediaState,
dispatchMedia,
updateSidebar,
}) })
}} }}
clickFavorite={() => { clickFavorite={() => {
markFavorite({ toggleFavoriteAction({
variables: { media,
mediaId: media.id, markFavorite,
favorite: !media.favorite,
},
optimisticResponse: {
favoriteMedia: {
id: media.id,
favorite: !media.favorite,
__typename: 'Media',
},
},
}) })
}} }}
clickPresent={() => { clickPresent={() => {
dispatchMedia({ openPresentModeAction({ dispatchMedia, activeIndex: index })
type: 'setPresenting',
presenting: true,
})
}} }}
/> />
) )
@ -135,7 +105,10 @@ const PhotoGallery = ({
<PhotoFiller /> <PhotoFiller />
</Gallery> </Gallery>
{presenting && ( {presenting && (
<PresentView mediaState={mediaState} dispatchMedia={dispatchMedia} /> <PresentView
activeMedia={mediaState.media[mediaState.activeIndex]}
dispatchMedia={dispatchMedia}
/>
)} )}
</ClearWrap> </ClearWrap>
) )

View File

@ -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<markMediaFavorite, markMediaFavoriteVariables>(
markFavoriteMutation
)
}
export const toggleFavoriteAction = ({
media,
markFavorite,
}: {
media: PhotoGalleryProps_Media
markFavorite: MutationFunction<markMediaFavorite, markMediaFavoriteVariables>
}) => {
markFavorite({
variables: {
mediaId: media.id,
favorite: !media.favorite,
},
optimisticResponse: {
favoriteMedia: {
id: media.id,
favorite: !media.favorite,
__typename: 'Media',
},
},
})
}

View File

@ -1,24 +1,28 @@
import React from 'react' import React, { useEffect } from 'react'
import { UpdateSidebarFn } from '../sidebar/Sidebar'
import { PhotoGalleryProps_Media } from './PhotoGallery' import { PhotoGalleryProps_Media } from './PhotoGallery'
import MediaSidebar from '../sidebar/MediaSidebar'
export type PhotoGalleryState = { export interface PhotoGalleryState {
presenting: boolean presenting: boolean
activeIndex: number activeIndex: number
media: PhotoGalleryProps_Media[] media: PhotoGalleryProps_Media[]
} }
export type PhotoGalleryAction = export type GalleryAction =
| { type: 'nextImage' } | { type: 'nextImage' }
| { type: 'previousImage' } | { type: 'previousImage' }
| { type: 'setPresenting'; presenting: boolean } | { type: 'closePresentMode' }
export type PhotoGalleryAction =
| GalleryAction
| { type: 'openPresentMode'; activeIndex: number }
| { type: 'selectImage'; index: number } | { type: 'selectImage'; index: number }
| { type: 'replaceMedia'; media: PhotoGalleryProps_Media[] }
export function photoGalleryReducer( export function photoGalleryReducer(
state: PhotoGalleryState, state: PhotoGalleryState,
action: PhotoGalleryAction action: PhotoGalleryAction
): PhotoGalleryState { ): PhotoGalleryState {
console.log('photo gallery reducer:', state, action)
switch (action.type) { switch (action.type) {
case 'nextImage': case 'nextImage':
return { return {
@ -37,10 +41,16 @@ export function photoGalleryReducer(
activeIndex: state.activeIndex - 1, activeIndex: state.activeIndex - 1,
} }
} }
case 'setPresenting': case 'openPresentMode':
return { return {
...state, ...state,
presenting: action.presenting, presenting: true,
activeIndex: action.activeIndex,
}
case 'closePresentMode':
return {
...state,
presenting: false,
} }
case 'selectImage': case 'selectImage':
return { return {
@ -50,25 +60,64 @@ export function photoGalleryReducer(
Math.min(state.media.length - 1, action.index) Math.min(state.media.length - 1, action.index)
), ),
} }
case 'replaceMedia':
return {
...state,
media: action.media,
activeIndex: -1,
}
} }
} }
export const selectImageAction = ({ export const urlPresentModeSetupHook = ({
index,
mediaState,
dispatchMedia, dispatchMedia,
updateSidebar, openPresentMode,
}: { }: {
index: number dispatchMedia: React.Dispatch<GalleryAction>
mediaState: PhotoGalleryState openPresentMode: (event: PopStateEvent) => void
dispatchMedia: React.Dispatch<PhotoGalleryAction>
updateSidebar: UpdateSidebarFn
}) => { }) => {
updateSidebar( useEffect(() => {
<MediaSidebar media={mediaState.media[mediaState.activeIndex]} /> const urlChangeListener = (event: PopStateEvent) => {
) if (event.state.presenting === true) {
dispatchMedia({ openPresentMode(event)
type: 'selectImage', } else {
index, dispatchMedia({ type: 'closePresentMode' })
}) }
}
window.addEventListener('popstate', urlChangeListener)
history.replaceState({ presenting: false }, '')
return () => {
window.removeEventListener('popstate', urlChangeListener)
}
}, [])
}
export const openPresentModeAction = ({
dispatchMedia,
activeIndex,
}: {
dispatchMedia: React.Dispatch<PhotoGalleryAction>
activeIndex: number
}) => {
dispatchMedia({
type: 'openPresentMode',
activeIndex: activeIndex,
})
history.pushState({ presenting: true, activeIndex }, '')
}
export const closePresentModeAction = ({
dispatchMedia,
}: {
dispatchMedia: React.Dispatch<GalleryAction>
}) => {
dispatchMedia({
type: 'closePresentMode',
})
history.back()
} }

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { debounce, DebouncedFn } from '../../../helpers/utils' import { debounce, DebouncedFn } from '../../../helpers/utils'
import { PhotoGalleryAction } from '../photoGalleryReducer' import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer'
import ExitIcon from './icons/Exit' import ExitIcon from './icons/Exit'
import NextIcon from './icons/Next' import NextIcon from './icons/Next'
@ -66,7 +66,7 @@ const NavigationButton = styled(OverlayButton)<{ float: 'left' | 'right' }>`
type PresentNavigationOverlayProps = { type PresentNavigationOverlayProps = {
children?: React.ReactChild children?: React.ReactChild
dispatchMedia: React.Dispatch<PhotoGalleryAction> dispatchMedia: React.Dispatch<GalleryAction>
} }
const PresentNavigationOverlay = ({ const PresentNavigationOverlay = ({
@ -113,9 +113,7 @@ const PresentNavigationOverlay = ({
</NavigationButton> </NavigationButton>
<ExitButton <ExitButton
className={hide ? 'hide' : undefined} className={hide ? 'hide' : undefined}
onClick={() => onClick={() => closePresentModeAction({ dispatchMedia })}
dispatchMedia({ type: 'setPresenting', presenting: false })
}
> >
<ExitIcon /> <ExitIcon />
</ExitButton> </ExitButton>

View File

@ -1,8 +1,8 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import styled, { createGlobalStyle } from 'styled-components' import styled, { createGlobalStyle } from 'styled-components'
import PresentNavigationOverlay from './PresentNavigationOverlay' import PresentNavigationOverlay from './PresentNavigationOverlay'
import PresentMedia from './PresentMedia' import PresentMedia, { PresentMediaProps_Media } from './PresentMedia'
import { PhotoGalleryAction, PhotoGalleryState } from '../photoGalleryReducer' import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer'
const StyledContainer = styled.div` const StyledContainer = styled.div`
position: fixed; position: fixed;
@ -24,14 +24,14 @@ const PreventScroll = createGlobalStyle`
type PresentViewProps = { type PresentViewProps = {
className?: string className?: string
imageLoaded?(): void imageLoaded?(): void
mediaState: PhotoGalleryState activeMedia: PresentMediaProps_Media
dispatchMedia: React.Dispatch<PhotoGalleryAction> dispatchMedia: React.Dispatch<GalleryAction>
} }
const PresentView = ({ const PresentView = ({
className, className,
imageLoaded, imageLoaded,
mediaState, activeMedia,
dispatchMedia, dispatchMedia,
}: PresentViewProps) => { }: PresentViewProps) => {
useEffect(() => { useEffect(() => {
@ -48,7 +48,8 @@ const PresentView = ({
if (e.key == 'Escape') { if (e.key == 'Escape') {
e.stopPropagation() e.stopPropagation()
dispatchMedia({ type: 'setPresenting', presenting: false }) // dispatchMedia({ type: 'setPresenting', presenting: false })
closePresentModeAction({ dispatchMedia })
} }
} }
@ -63,10 +64,7 @@ const PresentView = ({
<StyledContainer {...className}> <StyledContainer {...className}>
<PreventScroll /> <PreventScroll />
<PresentNavigationOverlay dispatchMedia={dispatchMedia}> <PresentNavigationOverlay dispatchMedia={dispatchMedia}>
<PresentMedia <PresentMedia media={activeMedia} imageLoaded={imageLoaded} />
media={mediaState.media[mediaState.activeIndex]}
imageLoaded={imageLoaded}
/>
</PresentNavigationOverlay> </PresentNavigationOverlay>
</StyledContainer> </StyledContainer>
) )

View File

@ -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 PropTypes from 'prop-types'
import { useQuery, gql } from '@apollo/client' import { useQuery, gql } from '@apollo/client'
import TimelineGroupDate from './TimelineGroupDate' import TimelineGroupDate from './TimelineGroupDate'
@ -16,6 +16,13 @@ import {
myTimelineVariables, myTimelineVariables,
myTimeline_myTimeline, myTimeline_myTimeline,
} from './__generated__/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` const MY_TIMELINE_QUERY = gql`
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) { query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) {
@ -64,14 +71,14 @@ export type TimelineActiveIndex = {
media: number media: number
} }
export type TimelineGroup = {
date: string
groups: myTimeline_myTimeline[]
}
const TimelineGallery = () => { const TimelineGallery = () => {
const { t } = useTranslation() const { t } = useTranslation()
const [activeIndex, setActiveIndex] = useState({ const { updateSidebar } = useContext(SidebarContext)
dateGroup: -1,
albumGroup: -1,
media: -1,
})
const [presenting, setPresenting] = useState(false)
const { getParam, setParam } = useURLParameters() const { getParam, setParam } = useURLParameters()
@ -81,68 +88,15 @@ const TimelineGallery = () => {
const favoritesNeedsRefresh = useRef(false) const favoritesNeedsRefresh = useRef(false)
const nextMedia = useCallback(() => { const [mediaState, dispatchMedia] = useReducer(timelineGalleryReducer, {
setActiveIndex(activeIndex => { presenting: false,
const albumGroups = dateGroupedAlbums[activeIndex.dateGroup].groups timelineGroups: [],
const albumMedia = albumGroups[activeIndex.albumGroup].media activeIndex: {
media: -1,
if (activeIndex.media < albumMedia.length - 1) { album: -1,
return { date: -1,
...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 { data, error, loading, refetch, fetchMore } = useQuery< const { data, error, loading, refetch, fetchMore } = useQuery<
myTimeline, myTimeline,
@ -166,12 +120,36 @@ const TimelineGallery = () => {
}) })
useEffect(() => { useEffect(() => {
if (favoritesNeedsRefresh) { dispatchMedia({
favoritesNeedsRefresh.current = false type: 'replaceTimelineGroups',
refetch({ timeline: data?.myTimeline || [],
onlyFavorites: onlyFavorites, })
}) }, [data])
useEffect(() => {
const activeMedia = getActiveTimelineMedia({ mediaState })
if (activeMedia) {
updateSidebar(<MediaSidebar media={activeMedia} />)
} else {
updateSidebar(null)
} }
}, [mediaState.activeIndex])
urlPresentModeSetupHook({
dispatchMedia,
openPresentMode: event => {
dispatchMedia({
type: 'openPresentMode',
activeIndex: event.state.activeIndex,
})
},
})
useEffect(() => {
favoritesNeedsRefresh.current = false
refetch({
onlyFavorites: onlyFavorites,
})
}, [onlyFavorites]) }, [onlyFavorites])
useEffect(() => { useEffect(() => {
@ -186,51 +164,14 @@ const TimelineGallery = () => {
return <div>{error.message}</div> return <div>{error.message}</div>
} }
type TimelineGroup = { const timelineGroups = mediaState.timelineGroups.map((_, i) => (
date: string <TimelineGroupDate
groups: myTimeline_myTimeline[] key={i}
} groupIndex={i}
mediaState={mediaState}
let timelineGroups = null dispatchMedia={dispatchMedia}
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) => (
<TimelineGroupDate
key={date}
date={date}
groups={groups}
activeIndex={
activeIndex.dateGroup == i
? activeIndex
: { albumGroup: -1, media: -1 }
}
setPresenting={setPresenting}
onSelectDateGroup={({ media, albumGroup }) => {
setActiveIndex({
media,
albumGroup,
dateGroup: i,
})
}}
onFavorite={() => {
favoritesNeedsRefresh.current = true
}}
/>
))
}
return ( return (
<> <>
@ -246,17 +187,10 @@ const TimelineGallery = () => {
active={!finishedLoadingMore && !loading} active={!finishedLoadingMore && !loading}
text={t('general.loading.paginate.media', 'Loading more media')} text={t('general.loading.paginate.media', 'Loading more media')}
/> />
{presenting && ( {mediaState.presenting && (
<PresentView <PresentView
media={ activeMedia={getActiveTimelineMedia({ mediaState })!}
dateGroupedAlbums && dispatchMedia={dispatchMedia}
dateGroupedAlbums[activeIndex.dateGroup].groups[
activeIndex.albumGroup
].media[activeIndex.media]
}
nextImage={nextMedia}
previousImage={previousMedia}
setPresenting={setPresenting}
/> />
)} )}
</> </>

View File

@ -2,12 +2,17 @@ import React, { useContext } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import { MediaThumbnail } from '../photoGallery/MediaThumbnail' import { MediaThumbnail } from '../photoGallery/MediaThumbnail'
import MediaSidebar from '../sidebar/MediaSidebar' import {
toggleFavoriteAction,
useMarkFavoriteMutation,
} from '../photoGallery/photoGalleryMutations'
import { SidebarContext } from '../sidebar/Sidebar' import { SidebarContext } from '../sidebar/Sidebar'
import { import {
myTimeline_myTimeline_album, getActiveTimelineImage,
myTimeline_myTimeline_media, openTimelinePresentMode,
} from './__generated__/myTimeline' TimelineGalleryAction,
TimelineGalleryState,
} from './timelineGalleryReducer'
const MediaWrapper = styled.div` const MediaWrapper = styled.div`
display: flex; display: flex;
@ -55,38 +60,55 @@ const TotalItemsBubble = styled(Link)`
` `
type TimelineGroupAlbumProps = { type TimelineGroupAlbumProps = {
group: { dateIndex: number
album: myTimeline_myTimeline_album albumIndex: number
media: myTimeline_myTimeline_media[] mediaState: TimelineGalleryState
mediaTotal: number dispatchMedia: React.Dispatch<TimelineGalleryAction>
}
onSelectMedia(index: number): void
setPresenting: React.Dispatch<React.SetStateAction<boolean>>
activeIndex: number
onFavorite(): void
} }
const TimelineGroupAlbum = ({ const TimelineGroupAlbum = ({
group: { album, media, mediaTotal }, dateIndex,
onSelectMedia, albumIndex,
setPresenting, mediaState,
activeIndex, dispatchMedia,
onFavorite,
}: TimelineGroupAlbumProps) => { }: 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) => (
<MediaThumbnail <MediaThumbnail
key={media.id} key={media.id}
media={media} media={media}
onSelectImage={index => { selectImage={() => {
onSelectMedia(index) dispatchMedia({
updateSidebar(<MediaSidebar media={media} />) type: 'selectImage',
index: {
album: albumIndex,
date: dateIndex,
media: index,
},
})
}} }}
setPresenting={setPresenting} clickPresent={() => {
onFavorite={onFavorite} openTimelinePresentMode({
index={i} dispatchMedia,
active={activeIndex == i} activeIndex: {
album: albumIndex,
date: dateIndex,
media: index,
},
})
}}
clickFavorite={() => {
toggleFavoriteAction({
media,
markFavorite,
})
}}
active={media.id === getActiveTimelineImage({ mediaState })?.id}
/> />
)) ))

View File

@ -4,6 +4,10 @@ import styled from 'styled-components'
import { myTimeline_myTimeline } from './__generated__/myTimeline' import { myTimeline_myTimeline } from './__generated__/myTimeline'
import { TimelineActiveIndex } from './TimelineGallery' import { TimelineActiveIndex } from './TimelineGallery'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
TimelineGalleryAction,
TimelineGalleryState,
} from './timelineGalleryReducer'
const dateFormatterOptions: Intl.DateTimeFormatOptions = { const dateFormatterOptions: Intl.DateTimeFormatOptions = {
year: 'numeric', year: 'numeric',
@ -26,37 +30,27 @@ const GroupAlbumWrapper = styled.div`
` `
type TimelineGroupDateProps = { type TimelineGroupDateProps = {
date: string groupIndex: number
groups: myTimeline_myTimeline[] mediaState: TimelineGalleryState
onSelectDateGroup(args: { media: number; albumGroup: number }): void dispatchMedia: React.Dispatch<TimelineGalleryAction>
activeIndex: TimelineActiveIndex
setPresenting: React.Dispatch<React.SetStateAction<boolean>>
onFavorite(): void
} }
const TimelineGroupDate = ({ const TimelineGroupDate = ({
date, groupIndex,
groups, mediaState,
onSelectDateGroup, dispatchMedia,
activeIndex,
setPresenting,
onFavorite,
}: TimelineGroupDateProps) => { }: TimelineGroupDateProps) => {
const { i18n } = useTranslation() const { i18n } = useTranslation()
const albumGroupElms = groups.map((group, i) => ( const group = mediaState.timelineGroups[groupIndex]
const albumGroupElms = group.groups.map((group, i) => (
<TimelineGroupAlbum <TimelineGroupAlbum
key={`${group.date}_${group.album.id}`} key={`${group.date}_${group.album.id}`}
group={group} dateIndex={groupIndex}
onSelectMedia={mediaIndex => { albumIndex={i}
onSelectDateGroup({ mediaState={mediaState}
media: mediaIndex, dispatchMedia={dispatchMedia}
albumGroup: i,
})
}}
activeIndex={activeIndex.albumGroup == i ? activeIndex.media : -1}
setPresenting={setPresenting}
onFavorite={onFavorite}
/> />
)) ))
@ -65,7 +59,7 @@ const TimelineGroupDate = ({
dateFormatterOptions dateFormatterOptions
) )
const formattedDate = dateFormatter.format(new Date(date)) const formattedDate = dateFormatter.format(new Date(group.date))
return ( return (
<GroupDateWrapper> <GroupDateWrapper>

View File

@ -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<TimelineGalleryAction>
// updateSidebar: UpdateSidebarFn
// }) => {
// updateSidebar(
// <MediaSidebar media={getTimelineImage({ mediaState, index })} />
// )
// dispatchMedia({
// type: 'selectImage',
// index,
// })
// }
export const openTimelinePresentMode = ({
dispatchMedia,
activeIndex,
}: {
dispatchMedia: React.Dispatch<TimelineGalleryAction>
activeIndex: TimelineMediaIndex
}) => {
dispatchMedia({
type: 'openPresentMode',
activeIndex,
})
history.pushState({ presenting: true, activeIndex: activeIndex }, '')
}