diff --git a/ui/src/Pages/SharePage/MediaSharePage.tsx b/ui/src/Pages/SharePage/MediaSharePage.tsx index 366a7905..e7a44e23 100644 --- a/ui/src/Pages/SharePage/MediaSharePage.tsx +++ b/ui/src/Pages/SharePage/MediaSharePage.tsx @@ -6,7 +6,7 @@ import { ProtectedVideo, } from '../../components/photoGallery/ProtectedMedia' 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 { SharePageToken_shareToken_media } from './__generated__/SharePageToken' import { MediaType } from '../../__generated__/globalTypes' diff --git a/ui/src/components/facesOverlay/FacesOverlay.tsx b/ui/src/components/facesOverlay/FacesOverlay.tsx index c38ed1d2..a8130bbd 100644 --- a/ui/src/components/facesOverlay/FacesOverlay.tsx +++ b/ui/src/components/facesOverlay/FacesOverlay.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Link } from 'react-router-dom' import styled from 'styled-components' import { MediaType } from '../../__generated__/globalTypes' -import { MediaSidebarMedia } from '../sidebar/MediaSidebar' +import { MediaSidebarMedia } from '../sidebar/MediaSidebar/MediaSidebar' import { sidebarPhoto_media_faces } from '../sidebar/__generated__/sidebarPhoto' interface FaceBoxStyleProps { diff --git a/ui/src/components/mapbox/MapboxMap.tsx b/ui/src/components/mapbox/MapboxMap.tsx index d996e1c4..071bd062 100644 --- a/ui/src/components/mapbox/MapboxMap.tsx +++ b/ui/src/components/mapbox/MapboxMap.tsx @@ -20,10 +20,13 @@ const MapContainer = styled.div` type MapboxMapProps = { configureMapbox(map: mapboxgl.Map, mapboxLibrary: typeof mapboxgl): void - readonly initialZoom?: number + mapboxOptions?: Partial } -const useMapboxMap = ({ configureMapbox, initialZoom = 1 }: MapboxMapProps) => { +const useMapboxMap = ({ + configureMapbox, + mapboxOptions = undefined, +}: MapboxMapProps) => { const [mapboxLibrary, setMapboxLibrary] = useState() const mapContainer = useRef(null) const map = useRef(null) @@ -57,12 +60,15 @@ const useMapboxMap = ({ configureMapbox, initialZoom = 1 }: MapboxMapProps) => { map.current = new mapboxLibrary.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/streets-v11', - zoom: initialZoom, + ...mapboxOptions, }) configureMapbox(map.current, mapboxLibrary) + map.current?.resize() }, [mapContainer, mapboxLibrary, mapboxData]) + map.current?.resize() + return { mapContainer: , mapboxMap: map.current, diff --git a/ui/src/components/photoGallery/PhotoGallery.tsx b/ui/src/components/photoGallery/PhotoGallery.tsx index ba1f82e3..3d4dd258 100644 --- a/ui/src/components/photoGallery/PhotoGallery.tsx +++ b/ui/src/components/photoGallery/PhotoGallery.tsx @@ -13,7 +13,7 @@ import { toggleFavoriteAction, useMarkFavoriteMutation, } from './photoGalleryMutations' -import MediaSidebar from '../sidebar/MediaSidebar' +import MediaSidebar from '../sidebar/MediaSidebar/MediaSidebar' import { SidebarContext } from '../sidebar/Sidebar' const Gallery = styled.div` diff --git a/ui/src/components/sidebar/MediaSidebar.test.js b/ui/src/components/sidebar/MediaSidebar/MediaSidebar.test.js similarity index 100% rename from ui/src/components/sidebar/MediaSidebar.test.js rename to ui/src/components/sidebar/MediaSidebar/MediaSidebar.test.js diff --git a/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx new file mode 100644 index 00000000..bf4d5e8a --- /dev/null +++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebar.tsx @@ -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 + } + + if (media.type === MediaType.Video) { + return + } + + return
ERROR: Unknown media type: {media.type}
+} + +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 = + } + + return ( +
+ +
+ {!hidePreview && ( +
+ + +
+ )} +
+ + {sidebarMap} + + +
+ +
+
+ ) +} + +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 + } + + if (error) return
{error.message}
+ + if (loading || data == null) { + return + } + + return +} + +export default MediaSidebar diff --git a/ui/src/components/sidebar/MediaSidebar.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx similarity index 58% rename from ui/src/components/sidebar/MediaSidebar.tsx rename to ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx index f19b3c0f..12adeafd 100644 --- a/ui/src/components/sidebar/MediaSidebar.tsx +++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx @@ -1,147 +1,20 @@ -import React, { useEffect } 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 React from 'react' import { useTranslation } from 'react-i18next' -import { MediaType } from '../../__generated__/globalTypes' -import { TranslationFn } from '../../localization' -import { - sidebarPhoto, - sidebarPhotoVariables, - 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 - } - - if (media.type === MediaType.Video) { - return - } - - return
ERROR: Unknown media type: {media.type}
-} +import styled from 'styled-components' +import { isNil } from '../../../helpers/utils' +import { TranslationFn } from '../../../localization' +import SidebarItem from '../SidebarItem' +import { MediaSidebarMedia } from './MediaSidebar' const MetadataInfoContainer = styled.div` margin-bottom: 1.5rem; ` -type MediaInfoProps = { +type ExifDetailsProps = { media?: MediaSidebarMedia } -export const MetadataInfo = ({ media }: MediaInfoProps) => { +const ExifDetails = ({ media }: ExifDetailsProps) => { const { t } = useTranslation() let exifItems: JSX.Element[] = [] @@ -343,106 +216,4 @@ const flashLookup = (t: TranslationFn): { [key: number]: string } => { } } -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 - - return ( -
- -
- {!hidePreview && ( -
- - -
- )} -
- - - -
- -
-
- ) -} - -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 - } - - if (error) return
{error.message}
- - if (loading || data == null) { - return - } - - return -} - -export default MediaSidebar +export default ExifDetails diff --git a/ui/src/components/sidebar/MediaSidebar/MediaSidebarMap.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebarMap.tsx new file mode 100644 index 00000000..5bdb0e39 --- /dev/null +++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebarMap.tsx @@ -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 ( + + + {t('sidebar.location.title', 'Location')} + +
{mapContainer}
+
+ ) +} + +export default MediaSidebarMap diff --git a/ui/src/components/sidebar/SidebarDownloadMedia.tsx b/ui/src/components/sidebar/SidebarDownloadMedia.tsx index 261027b6..01d4f5ef 100644 --- a/ui/src/components/sidebar/SidebarDownloadMedia.tsx +++ b/ui/src/components/sidebar/SidebarDownloadMedia.tsx @@ -4,7 +4,7 @@ import { useLazyQuery, gql } from '@apollo/client' import { authToken } from '../../helpers/authentication' import { useTranslation } from 'react-i18next' import { TranslationFn } from '../../localization' -import { MediaSidebarMedia } from './MediaSidebar' +import { MediaSidebarMedia } from './MediaSidebar/MediaSidebar' import { sidebarDownloadQuery, sidebarDownloadQueryVariables, diff --git a/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx b/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx index c8b37432..7310e37e 100644 --- a/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx +++ b/ui/src/components/timelineGallery/TimelineGroupAlbum.tsx @@ -6,7 +6,7 @@ import { toggleFavoriteAction, useMarkFavoriteMutation, } from '../photoGallery/photoGalleryMutations' -import MediaSidebar from '../sidebar/MediaSidebar' +import MediaSidebar from '../sidebar/MediaSidebar/MediaSidebar' import { SidebarContext } from '../sidebar/Sidebar' import { getActiveTimelineImage, diff --git a/ui/src/extractedTranslations/da/translation.json b/ui/src/extractedTranslations/da/translation.json index 81209a00..dc8dbcd9 100644 --- a/ui/src/extractedTranslations/da/translation.json +++ b/ui/src/extractedTranslations/da/translation.json @@ -259,6 +259,9 @@ }, "title": "Download" }, + "location": { + "title": "Lokation" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/de/translation.json b/ui/src/extractedTranslations/de/translation.json index 3db9459f..0e0335c5 100644 --- a/ui/src/extractedTranslations/de/translation.json +++ b/ui/src/extractedTranslations/de/translation.json @@ -259,6 +259,9 @@ }, "title": "Download" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/en/translation.json b/ui/src/extractedTranslations/en/translation.json index d2c759e6..0feba477 100644 --- a/ui/src/extractedTranslations/en/translation.json +++ b/ui/src/extractedTranslations/en/translation.json @@ -259,6 +259,9 @@ }, "title": "Download" }, + "location": { + "title": "Location" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/es/translation.json b/ui/src/extractedTranslations/es/translation.json index 12357289..289f4ff6 100644 --- a/ui/src/extractedTranslations/es/translation.json +++ b/ui/src/extractedTranslations/es/translation.json @@ -259,6 +259,9 @@ }, "title": "Descargar" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/fr/translation.json b/ui/src/extractedTranslations/fr/translation.json index c5bcfb58..966d4a97 100644 --- a/ui/src/extractedTranslations/fr/translation.json +++ b/ui/src/extractedTranslations/fr/translation.json @@ -259,6 +259,9 @@ }, "title": "Télécharger" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/it/translation.json b/ui/src/extractedTranslations/it/translation.json index ce6b7309..12ca27f3 100644 --- a/ui/src/extractedTranslations/it/translation.json +++ b/ui/src/extractedTranslations/it/translation.json @@ -259,6 +259,9 @@ }, "title": "Download" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/pl/translation.json b/ui/src/extractedTranslations/pl/translation.json index 3f26dd60..14603059 100644 --- a/ui/src/extractedTranslations/pl/translation.json +++ b/ui/src/extractedTranslations/pl/translation.json @@ -264,6 +264,9 @@ }, "title": "Pobierz" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/ru/translation.json b/ui/src/extractedTranslations/ru/translation.json index b4bfb795..6a2499fe 100644 --- a/ui/src/extractedTranslations/ru/translation.json +++ b/ui/src/extractedTranslations/ru/translation.json @@ -264,6 +264,9 @@ }, "title": "Скачать" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": { diff --git a/ui/src/extractedTranslations/sv/translation.json b/ui/src/extractedTranslations/sv/translation.json index c19ff9ac..64cfb919 100644 --- a/ui/src/extractedTranslations/sv/translation.json +++ b/ui/src/extractedTranslations/sv/translation.json @@ -259,6 +259,9 @@ }, "title": "Ladda ner" }, + "location": { + "title": "" + }, "media": { "exif": { "exposure_program": {