From b34115cab0ffc215bdf8f3e5cac49f753bac9152 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sat, 11 Jul 2020 16:42:27 +0200 Subject: [PATCH] Start on video integration with web ui --- api/graphql/resolvers/media.go | 10 +- ui/src/Pages/AlbumPage/AlbumPage.js | 4 + ui/src/Pages/PhotosPage/PhotosPage.js | 4 + ui/src/Pages/SharePage/PhotoSharePage.js | 4 +- .../components/photoGallery/PhotoGallery.js | 6 +- .../photoGallery/presentView/PresentMedia.js | 59 +++++++++ .../presentView/PresentNavigationOverlay.js | 3 + .../photoGallery/presentView/PresentPhoto.js | 35 ----- .../photoGallery/presentView/PresentView.js | 13 +- .../{PhotoSidebar.js => MediaSidebar.js} | 120 ++++++++++++------ 10 files changed, 169 insertions(+), 89 deletions(-) create mode 100644 ui/src/components/photoGallery/presentView/PresentMedia.js delete mode 100644 ui/src/components/photoGallery/presentView/PresentPhoto.js rename ui/src/components/sidebar/{PhotoSidebar.js => MediaSidebar.js} (55%) diff --git a/api/graphql/resolvers/media.go b/api/graphql/resolvers/media.go index 9fa0f8d8..f3d83085 100644 --- a/api/graphql/resolvers/media.go +++ b/api/graphql/resolvers/media.go @@ -101,6 +101,10 @@ func (r *mediaResolver) Downloads(ctx context.Context, obj *models.Media) ([]*mo title = "Small" case url.Purpose == models.PhotoHighRes: title = "Large" + case url.Purpose == models.VideoThumbnail: + title = "Video thumbnail" + case url.Purpose == models.VideoWeb: + title = "Web optimized video" } downloads = append(downloads, &models.MediaDownload{ @@ -158,7 +162,11 @@ func (r *mediaResolver) VideoWeb(ctx context.Context, obj *models.Media) (*model url, err := models.NewMediaURLFromRow(row) if err != nil { - return nil, errors.Wrapf(err, "could not query video web-format url (%s)", obj.Path) + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, errors.Wrapf(err, "could not query video web-format url (%s)", obj.Path) + } } return url, nil diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js index f4620629..6d959f23 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.js +++ b/ui/src/Pages/AlbumPage/AlbumPage.js @@ -20,6 +20,7 @@ const albumQuery = gql` } media(filter: { order_by: "title", order_direction: DESC }) { id + type thumbnail { url width @@ -28,6 +29,9 @@ const albumQuery = gql` highRes { url } + videoWeb { + url + } favorite } } diff --git a/ui/src/Pages/PhotosPage/PhotosPage.js b/ui/src/Pages/PhotosPage/PhotosPage.js index 101146d3..d77cfd71 100644 --- a/ui/src/Pages/PhotosPage/PhotosPage.js +++ b/ui/src/Pages/PhotosPage/PhotosPage.js @@ -15,6 +15,7 @@ const photoQuery = gql` ) { id title + type thumbnail { url width @@ -23,6 +24,9 @@ const photoQuery = gql` highRes { url } + videoWeb { + url + } favorite } } diff --git a/ui/src/Pages/SharePage/PhotoSharePage.js b/ui/src/Pages/SharePage/PhotoSharePage.js index a1436940..6a44aa55 100644 --- a/ui/src/Pages/SharePage/PhotoSharePage.js +++ b/ui/src/Pages/SharePage/PhotoSharePage.js @@ -4,7 +4,7 @@ import styled from 'styled-components' import Layout from '../../Layout' import ProtectedImage from '../../components/photoGallery/ProtectedImage' import { SidebarConsumer } from '../../components/sidebar/Sidebar' -import PhotoSidebar from '../../components/sidebar/PhotoSidebar' +import MediaSidebar from '../../components/sidebar/MediaSidebar' const DisplayPhoto = styled(ProtectedImage)` width: 100%; @@ -22,7 +22,7 @@ const AlbumSharePage = ({ photo }) => { { - updateSidebar() + updateSidebar() }} /> diff --git a/ui/src/components/photoGallery/PhotoGallery.js b/ui/src/components/photoGallery/PhotoGallery.js index 06768231..7fa0a7e1 100644 --- a/ui/src/components/photoGallery/PhotoGallery.js +++ b/ui/src/components/photoGallery/PhotoGallery.js @@ -5,7 +5,7 @@ import { Photo, PhotoThumbnail } from './Photo' import PresentView from './presentView/PresentView' import PropTypes from 'prop-types' import { SidebarConsumer } from '../sidebar/Sidebar' -import PhotoSidebar from '../sidebar/PhotoSidebar' +import MediaSidebar from '../sidebar/MediaSidebar' const Gallery = styled.div` display: flex; @@ -79,7 +79,7 @@ const PhotoGallery = ({ key={photo.id} photo={photo} onSelectImage={index => { - updateSidebar() + updateSidebar() onSelectImage(index) }} setPresenting={setPresenting} @@ -109,7 +109,7 @@ const PhotoGallery = ({ {presenting && ( )} diff --git a/ui/src/components/photoGallery/presentView/PresentMedia.js b/ui/src/components/photoGallery/presentView/PresentMedia.js new file mode 100644 index 00000000..e1947c55 --- /dev/null +++ b/ui/src/components/photoGallery/presentView/PresentMedia.js @@ -0,0 +1,59 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styled from 'styled-components' +import ProtectedImage from '../ProtectedImage' + +const StyledPhoto = styled(ProtectedImage)` + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + object-fit: contain; + object-position: center; +` + +const StyledVideo = styled.video` + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; +` + +const PresentMedia = ({ media, imageLoaded, ...otherProps }) => { + if (media.type == 'photo') { + return ( +
+ + { + e.target.style.display = 'initial' + imageLoaded && imageLoaded() + }} + /> +
+ ) + } + + if (media.type == 'video') { + return ( +
+ + + +
+ ) + } + + throw new Error(`Unknown media type '${media.type}'`) +} + +PresentMedia.propTypes = { + media: PropTypes.object.isRequired, + imageLoaded: PropTypes.func, +} + +export default PresentMedia diff --git a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.js b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.js index cc41602f..e4d9a08e 100644 --- a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.js +++ b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.js @@ -64,6 +64,7 @@ const NavigationButton = styled(OverlayButton)` ` const PresentNavigationOverlay = ({ + children, nextImage, previousImage, setPresenting, @@ -92,6 +93,7 @@ const PresentNavigationOverlay = ({ onMouseMove.current() }} > + {children} ( -
- - { - e.target.style.display = 'initial' - imageLoaded && imageLoaded() - }} - /> -
-) - -PresentPhoto.propTypes = { - photo: PropTypes.object.isRequired, - imageLoaded: PropTypes.func, -} - -export default PresentPhoto diff --git a/ui/src/components/photoGallery/presentView/PresentView.js b/ui/src/components/photoGallery/presentView/PresentView.js index 448de330..dacb8c40 100644 --- a/ui/src/components/photoGallery/presentView/PresentView.js +++ b/ui/src/components/photoGallery/presentView/PresentView.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types' import React from 'react' import styled, { createGlobalStyle } from 'styled-components' import PresentNavigationOverlay from './PresentNavigationOverlay' -import PresentPhoto from './PresentPhoto' +import PresentMedia from './PresentMedia' const StyledContainer = styled.div` position: fixed; @@ -23,7 +23,7 @@ const PreventScroll = createGlobalStyle` const PresentView = ({ className, - photo, + media, imageLoaded, nextImage, previousImage, @@ -31,15 +31,14 @@ const PresentView = ({ }) => ( - - + + + ) PresentView.propTypes = { - photo: PropTypes.object.isRequired, + media: PropTypes.object.isRequired, imageLoaded: PropTypes.func, className: PropTypes.string, nextImage: PropTypes.func.isRequired, diff --git a/ui/src/components/sidebar/PhotoSidebar.js b/ui/src/components/sidebar/MediaSidebar.js similarity index 55% rename from ui/src/components/sidebar/PhotoSidebar.js rename to ui/src/components/sidebar/MediaSidebar.js index 814e1c06..e7b06a95 100644 --- a/ui/src/components/sidebar/PhotoSidebar.js +++ b/ui/src/components/sidebar/MediaSidebar.js @@ -1,23 +1,34 @@ -import React, { Component } from 'react' +import React, { Component, useEffect } from 'react' import PropTypes from 'prop-types' import styled from 'styled-components' -import { Query } from 'react-apollo' +import { useLazyQuery } from 'react-apollo' import gql from 'graphql-tag' import SidebarItem from './SidebarItem' import ProtectedImage from '../photoGallery/ProtectedImage' import SidebarShare from './Sharing' import SidebarDownload from './SidebarDownload' -const photoQuery = gql` +const mediaQuery = gql` query sidebarPhoto($id: Int!) { media(id: $id) { id title + type highRes { url width height } + thumbnail { + url + width + height + } + videoWeb { + url + width + height + } exif { camera maker @@ -50,6 +61,35 @@ const PreviewImage = styled(ProtectedImage)` object-fit: contain; ` +const PreviewVideo = styled.video` + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; +` + +const PreviewMedia = ({ media, previewImage }) => { + if (media.type == null || media.type == 'photo') { + return + } + + if (media.type == 'video') { + return ( + + + + ) + } + + throw new Error('Unknown media type') +} + +PreviewMedia.propTypes = { + media: PropTypes.object.isRequired, + previewImage: PropTypes.object.isRequired, +} + const Name = styled.div` text-align: center; font-size: 1.2rem; @@ -85,18 +125,18 @@ const exposurePrograms = { '8': 'Landscape mode ', } -const SidebarContent = ({ photo, hidePreview }) => { +const SidebarContent = ({ media, hidePreview }) => { let exifItems = [] - if (photo && photo.exif) { + if (media && media.exif) { let exifKeys = Object.keys(exifNameLookup).filter( - x => !!photo.exif[x] && x != '__typename' + x => !!media.exif[x] && x != '__typename' ) let exif = exifKeys.reduce( (prev, curr) => ({ ...prev, - [curr]: photo.exif[curr], + [curr]: media.exif[curr], }), {} ) @@ -120,9 +160,9 @@ const SidebarContent = ({ photo, hidePreview }) => { } let previewImage = null - if (photo) { - if (photo.highRes) previewImage = photo.highRes - else if (photo.thumbnail) previewImage = photo.thumbnail + if (media) { + if (media.highRes) previewImage = media.highRes + else if (media.thumbnail) previewImage = media.thumbnail } return ( @@ -131,55 +171,53 @@ const SidebarContent = ({ photo, hidePreview }) => { - + )} - {photo && photo.title} + {media && media.title} {exifItems} - - + + ) } SidebarContent.propTypes = { - photo: PropTypes.object, + media: PropTypes.object, hidePreview: PropTypes.bool, } -class PhotoSidebar extends Component { - render() { - const { photo, hidePreview } = this.props +const MediaSidebar = ({ media, hidePreview }) => { + const [loadMedia, { loading, error, data }] = useLazyQuery(mediaQuery) - if (!photo) return null - - if (!localStorage.getItem('token')) { - return + useEffect(() => { + if (media != null && localStorage.getItem('token')) { + loadMedia({ + variables: { + id: media.id, + }, + }) } + }, [media]) - return ( -
- - {({ loading, error, data }) => { - if (error) return error + if (!media) return null - if (loading) { - return - } - - return ( - - ) - }} - -
- ) + if (!localStorage.getItem('token')) { + return } + + if (error) return error + + if (loading || data == null) { + return + } + + return } -PhotoSidebar.propTypes = { - photo: PropTypes.object.isRequired, +MediaSidebar.propTypes = { + media: PropTypes.object.isRequired, hidePreview: PropTypes.bool, } -export default PhotoSidebar +export default MediaSidebar