1
Fork 0

Add mapbox to sidebar

This commit is contained in:
viktorstrate 2021-10-10 17:42:09 +02:00
parent 57d408cb52
commit c357532613
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
19 changed files with 348 additions and 246 deletions

View File

@ -6,7 +6,7 @@ import {
ProtectedVideo, ProtectedVideo,
} from '../../components/photoGallery/ProtectedMedia' } from '../../components/photoGallery/ProtectedMedia'
import { SidebarContext } from '../../components/sidebar/Sidebar' import { SidebarContext } from '../../components/sidebar/Sidebar'
import MediaSidebar from '../../components/sidebar/MediaSidebar' import MediaSidebar from '../../components/sidebar/MediaSidebar/MediaSidebar'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SharePageToken_shareToken_media } from './__generated__/SharePageToken' import { SharePageToken_shareToken_media } from './__generated__/SharePageToken'
import { MediaType } from '../../__generated__/globalTypes' import { MediaType } from '../../__generated__/globalTypes'

View File

@ -2,7 +2,7 @@ import React 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 { MediaType } from '../../__generated__/globalTypes' import { MediaType } from '../../__generated__/globalTypes'
import { MediaSidebarMedia } from '../sidebar/MediaSidebar' import { MediaSidebarMedia } from '../sidebar/MediaSidebar/MediaSidebar'
import { sidebarPhoto_media_faces } from '../sidebar/__generated__/sidebarPhoto' import { sidebarPhoto_media_faces } from '../sidebar/__generated__/sidebarPhoto'
interface FaceBoxStyleProps { interface FaceBoxStyleProps {

View File

@ -20,10 +20,13 @@ const MapContainer = styled.div`
type MapboxMapProps = { type MapboxMapProps = {
configureMapbox(map: mapboxgl.Map, mapboxLibrary: typeof mapboxgl): void configureMapbox(map: mapboxgl.Map, mapboxLibrary: typeof mapboxgl): void
readonly initialZoom?: number mapboxOptions?: Partial<mapboxgl.MapboxOptions>
} }
const useMapboxMap = ({ configureMapbox, initialZoom = 1 }: MapboxMapProps) => { const useMapboxMap = ({
configureMapbox,
mapboxOptions = undefined,
}: MapboxMapProps) => {
const [mapboxLibrary, setMapboxLibrary] = useState<typeof mapboxgl>() const [mapboxLibrary, setMapboxLibrary] = useState<typeof mapboxgl>()
const mapContainer = useRef<HTMLDivElement | null>(null) const mapContainer = useRef<HTMLDivElement | null>(null)
const map = useRef<mapboxgl.Map | null>(null) const map = useRef<mapboxgl.Map | null>(null)
@ -57,12 +60,15 @@ const useMapboxMap = ({ configureMapbox, initialZoom = 1 }: MapboxMapProps) => {
map.current = new mapboxLibrary.Map({ map.current = new mapboxLibrary.Map({
container: mapContainer.current, container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v11', style: 'mapbox://styles/mapbox/streets-v11',
zoom: initialZoom, ...mapboxOptions,
}) })
configureMapbox(map.current, mapboxLibrary) configureMapbox(map.current, mapboxLibrary)
map.current?.resize()
}, [mapContainer, mapboxLibrary, mapboxData]) }, [mapContainer, mapboxLibrary, mapboxData])
map.current?.resize()
return { return {
mapContainer: <MapContainer ref={mapContainer}></MapContainer>, mapContainer: <MapContainer ref={mapContainer}></MapContainer>,
mapboxMap: map.current, mapboxMap: map.current,

View File

@ -13,7 +13,7 @@ import {
toggleFavoriteAction, toggleFavoriteAction,
useMarkFavoriteMutation, useMarkFavoriteMutation,
} from './photoGalleryMutations' } from './photoGalleryMutations'
import MediaSidebar from '../sidebar/MediaSidebar' import MediaSidebar from '../sidebar/MediaSidebar/MediaSidebar'
import { SidebarContext } from '../sidebar/Sidebar' import { SidebarContext } from '../sidebar/Sidebar'
const Gallery = styled.div` const Gallery = styled.div`

View File

@ -0,0 +1,242 @@
import { gql, useLazyQuery } from '@apollo/client'
import React, { useEffect } from 'react'
import styled from 'styled-components'
import { authToken } from '../../../helpers/authentication'
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 {
sidebarPhoto,
sidebarPhotoVariables,
sidebarPhoto_media_exif,
sidebarPhoto_media_faces,
sidebarPhoto_media_thumbnail,
sidebarPhoto_media_videoMetadata,
} from '../__generated__/sidebarPhoto'
import ExifDetails from './MediaSidebarExif'
import MediaSidebarMap from './MediaSidebarMap'
const SIDEBAR_MEDIA_QUERY = gql`
query sidebarPhoto($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
}
}
faces {
id
rectangle {
minX
maxX
minY
maxY
}
faceGroup {
id
}
}
}
}
`
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) => {
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} />
}
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} />
{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?: sidebarPhoto_media_thumbnail | null
videoWeb?: null | {
__typename: 'MediaURL'
url: string
width?: number
height?: number
}
videoMetadata?: sidebarPhoto_media_videoMetadata | null
exif?: sidebarPhoto_media_exif | null
faces?: sidebarPhoto_media_faces[]
downloads?: sidebarDownloadQuery_media_downloads[]
}
type MediaSidebarType = {
media: MediaSidebarMedia
hidePreview?: boolean
}
const MediaSidebar = ({ media, hidePreview }: MediaSidebarType) => {
const [loadMedia, { loading, error, data }] = useLazyQuery<
sidebarPhoto,
sidebarPhotoVariables
>(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

View File

@ -1,147 +1,20 @@
import React, { useEffect } from 'react' import React from 'react'
import { useLazyQuery, gql } from '@apollo/client'
import styled from 'styled-components'
import { authToken } from '../../helpers/authentication'
import {
ProtectedImage,
ProtectedVideo,
ProtectedVideoProps_Media,
} from '../photoGallery/ProtectedMedia'
import { SidebarPhotoShare } from './Sharing'
import SidebarMediaDownload from './SidebarDownloadMedia'
import SidebarItem from './SidebarItem'
import { SidebarFacesOverlay } from '../facesOverlay/FacesOverlay'
import { isNil } from '../../helpers/utils'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { MediaType } from '../../__generated__/globalTypes' import styled from 'styled-components'
import { TranslationFn } from '../../localization' import { isNil } from '../../../helpers/utils'
import { import { TranslationFn } from '../../../localization'
sidebarPhoto, import SidebarItem from '../SidebarItem'
sidebarPhotoVariables, import { MediaSidebarMedia } from './MediaSidebar'
sidebarPhoto_media_exif,
sidebarPhoto_media_faces,
sidebarPhoto_media_thumbnail,
sidebarPhoto_media_videoMetadata,
} from './__generated__/sidebarPhoto'
import { sidebarDownloadQuery_media_downloads } from './__generated__/sidebarDownloadQuery'
import SidebarHeader from './SidebarHeader'
import { SidebarPhotoCover } from './AlbumCovers'
const SIDEBAR_MEDIA_QUERY = gql`
query sidebarPhoto($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
}
}
faces {
id
rectangle {
minX
maxX
minY
maxY
}
faceGroup {
id
}
}
}
}
`
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>
}
const MetadataInfoContainer = styled.div` const MetadataInfoContainer = styled.div`
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
` `
type MediaInfoProps = { type ExifDetailsProps = {
media?: MediaSidebarMedia media?: MediaSidebarMedia
} }
export const MetadataInfo = ({ media }: MediaInfoProps) => { const ExifDetails = ({ media }: ExifDetailsProps) => {
const { t } = useTranslation() const { t } = useTranslation()
let exifItems: JSX.Element[] = [] let exifItems: JSX.Element[] = []
@ -343,106 +216,4 @@ const flashLookup = (t: TranslationFn): { [key: number]: string } => {
} }
} }
type SidebarContentProps = { export default ExifDetails
media: MediaSidebarMedia
hidePreview?: boolean
}
const SidebarContent = ({ media, hidePreview }: SidebarContentProps) => {
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
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>
<MetadataInfo media={media} />
<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?: sidebarPhoto_media_thumbnail | null
videoWeb?: null | {
__typename: 'MediaURL'
url: string
width?: number
height?: number
}
videoMetadata?: sidebarPhoto_media_videoMetadata | null
exif?: sidebarPhoto_media_exif | null
faces?: sidebarPhoto_media_faces[]
downloads?: sidebarDownloadQuery_media_downloads[]
}
type MediaSidebarType = {
media: MediaSidebarMedia
hidePreview?: boolean
}
const MediaSidebar = ({ media, hidePreview }: MediaSidebarType) => {
const [loadMedia, { loading, error, data }] = useLazyQuery<
sidebarPhoto,
sidebarPhotoVariables
>(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

View File

@ -0,0 +1,56 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { isNil } from '../../../helpers/utils'
import useMapboxMap from '../../mapbox/MapboxMap'
import { SidebarSection, SidebarSectionTitle } from '../SidebarComponents'
import { sidebarPhoto_media_exif_coordinates } from '../__generated__/sidebarPhoto'
type MediaSidebarMapProps = {
coordinates: sidebarPhoto_media_exif_coordinates
}
const MediaSidebarMap = ({ coordinates }: MediaSidebarMapProps) => {
const { t } = useTranslation()
const { mapContainer, mapboxToken } = useMapboxMap({
mapboxOptions: {
interactive: false,
zoom: 12,
center: {
lat: coordinates.latitude,
lng: coordinates.longitude,
},
},
configureMapbox: (map, mapboxLibrary) => {
// todo
map.addControl(
new mapboxLibrary.NavigationControl({ showCompass: false })
)
const centerMarker = new mapboxLibrary.Marker({
color: 'red',
scale: 0.8,
})
centerMarker.setLngLat({
lat: coordinates.latitude,
lng: coordinates.longitude,
})
centerMarker.addTo(map)
},
})
if (isNil(mapboxToken)) {
return null
}
return (
<SidebarSection>
<SidebarSectionTitle>
{t('sidebar.location.title', 'Location')}
</SidebarSectionTitle>
<div className="w-full h-64">{mapContainer}</div>
</SidebarSection>
)
}
export default MediaSidebarMap

View File

@ -4,7 +4,7 @@ import { useLazyQuery, gql } from '@apollo/client'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { TranslationFn } from '../../localization' import { TranslationFn } from '../../localization'
import { MediaSidebarMedia } from './MediaSidebar' import { MediaSidebarMedia } from './MediaSidebar/MediaSidebar'
import { import {
sidebarDownloadQuery, sidebarDownloadQuery,
sidebarDownloadQueryVariables, sidebarDownloadQueryVariables,

View File

@ -6,7 +6,7 @@ import {
toggleFavoriteAction, toggleFavoriteAction,
useMarkFavoriteMutation, useMarkFavoriteMutation,
} from '../photoGallery/photoGalleryMutations' } from '../photoGallery/photoGalleryMutations'
import MediaSidebar from '../sidebar/MediaSidebar' import MediaSidebar from '../sidebar/MediaSidebar/MediaSidebar'
import { SidebarContext } from '../sidebar/Sidebar' import { SidebarContext } from '../sidebar/Sidebar'
import { import {
getActiveTimelineImage, getActiveTimelineImage,

View File

@ -259,6 +259,9 @@
}, },
"title": "Download" "title": "Download"
}, },
"location": {
"title": "Lokation"
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Download" "title": "Download"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Download" "title": "Download"
}, },
"location": {
"title": "Location"
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Descargar" "title": "Descargar"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Télécharger" "title": "Télécharger"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Download" "title": "Download"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -264,6 +264,9 @@
}, },
"title": "Pobierz" "title": "Pobierz"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -264,6 +264,9 @@
}, },
"title": "Скачать" "title": "Скачать"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {

View File

@ -259,6 +259,9 @@
}, },
"title": "Ladda ner" "title": "Ladda ner"
}, },
"location": {
"title": ""
},
"media": { "media": {
"exif": { "exif": {
"exposure_program": { "exposure_program": {