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"
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 />)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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 = ({
|
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,
|
||||||
|
|
|
@ -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 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,
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue