Files
photoview/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx
2022-02-08 21:14:10 +01:00

301 lines
6.9 KiB
TypeScript

import { gql, useLazyQuery } from '@apollo/client'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { authToken } from '../../../helpers/authentication'
import { isNil } from '../../../helpers/utils'
import { MediaType } from '../../../__generated__/globalTypes'
import { SidebarFacesOverlay } from '../../facesOverlay/FacesOverlay'
import {
ProtectedImage,
ProtectedVideo,
ProtectedVideoProps_Media,
} from '../../photoGallery/ProtectedMedia'
import { SidebarPhotoCover } from '../AlbumCovers'
import { SidebarPhotoShare } from '../Sharing'
import SidebarMediaDownload from '../SidebarDownloadMedia'
import SidebarHeader from '../SidebarHeader'
import { sidebarDownloadQuery_media_downloads } from '../__generated__/sidebarDownloadQuery'
import ExifDetails from './MediaSidebarExif'
import MediaSidebarPeople from './MediaSidebarPeople'
import MediaSidebarMap from './MediaSidebarMap'
import {
sidebarMediaQuery,
sidebarMediaQueryVariables,
sidebarMediaQuery_media_album_path,
sidebarMediaQuery_media_exif,
sidebarMediaQuery_media_faces,
sidebarMediaQuery_media_thumbnail,
sidebarMediaQuery_media_videoMetadata,
} from './__generated__/sidebarMediaQuery'
import { BreadcrumbList } from '../../album/AlbumTitle'
export const SIDEBAR_MEDIA_QUERY = gql`
query sidebarMediaQuery($id: ID!) {
media(id: $id) {
id
title
type
highRes {
url
width
height
}
thumbnail {
url
width
height
}
videoWeb {
url
width
height
}
videoMetadata {
id
width
height
duration
codec
framerate
bitrate
colorProfile
audio
}
exif {
id
camera
maker
lens
dateShot
exposure
aperture
iso
focalLength
flash
exposureProgram
coordinates {
latitude
longitude
}
}
album {
id
title
path {
id
title
}
}
faces {
id
rectangle {
minX
maxX
minY
maxY
}
faceGroup {
id
label
imageFaceCount
}
media {
id
title
thumbnail {
url
width
height
}
}
}
}
}
`
const PreviewImage = styled(ProtectedImage)`
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
object-fit: contain;
`
const PreviewVideo = styled(ProtectedVideo)`
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
`
interface PreviewMediaPropsMedia extends ProtectedVideoProps_Media {
type: MediaType
}
type PreviewMediaProps = {
media: PreviewMediaPropsMedia
previewImage?: {
url: string
}
}
const PreviewMedia = ({ media, previewImage }: PreviewMediaProps) => {
if (media.type === MediaType.Photo) {
return <PreviewImage src={previewImage?.url} />
}
if (media.type === MediaType.Video) {
return <PreviewVideo media={media} />
}
return <div>ERROR: Unknown media type: {media.type}</div>
}
type SidebarContentProps = {
media: MediaSidebarMedia
hidePreview?: boolean
}
const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => {
const { t } = useTranslation()
let previewImage = null
if (media.highRes) previewImage = media.highRes
else if (media.thumbnail) previewImage = media.thumbnail
const imageAspect =
previewImage?.width && previewImage?.height
? previewImage.height / previewImage.width
: 3 / 2
let sidebarMap = null
const mediaCoordinates = media.exif?.coordinates
if (mediaCoordinates) {
sidebarMap = <MediaSidebarMap coordinates={mediaCoordinates} />
}
let albumPath = null
const mediaAlbum = media.album
if (!isNil(mediaAlbum)) {
const pathElms = [...(mediaAlbum.path ?? []), mediaAlbum].map(album => (
<li key={album.id} className="inline-block hover:underline">
<Link
className="text-blue-900 dark:text-blue-200 hover:underline"
to={`/album/${album.id}`}
>
{album.title}
</Link>
</li>
))
albumPath = (
<div className="mx-4 my-4">
<h2 className="uppercase text-xs text-gray-900 dark:text-gray-300 font-semibold">
{t('sidebar.media.album_path', 'Album path')}
</h2>
<BreadcrumbList hideLastArrow={true}>{pathElms}</BreadcrumbList>
</div>
)
}
return (
<div>
<SidebarHeader title={media.title ?? 'Loading...'} />
<div className="lg:mx-4">
{!hidePreview && (
<div
className="w-full h-0 relative"
style={{ paddingTop: `${Math.min(imageAspect, 0.75) * 100}%` }}
>
<PreviewMedia
previewImage={previewImage || undefined}
media={media}
/>
<SidebarFacesOverlay media={media} />
</div>
)}
</div>
<ExifDetails media={media} />
{albumPath}
<MediaSidebarPeople media={media} />
{sidebarMap}
<SidebarMediaDownload media={media} />
<SidebarPhotoShare id={media.id} />
<div className="mt-8">
<SidebarPhotoCover cover_id={media.id} />
</div>
</div>
)
}
export interface MediaSidebarMedia {
__typename: 'Media'
id: string
title?: string
type: MediaType
highRes?: null | {
__typename: 'MediaURL'
url: string
width?: number
height?: number
}
thumbnail?: sidebarMediaQuery_media_thumbnail | null
videoWeb?: null | {
__typename: 'MediaURL'
url: string
width?: number
height?: number
}
videoMetadata?: sidebarMediaQuery_media_videoMetadata | null
exif?: sidebarMediaQuery_media_exif | null
faces?: sidebarMediaQuery_media_faces[]
downloads?: sidebarDownloadQuery_media_downloads[]
album?: {
__typename: 'Album'
id: string
title: string
path?: sidebarMediaQuery_media_album_path[]
}
}
type MediaSidebarType = {
media: MediaSidebarMedia
hidePreview?: boolean
}
const MediaSidebar = ({ media, hidePreview }: MediaSidebarType) => {
const [loadMedia, { loading, error, data }] = useLazyQuery<
sidebarMediaQuery,
sidebarMediaQueryVariables
>(SIDEBAR_MEDIA_QUERY)
useEffect(() => {
if (media != null && authToken()) {
loadMedia({
variables: {
id: media.id,
},
})
}
}, [media])
if (!media) return null
if (!authToken()) {
return <SidebarContent media={media} hidePreview={hidePreview} />
}
if (error) return <div>{error.message}</div>
if (loading || data == null) {
return <SidebarContent media={media} hidePreview={hidePreview} />
}
return <SidebarContent media={data.media} hidePreview={hidePreview} />
}
export default MediaSidebar