Rewrite SharePage to typescript
This commit is contained in:
parent
9c939fd06c
commit
34a411be9d
|
@ -1,4 +1,3 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import Layout from '../../Layout'
|
||||
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
|
||||
|
@ -73,7 +72,13 @@ const AlbumSharePageWrapper = styled.div`
|
|||
height: 100%;
|
||||
`
|
||||
|
||||
const AlbumSharePage = ({ albumID, token, password }) => {
|
||||
type AlbumSharePageProps = {
|
||||
albumID: string
|
||||
token: string
|
||||
password: string | null
|
||||
}
|
||||
|
||||
const AlbumSharePage = ({ albumID, token, password }: AlbumSharePageProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { data, loading, error } = useQuery(SHARE_ALBUM_QUERY, {
|
||||
variables: {
|
||||
|
@ -86,11 +91,11 @@ const AlbumSharePage = ({ albumID, token, password }) => {
|
|||
})
|
||||
|
||||
if (error) {
|
||||
return error.message
|
||||
return <div>{error.message}</div>
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return t('general.loading.default', 'Loading...')
|
||||
return <div>{t('general.loading.default', 'Loading...')}</div>
|
||||
}
|
||||
|
||||
const album = data.album
|
||||
|
@ -111,10 +116,4 @@ const AlbumSharePage = ({ albumID, token, password }) => {
|
|||
)
|
||||
}
|
||||
|
||||
AlbumSharePage.propTypes = {
|
||||
albumID: PropTypes.string.isRequired,
|
||||
token: PropTypes.string.isRequired,
|
||||
password: PropTypes.string,
|
||||
}
|
||||
|
||||
export default AlbumSharePage
|
|
@ -1,58 +0,0 @@
|
|||
import React, { useContext, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import Layout from '../../Layout'
|
||||
import {
|
||||
ProtectedImage,
|
||||
ProtectedVideo,
|
||||
} from '../../components/photoGallery/ProtectedMedia'
|
||||
import { SidebarContext } from '../../components/sidebar/Sidebar'
|
||||
import MediaSidebar from '../../components/sidebar/MediaSidebar'
|
||||
|
||||
const DisplayPhoto = styled(ProtectedImage)`
|
||||
width: 100%;
|
||||
max-height: calc(80vh);
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const DisplayVideo = styled(ProtectedVideo)`
|
||||
width: 100%;
|
||||
max-height: calc(80vh);
|
||||
`
|
||||
|
||||
const MediaView = ({ media }) => {
|
||||
const { updateSidebar } = useContext(SidebarContext)
|
||||
|
||||
useEffect(() => {
|
||||
updateSidebar(<MediaSidebar media={media} hidePreview />)
|
||||
}, [media])
|
||||
|
||||
if (media.type == 'photo') {
|
||||
return <DisplayPhoto src={media.highRes.url} />
|
||||
}
|
||||
|
||||
if (media.type == 'video') {
|
||||
return <DisplayVideo media={media} />
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported media type: ${media.type}`)
|
||||
}
|
||||
|
||||
MediaView.propTypes = {
|
||||
media: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const MediaSharePage = ({ media }) => (
|
||||
<Layout>
|
||||
<div data-testid="MediaSharePage">
|
||||
<h1>{media.title}</h1>
|
||||
<MediaView media={media} />
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
MediaSharePage.propTypes = {
|
||||
media: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default MediaSharePage
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useContext, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Layout from '../../Layout'
|
||||
import {
|
||||
ProtectedImage,
|
||||
ProtectedVideo,
|
||||
} from '../../components/photoGallery/ProtectedMedia'
|
||||
import { SidebarContext } from '../../components/sidebar/Sidebar'
|
||||
import MediaSidebar from '../../components/sidebar/MediaSidebar'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SharePageToken_shareToken_media } from './__generated__/SharePageToken'
|
||||
import { MediaType } from '../../../__generated__/globalTypes'
|
||||
|
||||
const DisplayPhoto = styled(ProtectedImage)`
|
||||
width: 100%;
|
||||
max-height: calc(80vh);
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const DisplayVideo = styled(ProtectedVideo)`
|
||||
width: 100%;
|
||||
max-height: calc(80vh);
|
||||
`
|
||||
|
||||
type MediaViewProps = {
|
||||
media: SharePageToken_shareToken_media
|
||||
}
|
||||
|
||||
const MediaView = ({ media }: MediaViewProps) => {
|
||||
const { updateSidebar } = useContext(SidebarContext)
|
||||
|
||||
useEffect(() => {
|
||||
updateSidebar(<MediaSidebar media={media} hidePreview />)
|
||||
}, [media])
|
||||
|
||||
switch (media.type) {
|
||||
case MediaType.Photo:
|
||||
return <DisplayPhoto src={media.highRes?.url} />
|
||||
case MediaType.Video:
|
||||
return <DisplayVideo media={media} />
|
||||
}
|
||||
}
|
||||
|
||||
type MediaSharePageType = {
|
||||
media: SharePageToken_shareToken_media
|
||||
}
|
||||
|
||||
const MediaSharePage = ({ media }: MediaSharePageType) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Layout title={t('share_page.media.title', 'Shared media')}>
|
||||
<div data-testid="MediaSharePage">
|
||||
<h1>{media.title}</h1>
|
||||
<MediaView media={media} />
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default MediaSharePage
|
|
@ -82,7 +82,7 @@ describe('load correct share page, based on graphql query', () => {
|
|||
media: {
|
||||
id: '1',
|
||||
title: 'shared_image.jpg',
|
||||
type: 'photo',
|
||||
type: 'Photo',
|
||||
highRes: {
|
||||
url: 'https://example.com/shared_image.jpg',
|
||||
},
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery, gql } from '@apollo/client'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import RouterProps from 'react-router-prop-types'
|
||||
import { match as MatchType, Route, Switch } from 'react-router-dom'
|
||||
import { Form, Header, Icon, Input, Message } from 'semantic-ui-react'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
|
@ -71,7 +70,7 @@ export const VALIDATE_TOKEN_PASSWORD_QUERY = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const AuthorizedTokenRoute = ({ match }) => {
|
||||
const AuthorizedTokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const token = match.params.token
|
||||
|
@ -84,11 +83,11 @@ const AuthorizedTokenRoute = ({ match }) => {
|
|||
},
|
||||
})
|
||||
|
||||
if (error) return error.message
|
||||
if (loading) return 'Loading...'
|
||||
if (error) return <div>{error.message}</div>
|
||||
if (loading) return <div>{t('general.loading.default', 'Loading...')}</div>
|
||||
|
||||
if (data.shareToken.album) {
|
||||
const SharedSubAlbumPage = ({ match }) => {
|
||||
const SharedSubAlbumPage = ({ match }: MatchProps<SubalbumRouteMatch>) => {
|
||||
return (
|
||||
<AlbumSharePage
|
||||
albumID={match.params.subAlbum}
|
||||
|
@ -136,10 +135,15 @@ const MessageContainer = styled.div`
|
|||
margin: 100px auto 0;
|
||||
`
|
||||
|
||||
type ProtectedTokenEnterPasswordProps = {
|
||||
refetchWithPassword(password: string): void
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const ProtectedTokenEnterPassword = ({
|
||||
refetchWithPassword,
|
||||
loading = false,
|
||||
}) => {
|
||||
}: ProtectedTokenEnterPasswordProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [passwordValue, setPasswordValue] = useState('')
|
||||
|
@ -178,7 +182,9 @@ const ProtectedTokenEnterPassword = ({
|
|||
<Input
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
onKeyUp={event => event.key == 'Enter' && onSubmit()}
|
||||
onKeyUp={(event: KeyboardEvent) =>
|
||||
event.key == 'Enter' && onSubmit()
|
||||
}
|
||||
onChange={e => setPasswordValue(e.target.value)}
|
||||
placeholder={t('login_page.field.password', 'Password')}
|
||||
type="password"
|
||||
|
@ -191,12 +197,19 @@ const ProtectedTokenEnterPassword = ({
|
|||
)
|
||||
}
|
||||
|
||||
ProtectedTokenEnterPassword.propTypes = {
|
||||
refetchWithPassword: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
interface TokenRouteMatch {
|
||||
token: string
|
||||
}
|
||||
|
||||
const TokenRoute = ({ match }) => {
|
||||
interface SubalbumRouteMatch extends TokenRouteMatch {
|
||||
subAlbum: string
|
||||
}
|
||||
|
||||
interface MatchProps<Route> {
|
||||
match: MatchType<Route>
|
||||
}
|
||||
|
||||
const TokenRoute = ({ match }: MatchProps<TokenRouteMatch>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const token = match.params.token
|
||||
|
@ -227,13 +240,12 @@ const TokenRoute = ({ match }) => {
|
|||
)
|
||||
}
|
||||
|
||||
return error.message
|
||||
return <div>{error.message}</div>
|
||||
}
|
||||
|
||||
if (data && data.shareTokenValidatePassword == false) {
|
||||
return (
|
||||
<ProtectedTokenEnterPassword
|
||||
match={match}
|
||||
refetchWithPassword={password => {
|
||||
saveSharePassword(token, password)
|
||||
refetch({ token, password })
|
||||
|
@ -243,22 +255,18 @@ const TokenRoute = ({ match }) => {
|
|||
)
|
||||
}
|
||||
|
||||
if (loading) return t('general.loading.default', 'Loading...')
|
||||
if (loading) return <div>{t('general.loading.default', 'Loading...')}</div>
|
||||
|
||||
return <AuthorizedTokenRoute match={match} />
|
||||
}
|
||||
|
||||
TokenRoute.propTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
const SharePage = ({ match }) => {
|
||||
const SharePage = ({ match }: { match: MatchType }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/:token`}>
|
||||
{({ match }) => {
|
||||
{({ match }: { match: MatchType<TokenRouteMatch> }) => {
|
||||
return <TokenRoute match={match} />
|
||||
}}
|
||||
</Route>
|
||||
|
@ -267,8 +275,4 @@ const SharePage = ({ match }) => {
|
|||
)
|
||||
}
|
||||
|
||||
SharePage.propTypes = {
|
||||
...RouterProps,
|
||||
}
|
||||
|
||||
export default SharePage
|
|
@ -1,9 +1,21 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
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'
|
||||
|
||||
type AlbumGalleryProps = {
|
||||
album: albumQuery_album
|
||||
loading?: boolean
|
||||
customAlbumLink?(albumID: string): string
|
||||
showFilter?: boolean
|
||||
setOnlyFavorites?(favorites: boolean): void
|
||||
setOrdering?(ordering: { orderBy: string }): void
|
||||
ordering?: { orderBy: string }
|
||||
onlyFavorites?: boolean
|
||||
onFavorite?(): void
|
||||
}
|
||||
|
||||
const AlbumGallery = React.forwardRef(
|
||||
(
|
||||
|
@ -17,18 +29,23 @@ const AlbumGallery = React.forwardRef(
|
|||
ordering,
|
||||
onlyFavorites = false,
|
||||
onFavorite,
|
||||
},
|
||||
ref
|
||||
}: AlbumGalleryProps,
|
||||
ref: React.ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
const [imageState, setImageState] = useState({
|
||||
type ImageStateType = {
|
||||
activeImage: number
|
||||
presenting: boolean
|
||||
}
|
||||
|
||||
const [imageState, setImageState] = useState<ImageStateType>({
|
||||
activeImage: -1,
|
||||
presenting: false,
|
||||
})
|
||||
|
||||
const setPresenting = presenting =>
|
||||
const setPresenting = (presenting: boolean) =>
|
||||
setImageState(state => ({ ...state, presenting }))
|
||||
|
||||
const setPresentingWithHistory = presenting => {
|
||||
const setPresentingWithHistory = (presenting: boolean) => {
|
||||
setPresenting(presenting)
|
||||
if (presenting) {
|
||||
history.pushState({ imageState }, '')
|
||||
|
@ -37,12 +54,12 @@ const AlbumGallery = React.forwardRef(
|
|||
}
|
||||
}
|
||||
|
||||
const updateHistory = imageState => {
|
||||
const updateHistory = (imageState: ImageStateType) => {
|
||||
history.replaceState({ imageState }, '')
|
||||
return imageState
|
||||
}
|
||||
|
||||
const setActiveImage = activeImage => {
|
||||
const setActiveImage = (activeImage: number) => {
|
||||
setImageState(state => updateHistory({ ...state, activeImage }))
|
||||
}
|
||||
|
||||
|
@ -59,9 +76,10 @@ const AlbumGallery = React.forwardRef(
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateImageState = event => {
|
||||
const updateImageState = (event: PopStateEvent) => {
|
||||
setImageState(event.state.imageState)
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', updateImageState)
|
||||
|
||||
return () => {
|
||||
|
@ -129,16 +147,4 @@ const AlbumGallery = React.forwardRef(
|
|||
}
|
||||
)
|
||||
|
||||
AlbumGallery.propTypes = {
|
||||
album: PropTypes.object,
|
||||
loading: PropTypes.bool,
|
||||
customAlbumLink: PropTypes.func,
|
||||
showFilter: PropTypes.bool,
|
||||
setOnlyFavorites: PropTypes.func,
|
||||
onlyFavorites: PropTypes.bool,
|
||||
onFavorite: PropTypes.func,
|
||||
setOrdering: PropTypes.func,
|
||||
ordering: PropTypes.object,
|
||||
}
|
||||
|
||||
export default AlbumGallery
|
|
@ -119,7 +119,7 @@ type MediaThumbnailProps = {
|
|||
index: number
|
||||
active: boolean
|
||||
setPresenting(presenting: boolean): void
|
||||
onFavorite(): void
|
||||
onFavorite?(): void
|
||||
}
|
||||
|
||||
export const MediaThumbnail = ({
|
||||
|
|
|
@ -48,7 +48,7 @@ type PhotoGalleryProps = {
|
|||
setPresenting(presenting: boolean): void
|
||||
nextImage(): void
|
||||
previousImage(): void
|
||||
onFavorite(): void
|
||||
onFavorite?(): void
|
||||
}
|
||||
|
||||
const PhotoGallery = ({
|
||||
|
|
|
@ -71,7 +71,7 @@ export interface ProtectedVideoProps_Media {
|
|||
thumbnail: null | {
|
||||
url: string
|
||||
}
|
||||
videoWeb: {
|
||||
videoWeb: null | {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,13 @@ export interface ProtectedVideoProps {
|
|||
media: ProtectedVideoProps_Media
|
||||
}
|
||||
|
||||
export const ProtectedVideo = ({ media, ...props }: ProtectedVideoProps) => (
|
||||
export const ProtectedVideo = ({ media, ...props }: ProtectedVideoProps) => {
|
||||
if (media.videoWeb === null) {
|
||||
console.error('ProetctedVideo called with media.videoWeb = null')
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<video
|
||||
{...props}
|
||||
controls
|
||||
|
@ -90,4 +96,5 @@ export const ProtectedVideo = ({ media, ...props }: ProtectedVideoProps) => (
|
|||
>
|
||||
<source src={getProtectedUrl(media.videoWeb.url)} type="video/mp4" />
|
||||
</video>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ const StyledVideo = styled(ProtectedVideo)`
|
|||
|
||||
export interface PresentMediaProps_Media extends ProtectedVideoProps_Media {
|
||||
type: MediaType
|
||||
highRes?: {
|
||||
highRes: null | {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,6 @@ export function debounce<T extends (...args: any[]) => any>(
|
|||
return debounced
|
||||
}
|
||||
|
||||
export function isNil(value: any) {
|
||||
export function isNil(value: any): value is undefined | null {
|
||||
return value === undefined || value === null
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true /* Do not emit outputs. */,
|
||||
"noEmit": true /* Do not emit outputs. */,
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
|
Loading…
Reference in New Issue