Move photourl to database
This commit is contained in:
parent
208cbda19a
commit
7a54fc9bc9
1
api/.env
1
api/.env
|
@ -3,5 +3,6 @@ NEO4J_USER=neo4j
|
||||||
NEO4J_PASSWORD=letmein
|
NEO4J_PASSWORD=letmein
|
||||||
GRAPHQL_LISTEN_PORT=4001
|
GRAPHQL_LISTEN_PORT=4001
|
||||||
GRAPHQL_URI=http://localhost:4001/graphql
|
GRAPHQL_URI=http://localhost:4001/graphql
|
||||||
|
HOST=http://localhost:4001
|
||||||
|
|
||||||
JWT_SECRET=topSecretEpicJWTKEYThing
|
JWT_SECRET=topSecretEpicJWTKEYThing
|
|
@ -7,8 +7,11 @@ import { exiftool } from 'exiftool-vendored'
|
||||||
import sharp from 'sharp'
|
import sharp from 'sharp'
|
||||||
import readChunk from 'read-chunk'
|
import readChunk from 'read-chunk'
|
||||||
import imageType from 'image-type'
|
import imageType from 'image-type'
|
||||||
|
import { promisify } from 'util'
|
||||||
import config from './config'
|
import config from './config'
|
||||||
|
|
||||||
|
const imageSize = promisify(require('image-size'))
|
||||||
|
|
||||||
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
|
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
|
||||||
|
|
||||||
const isImage = async path => {
|
const isImage = async path => {
|
||||||
|
@ -125,10 +128,11 @@ class PhotoScanner {
|
||||||
|
|
||||||
let foundAlbumIds = []
|
let foundAlbumIds = []
|
||||||
|
|
||||||
async function scanPath(path) {
|
async function scanPath(path, parentAlbum) {
|
||||||
const list = fs.readdirSync(path)
|
const list = fs.readdirSync(path)
|
||||||
|
|
||||||
let foundImage = false
|
let foundImage = false
|
||||||
|
let newAlbums = []
|
||||||
|
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const itemPath = pathResolve(path, item)
|
const itemPath = pathResolve(path, item)
|
||||||
|
@ -136,34 +140,39 @@ class PhotoScanner {
|
||||||
const stat = fs.statSync(itemPath)
|
const stat = fs.statSync(itemPath)
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
// console.log(`Entering directory ${itemPath}`)
|
const session = driver.session()
|
||||||
const imagesInDirectory = await scanPath(itemPath)
|
let nextParentAlbum = null
|
||||||
|
|
||||||
|
const findAlbumResult = await session.run(
|
||||||
|
'MATCH (a:Album { path: {path} }) RETURN a',
|
||||||
|
{
|
||||||
|
path: itemPath,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
if (findAlbumResult.records.length != 0) {
|
||||||
|
const album = findAlbumResult.records[0].toObject().a.properties
|
||||||
|
console.log('Found existing album', album.title)
|
||||||
|
|
||||||
|
foundAlbumIds.push(album.id)
|
||||||
|
|
||||||
|
nextParentAlbum = album.id
|
||||||
|
scanAlbum(album)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
foundImage: imagesInDirectory,
|
||||||
|
newAlbums: childAlbums,
|
||||||
|
} = await scanPath(itemPath, nextParentAlbum)
|
||||||
|
|
||||||
if (imagesInDirectory) {
|
if (imagesInDirectory) {
|
||||||
console.log(`Found album at ${itemPath}`)
|
console.log(`Found new album at ${itemPath}`)
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
|
|
||||||
const findAlbumResult = await session.run(
|
|
||||||
'MATCH (a:Album { path: {path} }) RETURN a',
|
|
||||||
{
|
|
||||||
path: itemPath,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('FIND ALBUM RESULT', findAlbumResult.records)
|
|
||||||
|
|
||||||
if (findAlbumResult.records.length != 0) {
|
|
||||||
console.log('Album already exists')
|
|
||||||
|
|
||||||
const album = findAlbumResult.records[0].toObject().a.properties
|
|
||||||
|
|
||||||
foundAlbumIds.push(album.id)
|
|
||||||
|
|
||||||
scanAlbum(album)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Adding album')
|
console.log('Adding album')
|
||||||
const albumId = uuid()
|
const albumId = uuid()
|
||||||
const albumResult = await session.run(
|
const albumResult = await session.run(
|
||||||
|
@ -179,7 +188,36 @@ class PhotoScanner {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
foundAlbumIds.push(albumId)
|
||||||
|
newAlbums.push(albumId)
|
||||||
const album = albumResult.records[0].toObject().a.properties
|
const album = albumResult.records[0].toObject().a.properties
|
||||||
|
|
||||||
|
if (parentAlbum) {
|
||||||
|
console.log('Linking parent album for', album.title)
|
||||||
|
await session.run(
|
||||||
|
`MATCH (parent:Album { id: {parentId} })
|
||||||
|
MATCH (child:Album { id: {childId} })
|
||||||
|
CREATE (parent)-[:SUBALBUM]->(child)`,
|
||||||
|
{
|
||||||
|
childId: albumId,
|
||||||
|
parentId: parentAlbum,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Linking ${childAlbums.length} child albums`)
|
||||||
|
for (let childAlbum of childAlbums) {
|
||||||
|
await session.run(
|
||||||
|
`MATCH (parent:Album { id: {parentId} })
|
||||||
|
MATCH (child:Album { id: {childId} })
|
||||||
|
CREATE (parent)-[:SUBALBUM]->(child)`,
|
||||||
|
{
|
||||||
|
parentId: albumId,
|
||||||
|
childId: childAlbum,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
scanAlbum(album)
|
scanAlbum(album)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
@ -193,7 +231,7 @@ class PhotoScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundImage
|
return { foundImage, newAlbums }
|
||||||
}
|
}
|
||||||
|
|
||||||
await scanPath(user.rootPath)
|
await scanPath(user.rootPath)
|
||||||
|
@ -203,7 +241,7 @@ class PhotoScanner {
|
||||||
const session = this.driver.session()
|
const session = this.driver.session()
|
||||||
|
|
||||||
const userAlbumsResult = await session.run(
|
const userAlbumsResult = await session.run(
|
||||||
'MATCH (u:User { id: {userId} })-[:OWNS]->(a:Album) WHERE NOT a.id IN {foundAlbums} DETACH DELETE a return a',
|
'MATCH (u:User { id: {userId} })-[:OWNS]->(a:Album)-[:CONTAINS]->(p:Photo) WHERE NOT a.id IN {foundAlbums} DETACH DELETE a, p RETURN a',
|
||||||
{ userId: user.id, foundAlbums: foundAlbumIds }
|
{ userId: user.id, foundAlbums: foundAlbumIds }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -276,22 +314,29 @@ class PhotoScanner {
|
||||||
}
|
}
|
||||||
|
|
||||||
async processImage(id) {
|
async processImage(id) {
|
||||||
console.log('Processing image')
|
|
||||||
const session = this.driver.session()
|
const session = this.driver.session()
|
||||||
|
|
||||||
const result = await session.run('MATCH (p:Photo { id: {id} }) return p', {
|
const result = await session.run(`MATCH (p:Photo { id: {id} }) RETURN p`, {
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await session.run(
|
||||||
|
`MATCH (p:Photo { id: {id} })-[rel]->(url:PhotoURL) DELETE url, rel`,
|
||||||
|
{ id }
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('PROCESS IMAGE RESULT', result)
|
||||||
|
|
||||||
const photo = result.records[0].get('p').properties
|
const photo = result.records[0].get('p').properties
|
||||||
|
|
||||||
console.log('PHOTO', photo.path)
|
console.log('Processing photo', photo.path)
|
||||||
|
|
||||||
const imagePath = path.resolve(config.cachePath, 'images', id)
|
const imagePath = path.resolve(config.cachePath, 'images', id)
|
||||||
|
|
||||||
await fs.remove(imagePath)
|
await fs.remove(imagePath)
|
||||||
await fs.mkdirp(imagePath)
|
await fs.mkdirp(imagePath)
|
||||||
|
|
||||||
let resizeBaseImg = photo.path
|
let originalPath = photo.path
|
||||||
|
|
||||||
if (await isRawImage(photo.path)) {
|
if (await isRawImage(photo.path)) {
|
||||||
console.log('Processing RAW image')
|
console.log('Processing RAW image')
|
||||||
|
@ -299,15 +344,40 @@ class PhotoScanner {
|
||||||
const extractedPath = path.resolve(imagePath, 'extracted.jpg')
|
const extractedPath = path.resolve(imagePath, 'extracted.jpg')
|
||||||
await exiftool.extractPreview(photo.path, extractedPath)
|
await exiftool.extractPreview(photo.path, extractedPath)
|
||||||
|
|
||||||
resizeBaseImg = extractedPath
|
originalPath = extractedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize image
|
// Resize image
|
||||||
console.log('Resizing image', resizeBaseImg)
|
const thumbnailPath = path.resolve(imagePath, 'thumbnail.jpg')
|
||||||
await sharp(resizeBaseImg)
|
await sharp(originalPath)
|
||||||
.jpeg({ quality: 80 })
|
.jpeg({ quality: 80 })
|
||||||
.resize(1440, 1080, { fit: 'inside', withoutEnlargement: true })
|
.resize(1440, 1080, { fit: 'inside', withoutEnlargement: true })
|
||||||
.toFile(path.resolve(imagePath, 'thumbnail.jpg'))
|
.toFile(thumbnailPath)
|
||||||
|
|
||||||
|
const { width: originalWidth, height: originalHeight } = await imageSize(
|
||||||
|
originalPath
|
||||||
|
)
|
||||||
|
const { width: thumbnailWidth, height: thumbnailHeight } = await imageSize(
|
||||||
|
thumbnailPath
|
||||||
|
)
|
||||||
|
|
||||||
|
await session.run(
|
||||||
|
`MATCH (p:Photo { id: {id} })
|
||||||
|
CREATE (thumbnail:PhotoURL { url: {thumbnailUrl}, width: {thumbnailWidth}, height: {thumbnailHeight} })
|
||||||
|
CREATE (original:PhotoURL { url: {originalUrl}, width: {originalWidth}, height: {originalHeight} })
|
||||||
|
CREATE (p)-[:THUMBNAIL_URL]->(thumbnail)
|
||||||
|
CREATE (p)-[:ORIGINAL_URL]->(original)
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
thumbnailUrl: `/images/${id}/${path.basename(thumbnailPath)}`,
|
||||||
|
thumbnailWidth,
|
||||||
|
thumbnailHeight,
|
||||||
|
originalUrl: `/images/${id}/${path.basename(originalPath)}`,
|
||||||
|
originalWidth,
|
||||||
|
originalHeight,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,5 @@ import path from 'path'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
cachePath: path.resolve(__dirname, 'cache'),
|
cachePath: path.resolve(__dirname, 'cache'),
|
||||||
|
host: process.env.HOST || 'http://localhost:4001/',
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,10 +126,6 @@ app.use('/images/:id/:image', async function(req, res) {
|
||||||
|
|
||||||
console.log('image', image)
|
console.log('image', image)
|
||||||
|
|
||||||
if (image != 'original.jpg' && image != 'thumbnail.jpg') {
|
|
||||||
return res.status(404).send('Image not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = null
|
let user = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -142,7 +138,7 @@ app.use('/images/:id/:image', async function(req, res) {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
|
|
||||||
const result = await session.run(
|
const result = await session.run(
|
||||||
'MATCH (p:Photo { id: {id} }) MATCH (p)<-[:CONTAINS]-(:Album)<-[:OWNS]-(u:User) RETURN p as photo, u.id as userId',
|
'MATCH (p:Photo { id: {id} })<-[:CONTAINS]-(:Album)<-[:OWNS]-(u:User) RETURN p as photo, u.id as userId',
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
}
|
}
|
||||||
|
@ -153,7 +149,7 @@ app.use('/images/:id/:image', async function(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = result.records[0].get('userId')
|
const userId = result.records[0].get('userId')
|
||||||
const photo = result.records[0].get('photo')
|
const photo = result.records[0].get('photo').properties
|
||||||
|
|
||||||
if (userId != user.id) {
|
if (userId != user.id) {
|
||||||
return res.status(401).send(`Image not owned by you`)
|
return res.status(401).send(`Image not owned by you`)
|
||||||
|
@ -161,7 +157,12 @@ app.use('/images/:id/:image', async function(req, res) {
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
const imagePath = path.resolve(config.cachePath, 'images', id, image)
|
let imagePath = path.resolve(config.cachePath, 'images', id, image)
|
||||||
|
|
||||||
|
if (image != 'extracted.jpg' && image != 'thumbnail.jpg') {
|
||||||
|
imagePath = photo.path
|
||||||
|
}
|
||||||
|
|
||||||
const imageFound = await fs.exists(imagePath)
|
const imageFound = await fs.exists(imagePath)
|
||||||
|
|
||||||
if (!imageFound) {
|
if (!imageFound) {
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import { cypherQuery } from 'neo4j-graphql-js'
|
import { cypherQuery } from 'neo4j-graphql-js'
|
||||||
import { promisify } from 'util'
|
|
||||||
import fs from 'fs-extra'
|
|
||||||
import path from 'path'
|
|
||||||
import config from '../config'
|
import config from '../config'
|
||||||
import { isRawImage } from '../Scanner'
|
|
||||||
|
|
||||||
const imageSize = promisify(require('image-size'))
|
|
||||||
|
|
||||||
function injectAt(query, index, injection) {
|
function injectAt(query, index, injection) {
|
||||||
return query.substr(0, index) + injection + query.substr(index)
|
return query.substr(0, index) + injection + query.substr(index)
|
||||||
|
@ -130,75 +124,13 @@ const Query = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function photoResolver(image) {
|
const PhotoURL = {
|
||||||
return async (root, args, ctx, info) => {
|
url(root, args, ctx, info) {
|
||||||
const imgPath = path.resolve(config.cachePath, 'images', root.id, image)
|
return new URL(root.url, config.host).href
|
||||||
|
|
||||||
if (!(await fs.exists(imgPath))) {
|
|
||||||
await ctx.scanner.processImage(root.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { width, height } = await imageSize(imgPath)
|
|
||||||
return {
|
|
||||||
path: `${ctx.endpoint}/images/${root.id}/${image}`,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Photo = {
|
|
||||||
// TODO: Make original point to the right path
|
|
||||||
original: async (root, args, ctx, info) => {
|
|
||||||
async function getPath(retryAfterScan = false) {
|
|
||||||
let imgPath = path.resolve(
|
|
||||||
config.cachePath,
|
|
||||||
'images',
|
|
||||||
root.id,
|
|
||||||
'extracted.jpg'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!(await fs.exists(imgPath))) {
|
|
||||||
imgPath = root.path
|
|
||||||
|
|
||||||
if (!imgPath) {
|
|
||||||
const session = ctx.driver.session()
|
|
||||||
|
|
||||||
const result = await session.run(
|
|
||||||
'MATCH (p:Photo { id: {id} }) return p.path as path',
|
|
||||||
{
|
|
||||||
id: root.id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
imgPath = result.get('path')
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await fs.exists(imgPath)) || (await isRawImage(imgPath))) {
|
|
||||||
if (retryAfterScan)
|
|
||||||
throw new Error('Could not find image after rescan')
|
|
||||||
await ctx.scanner.processImage(root.id)
|
|
||||||
return getPath(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imgPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const imgPath = await getPath()
|
|
||||||
|
|
||||||
const { width, height } = await imageSize(imgPath)
|
|
||||||
return {
|
|
||||||
path: `${ctx.endpoint}/images/${root.id}/${path.basename(imgPath)}`,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
thumbnail: photoResolver('thumbnail.jpg'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query,
|
Query,
|
||||||
Photo,
|
PhotoURL,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,15 @@ type Album {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String
|
title: String
|
||||||
photos: [Photo] @relation(name: "CONTAINS", direction: "OUT")
|
photos: [Photo] @relation(name: "CONTAINS", direction: "OUT")
|
||||||
|
subAlbums: [Album] @relation(name: "SUBALBUM", direction: "OUT")
|
||||||
|
parentAlbum: Album @relation(name: "SUBALBUM", direction: "IN")
|
||||||
owner: User! @relation(name: "OWNS", direction: "IN")
|
owner: User! @relation(name: "OWNS", direction: "IN")
|
||||||
path: String
|
path: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type PhotoURL {
|
type PhotoURL {
|
||||||
# URL for the image
|
# URL for the image
|
||||||
path: String
|
url: String
|
||||||
# Width of the image in pixels
|
# Width of the image in pixels
|
||||||
width: Int
|
width: Int
|
||||||
# Height of the image in pixels
|
# Height of the image in pixels
|
||||||
|
@ -35,9 +37,9 @@ type Photo {
|
||||||
# Local filepath for the photo
|
# Local filepath for the photo
|
||||||
path: String
|
path: String
|
||||||
# URL to display the photo in full resolution
|
# URL to display the photo in full resolution
|
||||||
original: PhotoURL @neo4j_ignore
|
original: PhotoURL @relation(name: "ORIGINAL_URL", direction: "OUT")
|
||||||
# URL to display the photo in a smaller resolution
|
# URL to display the photo in a smaller resolution
|
||||||
thumbnail: PhotoURL @neo4j_ignore
|
thumbnail: PhotoURL @relation(name: "THUMBNAIL_URL", direction: "OUT")
|
||||||
# The album that holds the photo
|
# The album that holds the photo
|
||||||
album: Album! @relation(name: "CONTAINS", direction: "IN")
|
album: Album! @relation(name: "CONTAINS", direction: "IN")
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,19 @@ const albumQuery = gql`
|
||||||
query albumQuery($id: ID) {
|
query albumQuery($id: ID) {
|
||||||
album(id: $id) {
|
album(id: $id) {
|
||||||
title
|
title
|
||||||
|
subAlbums {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
photos {
|
||||||
|
thumbnail {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
photos {
|
photos {
|
||||||
id
|
id
|
||||||
thumbnail {
|
thumbnail {
|
||||||
path
|
url
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ const photoQuery = gql`
|
||||||
photo(id: $id) {
|
photo(id: $id) {
|
||||||
title
|
title
|
||||||
original {
|
original {
|
||||||
path
|
url
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class AlbumSidebar extends Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PreviewImage src={photo.original.path} />
|
<PreviewImage src={photo.original.url} />
|
||||||
<Name>{photo.title}</Name>
|
<Name>{photo.title}</Name>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
const AlbumBoxLink = styled(Link)`
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: #222;
|
||||||
|
`
|
||||||
|
|
||||||
|
const AlbumBoxImage = styled.div`
|
||||||
|
width: 220px;
|
||||||
|
height: 220px;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 4%;
|
||||||
|
background-image: url('${props => props.image}');
|
||||||
|
background-color: #eee;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const AlbumBox = ({ album, ...props }) => {
|
||||||
|
if (!album) {
|
||||||
|
return (
|
||||||
|
<AlbumBoxLink {...props} to="#">
|
||||||
|
<AlbumBoxImage />
|
||||||
|
</AlbumBoxLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlbumBoxLink {...props} to={`/album/${album.id}`}>
|
||||||
|
<AlbumBoxImage image={album.photos[0] && album.photos[0].thumbnail.url} />
|
||||||
|
<p>{album.title}</p>
|
||||||
|
</AlbumBoxLink>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { Loader } from 'semantic-ui-react'
|
||||||
|
import { AlbumBox } from './AlbumBox'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
margin: -10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 500px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const AlbumGallery = ({ loading, error, albums }) => {
|
||||||
|
if (error) return <div>Error {error.message}</div>
|
||||||
|
|
||||||
|
let albumElements = []
|
||||||
|
|
||||||
|
if (albums) {
|
||||||
|
albumElements = albums.map(album => (
|
||||||
|
<AlbumBox key={album.id} album={album} />
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
albumElements.push(<AlbumBox key={i} />)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Loader active={loading}>Loading albums</Loader>
|
||||||
|
{albumElements}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumGallery
|
|
@ -1,93 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import gql from 'graphql-tag'
|
|
||||||
import { Query } from 'react-apollo'
|
|
||||||
import styled from 'styled-components'
|
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import { Loader } from 'semantic-ui-react'
|
|
||||||
|
|
||||||
const getAlbumsQuery = gql`
|
|
||||||
query getMyAlbums {
|
|
||||||
myAlbums {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
photos {
|
|
||||||
thumbnail {
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
margin: -10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
position: relative;
|
|
||||||
min-height: 500px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const AlbumBoxLink = styled(Link)`
|
|
||||||
width: 240px;
|
|
||||||
height: 240px;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
color: #222;
|
|
||||||
`
|
|
||||||
|
|
||||||
const AlbumBoxImage = styled.div`
|
|
||||||
width: 220px;
|
|
||||||
height: 220px;
|
|
||||||
margin: auto;
|
|
||||||
border-radius: 4%;
|
|
||||||
background-image: url('${props => props.image}');
|
|
||||||
background-color: #eee;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
class Albums extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<Query query={getAlbumsQuery}>
|
|
||||||
{({ loading, error, data }) => {
|
|
||||||
// if (loading) return <Loader active />
|
|
||||||
if (error) return <div>Error {error.message}</div>
|
|
||||||
|
|
||||||
let albums
|
|
||||||
|
|
||||||
if (data && data.myAlbums) {
|
|
||||||
albums = data.myAlbums.map(album => (
|
|
||||||
<AlbumBoxLink key={album.id} to={`/album/${album.id}`}>
|
|
||||||
<AlbumBoxImage
|
|
||||||
image={album.photos[0] && album.photos[0].thumbnail.path}
|
|
||||||
/>
|
|
||||||
<p>{album.title}</p>
|
|
||||||
</AlbumBoxLink>
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
albums = []
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
albums.push(
|
|
||||||
<AlbumBoxLink key={i} to="#">
|
|
||||||
<AlbumBoxImage />
|
|
||||||
</AlbumBoxLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{' '}
|
|
||||||
<Loader active={loading}>Loading images</Loader>
|
|
||||||
{albums}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</Query>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Albums
|
|
|
@ -1,13 +1,37 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import Albums from './Albums'
|
import AlbumGallery from './AlbumGallery'
|
||||||
import Layout from '../../Layout'
|
import Layout from '../../Layout'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { Query } from 'react-apollo'
|
||||||
|
|
||||||
|
const getAlbumsQuery = gql`
|
||||||
|
query getMyAlbums {
|
||||||
|
myAlbums {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
photos {
|
||||||
|
thumbnail {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
class AlbumsPage extends Component {
|
class AlbumsPage extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<h1>Albums</h1>
|
<h1>Albums</h1>
|
||||||
<Albums />
|
<Query query={getAlbumsQuery}>
|
||||||
|
{({ loading, error, data }) => (
|
||||||
|
<AlbumGallery
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
albums={data && data.myAlbums}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Query>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ const photoQuery = gql`
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
thumbnail {
|
thumbnail {
|
||||||
path
|
url
|
||||||
width
|
width
|
||||||
height
|
height
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ const PhotoGallery = ({
|
||||||
onSelectImage && onSelectImage(index)
|
onSelectImage && onSelectImage(index)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Photo src={photo.thumbnail.path} />
|
<Photo src={photo.thumbnail.url} />
|
||||||
<PhotoOverlay active={active} />
|
<PhotoOverlay active={active} />
|
||||||
</PhotoContainer>
|
</PhotoContainer>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue