1
Fork 0

Start on video integration with web ui

This commit is contained in:
viktorstrate 2020-07-11 16:42:27 +02:00
parent f537b1d608
commit b34115cab0
10 changed files with 169 additions and 89 deletions

View File

@ -101,6 +101,10 @@ func (r *mediaResolver) Downloads(ctx context.Context, obj *models.Media) ([]*mo
title = "Small" title = "Small"
case url.Purpose == models.PhotoHighRes: case url.Purpose == models.PhotoHighRes:
title = "Large" title = "Large"
case url.Purpose == models.VideoThumbnail:
title = "Video thumbnail"
case url.Purpose == models.VideoWeb:
title = "Web optimized video"
} }
downloads = append(downloads, &models.MediaDownload{ downloads = append(downloads, &models.MediaDownload{
@ -158,8 +162,12 @@ func (r *mediaResolver) VideoWeb(ctx context.Context, obj *models.Media) (*model
url, err := models.NewMediaURLFromRow(row) url, err := models.NewMediaURLFromRow(row)
if err != nil { if err != nil {
if err == sql.ErrNoRows {
return nil, nil
} else {
return nil, errors.Wrapf(err, "could not query video web-format url (%s)", obj.Path) return nil, errors.Wrapf(err, "could not query video web-format url (%s)", obj.Path)
} }
}
return url, nil return url, nil
} }

View File

@ -20,6 +20,7 @@ const albumQuery = gql`
} }
media(filter: { order_by: "title", order_direction: DESC }) { media(filter: { order_by: "title", order_direction: DESC }) {
id id
type
thumbnail { thumbnail {
url url
width width
@ -28,6 +29,9 @@ const albumQuery = gql`
highRes { highRes {
url url
} }
videoWeb {
url
}
favorite favorite
} }
} }

View File

@ -15,6 +15,7 @@ const photoQuery = gql`
) { ) {
id id
title title
type
thumbnail { thumbnail {
url url
width width
@ -23,6 +24,9 @@ const photoQuery = gql`
highRes { highRes {
url url
} }
videoWeb {
url
}
favorite favorite
} }
} }

View File

