Add download support
This commit is contained in:
parent
37aea1fb3a
commit
e00a5553f7
|
@ -106,8 +106,10 @@ type ComplexityRoot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoDownload struct {
|
PhotoDownload struct {
|
||||||
Title func(childComplexity int) int
|
Height func(childComplexity int) int
|
||||||
URL func(childComplexity int) int
|
Title func(childComplexity int) int
|
||||||
|
URL func(childComplexity int) int
|
||||||
|
Width func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
PhotoExif struct {
|
PhotoExif struct {
|
||||||
|
@ -571,6 +573,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.Photo.Title(childComplexity), true
|
return e.complexity.Photo.Title(childComplexity), true
|
||||||
|
|
||||||
|
case "PhotoDownload.height":
|
||||||
|
if e.complexity.PhotoDownload.Height == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.PhotoDownload.Height(childComplexity), true
|
||||||
|
|
||||||
case "PhotoDownload.title":
|
case "PhotoDownload.title":
|
||||||
if e.complexity.PhotoDownload.Title == nil {
|
if e.complexity.PhotoDownload.Title == nil {
|
||||||
break
|
break
|
||||||
|
@ -585,6 +594,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.PhotoDownload.URL(childComplexity), true
|
return e.complexity.PhotoDownload.URL(childComplexity), true
|
||||||
|
|
||||||
|
case "PhotoDownload.width":
|
||||||
|
if e.complexity.PhotoDownload.Width == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.PhotoDownload.Width(childComplexity), true
|
||||||
|
|
||||||
case "PhotoEXIF.aperture":
|
case "PhotoEXIF.aperture":
|
||||||
if e.complexity.PhotoExif.Aperture == nil {
|
if e.complexity.PhotoExif.Aperture == nil {
|
||||||
break
|
break
|
||||||
|
@ -1151,6 +1167,8 @@ type PhotoURL {
|
||||||
|
|
||||||
type PhotoDownload {
|
type PhotoDownload {
|
||||||
title: String!
|
title: String!
|
||||||
|
width: Int!
|
||||||
|
height: Int!
|
||||||
url: String!
|
url: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3252,6 +3270,80 @@ func (ec *executionContext) _PhotoDownload_title(ctx context.Context, field grap
|
||||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _PhotoDownload_width(ctx context.Context, field graphql.CollectedField, obj *models.PhotoDownload) (ret graphql.Marshaler) {
|
||||||
|
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
ec.Tracer.EndFieldExecution(ctx)
|
||||||
|
}()
|
||||||
|
rctx := &graphql.ResolverContext{
|
||||||
|
Object: "PhotoDownload",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||||
|
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Width, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !ec.HasError(rctx) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
rctx.Result = res
|
||||||
|
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _PhotoDownload_height(ctx context.Context, field graphql.CollectedField, obj *models.PhotoDownload) (ret graphql.Marshaler) {
|
||||||
|
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
ec.Tracer.EndFieldExecution(ctx)
|
||||||
|
}()
|
||||||
|
rctx := &graphql.ResolverContext{
|
||||||
|
Object: "PhotoDownload",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||||
|
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Height, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !ec.HasError(rctx) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
rctx.Result = res
|
||||||
|
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _PhotoDownload_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoDownload) (ret graphql.Marshaler) {
|
func (ec *executionContext) _PhotoDownload_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoDownload) (ret graphql.Marshaler) {
|
||||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -6402,6 +6494,16 @@ func (ec *executionContext) _PhotoDownload(ctx context.Context, sel ast.Selectio
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
invalids++
|
||||||
}
|
}
|
||||||
|
case "width":
|
||||||
|
out.Values[i] = ec._PhotoDownload_width(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "height":
|
||||||
|
out.Values[i] = ec._PhotoDownload_height(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
case "url":
|
case "url":
|
||||||
out.Values[i] = ec._PhotoDownload_url(ctx, field, obj)
|
out.Values[i] = ec._PhotoDownload_url(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
|
|
|
@ -33,8 +33,10 @@ type Notification struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PhotoDownload struct {
|
type PhotoDownload struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
URL string `json:"url"`
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXIF metadata from the camera
|
// EXIF metadata from the camera
|
||||||
|
|
|
@ -79,3 +79,17 @@ func NewPhotoURLFromRow(row *sql.Row) (*PhotoURL, error) {
|
||||||
|
|
||||||
return &url, nil
|
return &url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPhotoURLFromRows(rows *sql.Rows) ([]*PhotoURL, error) {
|
||||||
|
urls := make([]*PhotoURL, 0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var url PhotoURL
|
||||||
|
if err := rows.Scan(&url.UrlID, &url.PhotoId, &url.PhotoName, &url.Width, &url.Height, &url.Purpose, &url.ContentType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
urls = append(urls, &url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
|
@ -69,8 +69,39 @@ func (r *photoResolver) Shares(ctx context.Context, obj *models.Photo) ([]*model
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *photoResolver) Downloads(ctx context.Context, obj *models.Photo) ([]*models.PhotoDownload, error) {
|
func (r *photoResolver) Downloads(ctx context.Context, obj *models.Photo) ([]*models.PhotoDownload, error) {
|
||||||
log.Println("Photo: downloads not implemented")
|
|
||||||
|
rows, err := r.Database.Query("SELECT * FROM photo_url WHERE photo_id = ?", obj.PhotoID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
photoUrls, err := models.NewPhotoURLFromRows(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
downloads := make([]*models.PhotoDownload, 0)
|
downloads := make([]*models.PhotoDownload, 0)
|
||||||
|
|
||||||
|
for _, url := range photoUrls {
|
||||||
|
|
||||||
|
var title string
|
||||||
|
switch {
|
||||||
|
case url.Purpose == models.PhotoOriginal:
|
||||||
|
title = "Original"
|
||||||
|
case url.Purpose == models.PhotoThumbnail:
|
||||||
|
title = "Small"
|
||||||
|
case url.Purpose == models.PhotoHighRes:
|
||||||
|
title = "Large"
|
||||||
|
}
|
||||||
|
|
||||||
|
downloads = append(downloads, &models.PhotoDownload{
|
||||||
|
Title: title,
|
||||||
|
Width: url.Width,
|
||||||
|
Height: url.Height,
|
||||||
|
URL: url.URL(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return downloads, nil
|
return downloads, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,8 @@ type PhotoURL {
|
||||||
|
|
||||||
type PhotoDownload {
|
type PhotoDownload {
|
||||||
title: String!
|
title: String!
|
||||||
|
width: Int!
|
||||||
|
height: Int!
|
||||||
url: String!
|
url: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,13 @@ import (
|
||||||
func CORSMiddleware(devMode bool) mux.MiddlewareFunc {
|
func CORSMiddleware(devMode bool) mux.MiddlewareFunc {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
methods := []string{http.MethodGet, http.MethodPost, http.MethodOptions}
|
methods := []string{http.MethodGet, http.MethodPost, http.MethodOptions}
|
||||||
headers := []string{"authorization", "content-type"}
|
headers := []string{"authorization", "content-type", "content-length"}
|
||||||
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
|
w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ","))
|
||||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ","))
|
w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ","))
|
||||||
|
w.Header().Set("Access-Control-Expose-Headers", "content-length")
|
||||||
|
|
||||||
endpoint, err := url.Parse(os.Getenv("API_ENDPOINT"))
|
endpoint, err := url.Parse(os.Getenv("API_ENDPOINT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -46,6 +46,12 @@ const tokenQuery = gql`
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
}
|
}
|
||||||
|
downloads {
|
||||||
|
title
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
highRes {
|
highRes {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
|
||||||
if (graphQLErrors.length == 1) {
|
if (graphQLErrors.length == 1) {
|
||||||
errorMessages.push({
|
errorMessages.push({
|
||||||
header: 'Something went wrong',
|
header: 'Something went wrong',
|
||||||
content: graphQLErrors[0].message,
|
content: `Server error: ${graphQLErrors[0].message} at (${graphQLErrors[0].path})`,
|
||||||
})
|
})
|
||||||
} else if (graphQLErrors.length > 1) {
|
} else if (graphQLErrors.length > 1) {
|
||||||
errorMessages.push({
|
errorMessages.push({
|
||||||
|
|
|
@ -17,17 +17,22 @@ export let MessageState = {
|
||||||
set: null,
|
set: null,
|
||||||
get: null,
|
get: null,
|
||||||
add: message => {
|
add: message => {
|
||||||
MessageState.set(messages => [...messages, message])
|
MessageState.set(messages => {
|
||||||
|
const newMessages = messages.filter(msg => msg.key != message.key)
|
||||||
|
newMessages.push(message)
|
||||||
|
|
||||||
|
return newMessages
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeKey: key => {
|
||||||
|
MessageState.set(messages => {
|
||||||
|
const newMessages = messages.filter(msg => msg.key != key)
|
||||||
|
return newMessages
|
||||||
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const Messages = () => {
|
const Messages = () => {
|
||||||
if (!localStorage.getItem('token')) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Rendering messages')
|
|
||||||
|
|
||||||
const [messages, setMessages] = useState([])
|
const [messages, setMessages] = useState([])
|
||||||
MessageState.set = setMessages
|
MessageState.set = setMessages
|
||||||
MessageState.get = messages
|
MessageState.get = messages
|
||||||
|
@ -76,7 +81,6 @@ const Messages = () => {
|
||||||
resolveFunc = resolve
|
resolveFunc = resolve
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(resolveFunc, waitPromise)
|
|
||||||
refHooks.set(message.key, {
|
refHooks.set(message.key, {
|
||||||
done: resolveFunc,
|
done: resolveFunc,
|
||||||
promise: waitPromise,
|
promise: waitPromise,
|
||||||
|
@ -89,13 +93,8 @@ const Messages = () => {
|
||||||
height: '0px',
|
height: '0px',
|
||||||
},
|
},
|
||||||
enter: item => async next => {
|
enter: item => async next => {
|
||||||
console.log('HERE', refMap, item)
|
|
||||||
|
|
||||||
const refPromise = refHooks.get(item.key).promise
|
const refPromise = refHooks.get(item.key).promise
|
||||||
console.log('promise', refPromise)
|
|
||||||
|
|
||||||
await refPromise
|
await refPromise
|
||||||
console.log('AFTER PROMISE', refMap, item)
|
|
||||||
|
|
||||||
await next({
|
await next({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
|
@ -109,7 +108,6 @@ const Messages = () => {
|
||||||
<Container>
|
<Container>
|
||||||
{transitions.map(({ item, props: style, key }) => {
|
{transitions.map(({ item, props: style, key }) => {
|
||||||
const getRef = ref => {
|
const getRef = ref => {
|
||||||
console.log('GET REF', refMap, refHooks, item.key)
|
|
||||||
refMap.set(item, ref)
|
refMap.set(item, ref)
|
||||||
if (refHooks.has(item.key)) {
|
if (refHooks.has(item.key)) {
|
||||||
refHooks.get(item.key).done()
|
refHooks.get(item.key).done()
|
||||||
|
|
|
@ -18,6 +18,10 @@ const notificationSubscription = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
const SubscriptionsHook = ({ messages, setMessages }) => {
|
const SubscriptionsHook = ({ messages, setMessages }) => {
|
||||||
|
if (!localStorage.getItem('token')) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const { data, error } = useSubscription(notificationSubscription)
|
const { data, error } = useSubscription(notificationSubscription)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -62,34 +66,6 @@ const SubscriptionsHook = ({ messages, setMessages }) => {
|
||||||
newMessages.push(newNotification)
|
newMessages.push(newNotification)
|
||||||
}
|
}
|
||||||
|
|
||||||
// const update = data.scannerStatusUpdate
|
|
||||||
|
|
||||||
// if (update.success) {
|
|
||||||
// newMessages[0] = {
|
|
||||||
// key: 'primary',
|
|
||||||
// type: 'progress',
|
|
||||||
// props: {
|
|
||||||
// header: update.finished ? 'Synced' : 'Syncing',
|
|
||||||
// content: update.message,
|
|
||||||
// percent: update.progress,
|
|
||||||
// positive: update.finished,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!update.finished) newMessages[0].props.onDismiss = null
|
|
||||||
// } else {
|
|
||||||
// const key = Math.random().toString(26)
|
|
||||||
// newMessages.push({
|
|
||||||
// key,
|
|
||||||
// type: 'message',
|
|
||||||
// props: {
|
|
||||||
// header: 'Sync error',
|
|
||||||
// content: update.message,
|
|
||||||
// negative: true,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
setMessages(newMessages)
|
setMessages(newMessages)
|
||||||
}, [data, error])
|
}, [data, error])
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ const ProtectedImage = ({ src, ...props }) => {
|
||||||
// Get share token if not authorized
|
// Get share token if not authorized
|
||||||
|
|
||||||
const token = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
const token = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||||
console.log('share token', location.pathname, token)
|
|
||||||
if (token) {
|
if (token) {
|
||||||
imgUrl.searchParams.set('token', token[1])
|
imgUrl.searchParams.set('token', token[1])
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ const SidebarContent = ({ photo, hidePreview }) => {
|
||||||
{!hidePreview && <PreviewImage src={previewUrl} />}
|
{!hidePreview && <PreviewImage src={previewUrl} />}
|
||||||
<Name>{photo && photo.title}</Name>
|
<Name>{photo && photo.title}</Name>
|
||||||
<div>{exifItems}</div>
|
<div>{exifItems}</div>
|
||||||
<SidebarDownload photoId={photo.id} />
|
<SidebarDownload photo={photo} />
|
||||||
<SidebarShare photo={photo} />
|
<SidebarShare photo={photo} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Menu, Dropdown, Button } from 'semantic-ui-react'
|
import { Menu, Dropdown, Button } from 'semantic-ui-react'
|
||||||
import { Query } from 'react-apollo'
|
import { MessageState } from '../messages/Messages'
|
||||||
|
import { Query, useLazyQuery } from 'react-apollo'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import download from 'downloadjs'
|
import download from 'downloadjs'
|
||||||
|
|
||||||
|
@ -12,49 +13,157 @@ const downloadQuery = gql`
|
||||||
downloads {
|
downloads {
|
||||||
title
|
title
|
||||||
url
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
if (bytes == 0) return '0 Byte'
|
||||||
|
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
|
||||||
|
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
const downloadPhoto = async url => {
|
const downloadPhoto = async url => {
|
||||||
const request = await fetch(url, {
|
const imgUrl = new URL(url)
|
||||||
headers: {
|
let headers = {
|
||||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem('token') == null) {
|
||||||
|
// Get share token if not authorized
|
||||||
|
const token = location.pathname.match(/^\/share\/([\d\w]+)(\/?.*)$/)
|
||||||
|
if (token) {
|
||||||
|
imgUrl.searchParams.set('token', token[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(imgUrl.href, {
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalBytes = Number(response.headers.get('content-length'))
|
||||||
|
console.log(totalBytes)
|
||||||
|
|
||||||
|
if (totalBytes == 0) {
|
||||||
|
MessageState.add({
|
||||||
|
key: Math.random().toString(26),
|
||||||
|
type: 'message',
|
||||||
|
props: {
|
||||||
|
header: 'Error downloading photo',
|
||||||
|
content: `Could not get size of photo from server`,
|
||||||
|
negative: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifKey = Math.random().toString(26)
|
||||||
|
MessageState.add({
|
||||||
|
key: notifKey,
|
||||||
|
type: 'progress',
|
||||||
|
props: {
|
||||||
|
header: 'Downloading photo',
|
||||||
|
content: `Starting download`,
|
||||||
|
progress: 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const content = await request.blob()
|
const reader = response.body.getReader()
|
||||||
|
let data = new Uint8Array(totalBytes)
|
||||||
|
|
||||||
|
let receivedBytes = 0
|
||||||
|
let result
|
||||||
|
do {
|
||||||
|
result = await reader.read()
|
||||||
|
|
||||||
|
if (result.value) data.set(result.value, receivedBytes)
|
||||||
|
|
||||||
|
receivedBytes += result.value ? result.value.length : 0
|
||||||
|
|
||||||
|
MessageState.add({
|
||||||
|
key: notifKey,
|
||||||
|
type: 'progress',
|
||||||
|
props: {
|
||||||
|
header: 'Downloading photo',
|
||||||
|
percent: (receivedBytes / totalBytes) * 100,
|
||||||
|
content: `${formatBytes(receivedBytes)} of ${formatBytes(
|
||||||
|
totalBytes
|
||||||
|
)} bytes downloaded`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} while (!result.done)
|
||||||
|
|
||||||
|
MessageState.add({
|
||||||
|
key: notifKey,
|
||||||
|
type: 'progress',
|
||||||
|
props: {
|
||||||
|
header: 'Downloading photo completed',
|
||||||
|
content: `The photo has been downloaded`,
|
||||||
|
percent: 100,
|
||||||
|
positive: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
MessageState.removeKey(notifKey)
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
const content = new Blob([data.buffer], {
|
||||||
|
type: response.headers.get('content-type'),
|
||||||
|
})
|
||||||
const filename = url.match(/[^/]*$/)[0]
|
const filename = url.match(/[^/]*$/)[0]
|
||||||
|
|
||||||
download(content, filename)
|
download(content, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarDownload = ({ photoId }) => {
|
const SidebarDownload = ({ photo }) => {
|
||||||
if (!photoId) return null
|
if (!photo || !photo.id) return null
|
||||||
|
|
||||||
|
const [
|
||||||
|
loadPhotoDownloads,
|
||||||
|
{ called, loading, data },
|
||||||
|
] = useLazyQuery(downloadQuery, { variables: { photoId: photo.id } })
|
||||||
|
|
||||||
|
let downloads = []
|
||||||
|
|
||||||
|
if (called) {
|
||||||
|
if (!loading) {
|
||||||
|
downloads = data && data.photo.downloads
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!photo.downloads) {
|
||||||
|
loadPhotoDownloads()
|
||||||
|
} else {
|
||||||
|
downloads = photo.downloads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = downloads.map(x => (
|
||||||
|
<Button
|
||||||
|
style={{ marginTop: 4 }}
|
||||||
|
key={x.url}
|
||||||
|
onClick={() => downloadPhoto(x.url)}
|
||||||
|
>
|
||||||
|
{`${x.title} (${x.width} x ${x.height})`}
|
||||||
|
</Button>
|
||||||
|
))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: 24 }}>
|
<div style={{ marginBottom: 24 }}>
|
||||||
<h2>Download</h2>
|
<h2>Download</h2>
|
||||||
<Query query={downloadQuery} variables={{ photoId }}>
|
<div>{buttons}</div>
|
||||||
{({ loading, error, data }) => {
|
|
||||||
if (error) return <div>Error {error.message}</div>
|
|
||||||
if (!data || !data.photo) return null
|
|
||||||
|
|
||||||
let buttons = data.photo.downloads.map(x => (
|
|
||||||
<Button key={x.url} onClick={() => downloadPhoto(x.url)}>
|
|
||||||
{x.title}
|
|
||||||
</Button>
|
|
||||||
))
|
|
||||||
return <Button.Group>{buttons}</Button.Group>
|
|
||||||
}}
|
|
||||||
</Query>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SidebarDownload.propTypes = {
|
SidebarDownload.propTypes = {
|
||||||
photoId: PropTypes.number,
|
photo: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SidebarDownload
|
export default SidebarDownload
|
||||||
|
|
Loading…
Reference in New Issue