Working on animations
This commit is contained in:
parent
f0bf1c2dc4
commit
705aaeba0e
|
@ -10,25 +10,40 @@ function injectAt(query, index, injection) {
|
||||||
return query.substr(0, index) + injection + query.substr(index)
|
return query.substr(0, index) + injection + query.substr(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Query = {
|
const myAlbumQuery = function(args, ctx, info) {
|
||||||
myAlbums: async function(root, args, ctx, info) {
|
const query = cypherQuery(args, ctx, info)
|
||||||
const query = cypherQuery(args, ctx, info)
|
|
||||||
|
|
||||||
const whereSplit = query[0].indexOf('RETURN')
|
const whereSplit = query[0].indexOf('RETURN')
|
||||||
|
|
||||||
|
query[0] = injectAt(
|
||||||
|
query[0],
|
||||||
|
whereSplit,
|
||||||
|
`MATCH (u:User { id: {userid} }) WHERE (u)-[:OWNS]->(album) `
|
||||||
|
)
|
||||||
|
query[1].userid = ctx.user.id
|
||||||
|
|
||||||
|
const addIDSplitIndex = query[0].indexOf('album_photos {')
|
||||||
|
|
||||||
|
if (addIDSplitIndex != -1) {
|
||||||
|
const addIDSplit = addIDSplitIndex + 14
|
||||||
|
|
||||||
|
console.log('ID SPLIT', query[0])
|
||||||
|
|
||||||
query[0] = injectAt(
|
query[0] = injectAt(
|
||||||
query[0],
|
query[0],
|
||||||
whereSplit,
|
addIDSplit,
|
||||||
`MATCH (u:User { id: {userid} }) WHERE (u)-[:OWNS]->(album) `
|
query[0].indexOf('album_photos {}') == -1 ? ` .id, ` : ` .id `
|
||||||
)
|
)
|
||||||
query[1].userid = ctx.user.id
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
const Query = {
|
||||||
|
async myAlbums(root, args, ctx, info) {
|
||||||
|
let query = myAlbumQuery(args, ctx, info)
|
||||||
console.log(query)
|
console.log(query)
|
||||||
|
|
||||||
const addIDSplit = query[0].indexOf('album_photos {') + 14
|
|
||||||
|
|
||||||
console.log('ID SPLIT', query[0].substr(0, addIDSplit))
|
|
||||||
query[0] = injectAt(query[0], addIDSplit, `.id,`)
|
|
||||||
|
|
||||||
const session = ctx.driver.session()
|
const session = ctx.driver.session()
|
||||||
|
|
||||||
const result = await session.run(...query)
|
const result = await session.run(...query)
|
||||||
|
@ -37,16 +52,32 @@ const Query = {
|
||||||
|
|
||||||
return result.records.map(record => record.get('album'))
|
return result.records.map(record => record.get('album'))
|
||||||
},
|
},
|
||||||
|
async album(root, args, ctx, info) {
|
||||||
|
const session = ctx.driver.session()
|
||||||
|
|
||||||
|
let query = myAlbumQuery(args, ctx, info)
|
||||||
|
|
||||||
|
const whereSplit = query[0].indexOf('RETURN')
|
||||||
|
|
||||||
|
query[0] = injectAt(query[0], whereSplit, ` AND album.id = {id} `)
|
||||||
|
query[1].id = args.id
|
||||||
|
console.log(query)
|
||||||
|
|
||||||
|
const result = await session.run(...query)
|
||||||
|
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
if (result.records.length == 0) {
|
||||||
|
throw new Error('Album was not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.records[0].get('album')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const Photo = {
|
function photoResolver(image) {
|
||||||
async original(root, args, ctx, info) {
|
return async (root, args, ctx, info) => {
|
||||||
const imgPath = path.resolve(
|
const imgPath = path.resolve(config.cachePath, 'images', root.id, image)
|
||||||
config.cachePath,
|
|
||||||
'images',
|
|
||||||
root.id,
|
|
||||||
'original.jpg'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!(await fs.exists(imgPath))) {
|
if (!(await fs.exists(imgPath))) {
|
||||||
await ctx.scanner.processImage(root.id)
|
await ctx.scanner.processImage(root.id)
|
||||||
|
@ -54,18 +85,16 @@ const Photo = {
|
||||||
|
|
||||||
const { width, height } = await imageSize(imgPath)
|
const { width, height } = await imageSize(imgPath)
|
||||||
return {
|
return {
|
||||||
path: `${ctx.endpoint}/images/${root.id}/original.jpg`,
|
path: `${ctx.endpoint}/images/${root.id}/${image}`,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
async thumbnail(root, args, ctx, info) {
|
}
|
||||||
return {
|
|
||||||
path: `${ctx.endpoint}/images/${root.id}/thumbnail.jpg`,
|
const Photo = {
|
||||||
width: 120,
|
original: photoResolver('original.jpg'),
|
||||||
height: 240,
|
thumbnail: photoResolver('thumbnail.jpg'),
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -74,4 +74,5 @@ type Query {
|
||||||
siteInfo: SiteInfo
|
siteInfo: SiteInfo
|
||||||
|
|
||||||
myAlbums: [Album] @isAuthenticated
|
myAlbums: [Album] @isAuthenticated
|
||||||
|
album(id: ID): Album @isAuthenticated
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"semantic-ui-react": "^0.87.2",
|
"semantic-ui-react": "^0.87.2",
|
||||||
|
"semantic-ui": "^2.4.2",
|
||||||
"styled-components": "^4.3.2"
|
"styled-components": "^4.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.0",
|
"@babel/core": "^7.5.0",
|
||||||
"husky": "^3.0.0",
|
"husky": "^3.0.0",
|
||||||
|
"isarray": "^2.0.5",
|
||||||
"lint-staged": "^9.1.0"
|
"lint-staged": "^9.1.0"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
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 { Dimmer, Loader } from 'semantic-ui-react'
|
||||||
|
|
||||||
|
const getAlbumsQuery = gql`
|
||||||
|
query getMyAlbums {
|
||||||
|
myAlbums {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
photos {
|
||||||
|
thumbnail {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div style={{ position: 'relative', minHeight: '500px' }}>
|
||||||
|
<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].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>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Albums
|
|
@ -0,0 +1,230 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { Query } from 'react-apollo'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { useSpring, animated } from 'react-spring'
|
||||||
|
|
||||||
|
const albumQuery = gql`
|
||||||
|
query albumQuery($id: ID) {
|
||||||
|
album(id: $id) {
|
||||||
|
title
|
||||||
|
photos {
|
||||||
|
id
|
||||||
|
thumbnail {
|
||||||
|
path
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Gallery = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Photo = styled.img`
|
||||||
|
margin: 4px;
|
||||||
|
background-color: #eee;
|
||||||
|
display: inline-block;
|
||||||
|
height: 200px;
|
||||||
|
flex-grow: 1;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
&:nth-last-child(6) ~ img {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
${props =>
|
||||||
|
props.active &&
|
||||||
|
`
|
||||||
|
will-change: transform;
|
||||||
|
position: relative;
|
||||||
|
z-index: 999;
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
|
||||||
|
const Dimmer = ({ onClick, active }) => {
|
||||||
|
const [props, set, stop] = useSpring(() => ({ opacity: 0 }))
|
||||||
|
|
||||||
|
set({
|
||||||
|
opacity: active ? 1 : 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const AnimatedDimmer = styled(animated.div)`
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: black;
|
||||||
|
margin: 0;
|
||||||
|
z-index: 10;
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatedDimmer
|
||||||
|
onClick={onClick}
|
||||||
|
style={{
|
||||||
|
...props,
|
||||||
|
pointerEvents: active ? 'auto' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumPage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeImage: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setActiveImage = this.setActiveImage.bind(this)
|
||||||
|
this.animateImage = this.animateImage.bind(this)
|
||||||
|
|
||||||
|
this.photoAmount = 1
|
||||||
|
this.previousActive = false
|
||||||
|
|
||||||
|
this.scrollEvent = () => {
|
||||||
|
if (this.state.activeImage != -1) {
|
||||||
|
this.dismissPopup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keyUpEvent = e => {
|
||||||
|
const activeImage = this.state.activeImage
|
||||||
|
if (activeImage != -1) {
|
||||||
|
if (e.key == 'ArrowRight') {
|
||||||
|
this.setActiveImage((activeImage + 1) % this.photoAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key == 'ArrowLeft') {
|
||||||
|
if (activeImage <= 0) {
|
||||||
|
this.setActiveImage(this.photoAmount - 1)
|
||||||
|
} else {
|
||||||
|
this.setActiveImage(activeImage - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('scroll', this.scrollEvent)
|
||||||
|
document.addEventListener('keyup', this.keyUpEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('scroll', this.scrollEvent)
|
||||||
|
document.removeEventListener('keyup', this.keyUpEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveImage(index) {
|
||||||
|
this.previousActive = this.state.activeImage != -1
|
||||||
|
this.setState({
|
||||||
|
activeImage: index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissPopup() {
|
||||||
|
this.previousActive = true
|
||||||
|
this.setState({
|
||||||
|
activeImage: -1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
animateImage(target) {
|
||||||
|
const margin = 32
|
||||||
|
|
||||||
|
const windowWidth = window.innerWidth
|
||||||
|
const windowHeight = window.innerHeight
|
||||||
|
|
||||||
|
const viewportWidth = windowWidth - margin * 2
|
||||||
|
const viewportHeight = windowHeight - margin * 2
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } = target
|
||||||
|
const { width, height, top, left } = target.getBoundingClientRect()
|
||||||
|
|
||||||
|
const scaleX = Math.min(naturalWidth, viewportWidth) / width
|
||||||
|
const scaleY = Math.min(naturalHeight, viewportHeight) / height
|
||||||
|
const scale = Math.min(scaleX, scaleY)
|
||||||
|
|
||||||
|
const translateX = (-left + (windowWidth - width) / 2) / scale
|
||||||
|
const translateY = (-top + (windowHeight - height) / 2) / scale
|
||||||
|
|
||||||
|
if (!this.previousActive) target.style.transition = `transform 0.4s`
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
target.style.transform = `scale(${scale})
|
||||||
|
translate3d(${translateX}px, ${translateY}px, 0)`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const albumId = this.props.match.params.id
|
||||||
|
|
||||||
|
// let dimmer = this.state.activeImage != -1 && (
|
||||||
|
// <Dimmer onClick={() => this.setActiveImage(-1)} />
|
||||||
|
// )
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Dimmer
|
||||||
|
onClick={() => this.setActiveImage(-1)}
|
||||||
|
active={this.state.activeImage != -1}
|
||||||
|
/>
|
||||||
|
<Query query={albumQuery} variables={{ id: albumId }}>
|
||||||
|
{({ loading, error, data }) => {
|
||||||
|
if (error) return <div>Error</div>
|
||||||
|
if (loading) return <div>Loading</div>
|
||||||
|
|
||||||
|
this.photoAmount = data.album.photos.length
|
||||||
|
|
||||||
|
const { activeImage } = this.state
|
||||||
|
|
||||||
|
const photos = data.album.photos.map((photo, index) => {
|
||||||
|
const active = index == activeImage
|
||||||
|
let ref = null
|
||||||
|
let style = {}
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
ref = target => {
|
||||||
|
if (target == null) return
|
||||||
|
this.animateImage(target)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
style.transform = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Photo
|
||||||
|
ref={ref}
|
||||||
|
active={active}
|
||||||
|
key={photo.id}
|
||||||
|
onClick={() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (activeImage != index) {
|
||||||
|
this.setActiveImage(index)
|
||||||
|
} else {
|
||||||
|
this.dismissPopup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
style={style}
|
||||||
|
src={photo.thumbnail.path}
|
||||||
|
></Photo>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return <Gallery>{photos}</Gallery>
|
||||||
|
}}
|
||||||
|
</Query>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumPage
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import Albums from '../Albums'
|
||||||
|
|
||||||
class HomePage extends Component {
|
class HomePage extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
|
<Albums />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,13 @@ const authorizeMutation = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
function setCookie(cname, cvalue, exdays) {
|
||||||
|
var d = new Date()
|
||||||
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
|
||||||
|
var expires = 'expires=' + d.toUTCString()
|
||||||
|
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'
|
||||||
|
}
|
||||||
|
|
||||||
class LoginPage extends Component {
|
class LoginPage extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
@ -57,6 +64,7 @@ class LoginPage extends Component {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
localStorage.setItem('token', token)
|
localStorage.setItem('token', token)
|
||||||
|
setCookie('token', token, 360)
|
||||||
window.location = '/'
|
window.location = '/'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -3,13 +3,15 @@ import { Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
import LoginPage from './Pages/LoginPage'
|
import LoginPage from './Pages/LoginPage'
|
||||||
import HomePage from './Pages/HomePage'
|
import HomePage from './Pages/HomePage'
|
||||||
|
import AlbumPage from './Pages/AlbumPage'
|
||||||
|
|
||||||
class Routes extends Component {
|
class Routes extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/login" component={LoginPage} />
|
<Route path="/login" component={LoginPage} />
|
||||||
<Route path="/" component={HomePage} />
|
<Route exact path="/" component={HomePage} />
|
||||||
|
<Route path="/album/:id" component={AlbumPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3489,6 +3489,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||||
|
|
||||||
|
isarray@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||||
|
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||||
|
|
||||||
isexe@^2.0.0:
|
isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
|
Loading…
Reference in New Issue