@ -4,7 +4,7 @@ import styled from 'styled-components'
import Layout from '../../Layout' import Layout from '../../Layout'
import ProtectedImage from '../../components/photoGallery/ProtectedImage' import ProtectedImage from '../../components/photoGallery/ProtectedImage'
import { SidebarConsumer } from '../../components/sidebar/Sidebar' import { SidebarConsumer } from '../../components/sidebar/Sidebar'
import PhotoSidebar from '../../components/sidebar/PhotoSidebar' import MediaSidebar from '../../components/sidebar/MediaSidebar'
const DisplayPhoto = styled(ProtectedImage)` const DisplayPhoto = styled(ProtectedImage)`
width: 100%; width: 100%;
@ -22,7 +22,7 @@ const AlbumSharePage = ({ photo }) => {
<DisplayPhoto <DisplayPhoto
src={photo.highRes.url} src={photo.highRes.url}
onLoad={() => { onLoad={() => {
updateSidebar(<PhotoSidebar photo={photo} hidePreview />) updateSidebar(<MediaSidebar media={photo} hidePreview />)
}} }}
/> />
</> </>

View File

@ -5,7 +5,7 @@ import { Photo, PhotoThumbnail } from './Photo'
import PresentView from './presentView/PresentView' import PresentView from './presentView/PresentView'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { SidebarConsumer } from '../sidebar/Sidebar' import { SidebarConsumer } from '../sidebar/Sidebar'
import PhotoSidebar from '../sidebar/PhotoSidebar' import MediaSidebar from '../sidebar/MediaSidebar'
const Gallery = styled.div` const Gallery = styled.div`
display: flex; display: flex;
@ -79,7 +79,7 @@ const PhotoGallery = ({
key={photo.id} key={photo.id}
photo={photo} photo={photo}
onSelectImage={index => { onSelectImage={index => {
updateSidebar(<PhotoSidebar photo={photo} />) updateSidebar(<MediaSidebar media={photo} />)
onSelectImage(index) onSelectImage(index)
}} }}
setPresenting={setPresenting} setPresenting={setPresenting}
@ -109,7 +109,7 @@ const PhotoGallery = ({
</Gallery> </Gallery>
{presenting && ( {presenting && (
<PresentView <PresentView
photo={activeImage} media={activeImage}
{...{ nextImage, previousImage, setPresenting }} {...{ nextImage, previousImage, setPresenting }}
/> />
)} )}

View File

@ -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 (
<div {...otherProps}>
<StyledPhoto src={media.thumbnail.url} />
<StyledPhoto
style={{ display: 'none' }}
src={media.highRes.url}
onLoad={e => {
e.target.style.display = 'initial'
imageLoaded && imageLoaded()
}}
/>
</div>
)
}
if (media.type == 'video') {
return (
<div {...otherProps}>
<StyledVideo controls key={media.id}>
<source src={media.videoWeb.url} type="video/mp4" />
</StyledVideo>
</div>
)
}
throw new Error(`Unknown media type '${media.type}'`)
}
PresentMedia.propTypes = {
media: PropTypes.object.isRequired,
imageLoaded: PropTypes.func,
}
export default PresentMedia

View File

@ -64,6 +64,7 @@ const NavigationButton = styled(OverlayButton)`
` `
const PresentNavigationOverlay = ({ const PresentNavigationOverlay = ({
children,
nextImage, nextImage,
previousImage, previousImage,
setPresenting, setPresenting,
@ -92,6 +93,7 @@ const PresentNavigationOverlay = ({
onMouseMove.current() onMouseMove.current()
}} }}
> >
{children}
<NavigationButton <NavigationButton
className={hide && 'hide'} className={hide && 'hide'}
float="left" float="left"
@ -117,6 +119,7 @@ const PresentNavigationOverlay = ({
} }
PresentNavigationOverlay.propTypes = { PresentNavigationOverlay.propTypes = {
children: PropTypes.element,
nextImage: PropTypes.func.isRequired, nextImage: PropTypes.func.isRequired,
previousImage: PropTypes.func.isRequired, previousImage: PropTypes.func.isRequired,
setPresenting: PropTypes.func.isRequired, setPresenting: PropTypes.func.isRequired,

View File

@ -1,35 +0,0 @@
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 PresentPhoto = ({ photo, imageLoaded, ...otherProps }) => (
<div {...otherProps}>
<StyledPhoto src={photo.thumbnail.url} />
<StyledPhoto
style={{ display: 'none' }}
src={photo.highRes.url}
onLoad={e => {
e.target.style.display = 'initial'
imageLoaded && imageLoaded()
}}
/>
</div>
)
PresentPhoto.propTypes = {
photo: PropTypes.object.isRequired,
imageLoaded: PropTypes.func,
}
export default PresentPhoto

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import styled, { createGlobalStyle } from 'styled-components' import styled, { createGlobalStyle } from 'styled-components'
import PresentNavigationOverlay from './PresentNavigationOverlay' import PresentNavigationOverlay from './PresentNavigationOverlay'
import PresentPhoto from './PresentPhoto' import PresentMedia from './PresentMedia'
const StyledContainer = styled.div` const StyledContainer = styled.div`
position: fixed; position: fixed;
@ -23,7 +23,7 @@ const PreventScroll = createGlobalStyle`
const PresentView = ({ const PresentView = ({
className, className,
photo, media,
imageLoaded, imageLoaded,
nextImage, nextImage,
previousImage, previousImage,
@ -31,15 +31,14 @@ const PresentView = ({
}) => ( }) => (
<StyledContainer {...className}> <StyledContainer {...className}>
<PreventScroll /> <PreventScroll />
<PresentPhoto photo={photo} imageLoaded={imageLoaded} /> <PresentNavigationOverlay {...{ nextImage, previousImage, setPresenting }}>
<PresentNavigationOverlay <PresentMedia media={media} imageLoaded={imageLoaded} />
{...{ nextImage, previousImage, setPresenting }} </PresentNavigationOverlay>
/>
</StyledContainer> </StyledContainer>
) )
PresentView.propTypes = { PresentView.propTypes = {
photo: PropTypes.object.isRequired, media: PropTypes.object.isRequired,
imageLoaded: PropTypes.func, imageLoaded: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
nextImage: PropTypes.func.isRequired, nextImage: PropTypes.func.isRequired,

View File

@ -1,23 +1,34 @@
import React, { Component } from 'react' import React, { Component, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styled from 'styled-components' import styled from 'styled-components'
import { Query } from 'react-apollo' import { useLazyQuery } from 'react-apollo'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import SidebarItem from './SidebarItem' import SidebarItem from './SidebarItem'
import ProtectedImage from '../photoGallery/ProtectedImage' import ProtectedImage from '../photoGallery/ProtectedImage'
import SidebarShare from './Sharing' import SidebarShare from './Sharing'
import SidebarDownload from './SidebarDownload' import SidebarDownload from './SidebarDownload'
const photoQuery = gql` const mediaQuery = gql`
query sidebarPhoto($id: Int!) { query sidebarPhoto($id: Int!) {
media(id: $id) { media(id: $id) {
id id
title title
type
highRes { highRes {
url url
width width
height height
} }
thumbnail {
url
width
height
}
videoWeb {
url
width
height
}
exif { exif {
camera camera
maker maker
@ -50,6 +61,35 @@ const PreviewImage = styled(ProtectedImage)`
object-fit: contain; 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 <PreviewImage src={previewImage.url} />
}
if (media.type == 'video') {
return (
<PreviewVideo controls key={media.id}>
<source src={media.videoWeb.url} type="video/mp4" />
</PreviewVideo>
)
}
throw new Error('Unknown media type')
}
PreviewMedia.propTypes = {
media: PropTypes.object.isRequired,
previewImage: PropTypes.object.isRequired,
}
const Name = styled.div` const Name = styled.div`
text-align: center; text-align: center;
font-size: 1.2rem; font-size: 1.2rem;
@ -85,18 +125,18 @@ const exposurePrograms = {
'8': 'Landscape mode ', '8': 'Landscape mode ',
} }
const SidebarContent = ({ photo, hidePreview }) => { const SidebarContent = ({ media, hidePreview }) => {
let exifItems = [] let exifItems = []
if (photo && photo.exif) { if (media && media.exif) {
let exifKeys = Object.keys(exifNameLookup).filter( let exifKeys = Object.keys(exifNameLookup).filter(
x => !!photo.exif[x] && x != '__typename' x => !!media.exif[x] && x != '__typename'
) )
let exif = exifKeys.reduce( let exif = exifKeys.reduce(
(prev, curr) => ({ (prev, curr) => ({
...prev, ...prev,
[curr]: photo.exif[curr], [curr]: media.exif[curr],
}), }),
{} {}
) )
@ -120,9 +160,9 @@ const SidebarContent = ({ photo, hidePreview }) => {
} }
let previewImage = null let previewImage = null
if (photo) { if (media) {
if (photo.highRes) previewImage = photo.highRes if (media.highRes) previewImage = media.highRes
else if (photo.thumbnail) previewImage = photo.thumbnail else if (media.thumbnail) previewImage = media.thumbnail
} }
return ( return (
@ -131,55 +171,53 @@ const SidebarContent = ({ photo, hidePreview }) => {
<PreviewImageWrapper <PreviewImageWrapper
imageAspect={previewImage.height / previewImage.width} imageAspect={previewImage.height / previewImage.width}
> >
<PreviewImage src={previewImage.url} /> <PreviewMedia previewImage={previewImage} media={media} />
</PreviewImageWrapper> </PreviewImageWrapper>
)} )}
<Name>{photo && photo.title}</Name> <Name>{media && media.title}</Name>
<ExifInfo>{exifItems}</ExifInfo> <ExifInfo>{exifItems}</ExifInfo>
<SidebarDownload photo={photo} /> <SidebarDownload photo={media} />
<SidebarShare photo={photo} /> <SidebarShare photo={media} />
</div> </div>
) )
} }
SidebarContent.propTypes = { SidebarContent.propTypes = {
photo: PropTypes.object, media: PropTypes.object,
hidePreview: PropTypes.bool, hidePreview: PropTypes.bool,
} }
class PhotoSidebar extends Component { const MediaSidebar = ({ media, hidePreview }) => {
render() { const [loadMedia, { loading, error, data }] = useLazyQuery(mediaQuery)
const { photo, hidePreview } = this.props
if (!photo) return null useEffect(() => {
if (media != null && localStorage.getItem('token')) {
loadMedia({
variables: {
id: media.id,
},
})
}
}, [media])
if (!media) return null
if (!localStorage.getItem('token')) { if (!localStorage.getItem('token')) {
return <SidebarContent photo={photo} hidePreview={hidePreview} /> return <SidebarContent media={media} hidePreview={hidePreview} />
} }
return (
<div>
<Query query={photoQuery} variables={{ id: photo.id }}>
{({ loading, error, data }) => {
if (error) return error if (error) return error
if (loading) { if (loading || data == null) {
return <SidebarContent photo={photo} hidePreview={hidePreview} /> return <SidebarContent media={media} hidePreview={hidePreview} />
} }
return ( return <SidebarContent media={data.media} hidePreview={hidePreview} />
<SidebarContent photo={data.media} hidePreview={hidePreview} />
)
}}
</Query>
</div>
)
}
} }
PhotoSidebar.propTypes = { MediaSidebar.propTypes = {
photo: PropTypes.object.isRequired, media: PropTypes.object.isRequired,
hidePreview: PropTypes.bool, hidePreview: PropTypes.bool,
} }
export default PhotoSidebar export default MediaSidebar