Start on video integration with web ui
This commit is contained in:
parent
f537b1d608
commit
b34115cab0
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }) => {
|
|||
<DisplayPhoto
|
||||
src={photo.highRes.url}
|
||||
onLoad={() => {
|
||||
updateSidebar(<PhotoSidebar photo={photo} hidePreview />)
|
||||
updateSidebar(<MediaSidebar media={photo} hidePreview />)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -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(<PhotoSidebar photo={photo} />)
|
||||
updateSidebar(<MediaSidebar media={photo} />)
|
||||
onSelectImage(index)
|
||||
}}
|
||||
setPresenting={setPresenting}
|
||||
|
@ -109,7 +109,7 @@ const PhotoGallery = ({
|
|||
</Gallery>
|
||||
{presenting && (
|
||||
<PresentView
|
||||
photo={activeImage}
|
||||
media={activeImage}
|
||||
{...{ nextImage, previousImage, setPresenting }}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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
|
|
@ -64,6 +64,7 @@ const NavigationButton = styled(OverlayButton)`
|
|||
`
|
||||
|
||||
const PresentNavigationOverlay = ({
|
||||
children,
|
||||
nextImage,
|
||||
previousImage,
|
||||
setPresenting,
|
||||
|
@ -92,6 +93,7 @@ const PresentNavigationOverlay = ({
|
|||
onMouseMove.current()
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<NavigationButton
|
||||
className={hide && 'hide'}
|
||||
float="left"
|
||||
|
@ -117,6 +119,7 @@ const PresentNavigationOverlay = ({
|
|||
}
|
||||
|
||||
PresentNavigationOverlay.propTypes = {
|
||||
children: PropTypes.element,
|
||||
nextImage: PropTypes.func.isRequired,
|
||||
previousImage: PropTypes.func.isRequired,
|
||||
setPresenting: PropTypes.func.isRequired,
|
||||
|
|
|
@ -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
|
|
@ -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 = ({
|
|||
}) => (
|
||||
<StyledContainer {...className}>
|
||||
<PreventScroll />
|
||||
<PresentPhoto photo={photo} imageLoaded={imageLoaded} />
|
||||
<PresentNavigationOverlay
|
||||
{...{ nextImage, previousImage, setPresenting }}
|
||||
/>
|
||||
<PresentNavigationOverlay {...{ nextImage, previousImage, setPresenting }}>
|
||||
<PresentMedia media={media} imageLoaded={imageLoaded} />
|
||||
</PresentNavigationOverlay>
|
||||
</StyledContainer>
|
||||
)
|
||||
|
||||
PresentView.propTypes = {
|
||||
photo: PropTypes.object.isRequired,
|
||||
media: PropTypes.object.isRequired,
|
||||
imageLoaded: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
nextImage: PropTypes.func.isRequired,
|
||||
|
|
|
@ -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 <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`
|
||||
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 }) => {
|
|||
<PreviewImageWrapper
|
||||
imageAspect={previewImage.height / previewImage.width}
|
||||
>
|
||||
<PreviewImage src={previewImage.url} />
|
||||
<PreviewMedia previewImage={previewImage} media={media} />
|
||||
</PreviewImageWrapper>
|
||||
)}
|
||||
<Name>{photo && photo.title}</Name>
|
||||
<Name>{media && media.title}</Name>
|
||||
<ExifInfo>{exifItems}</ExifInfo>
|
||||
<SidebarDownload photo={photo} />
|
||||
<SidebarShare photo={photo} />
|
||||
<SidebarDownload photo={media} />
|
||||
<SidebarShare photo={media} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 <SidebarContent photo={photo} hidePreview={hidePreview} />
|
||||
useEffect(() => {
|
||||
if (media != null && localStorage.getItem('token')) {
|
||||
loadMedia({
|
||||
variables: {
|
||||
id: media.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
}, [media])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Query query={photoQuery} variables={{ id: photo.id }}>
|
||||
{({ loading, error, data }) => {
|
||||
if (error) return error
|
||||
if (!media) return null
|
||||
|
||||
if (loading) {
|
||||
return <SidebarContent photo={photo} hidePreview={hidePreview} />
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarContent photo={data.media} hidePreview={hidePreview} />
|
||||
)
|
||||
}}
|
||||
</Query>
|
||||
</div>
|
||||
)
|
||||
if (!localStorage.getItem('token')) {
|
||||
return <SidebarContent media={media} hidePreview={hidePreview} />
|
||||
}
|
||||
|
||||
if (error) return error
|
||||
|
||||
if (loading || data == null) {
|
||||
return <SidebarContent media={media} hidePreview={hidePreview} />
|
||||
}
|
||||
|
||||
return <SidebarContent media={data.media} hidePreview={hidePreview} />
|
||||
}
|
||||
|
||||
PhotoSidebar.propTypes = {
|
||||
photo: PropTypes.object.isRequired,
|
||||
MediaSidebar.propTypes = {
|
||||
media: PropTypes.object.isRequired,
|
||||
hidePreview: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default PhotoSidebar
|
||||
export default MediaSidebar
|
Loading…
Reference in New Issue