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)
|
||||
}
|
||||
|
||||
const Query = {
|
||||
myAlbums: async function(root, args, ctx, info) {
|
||||
const query = cypherQuery(args, ctx, info)
|
||||
const myAlbumQuery = function(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],
|
||||
whereSplit,
|
||||
`MATCH (u:User { id: {userid} }) WHERE (u)-[:OWNS]->(album) `
|
||||
addIDSplit,
|
||||
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)
|
||||
|
||||
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 result = await session.run(...query)
|
||||
|
@ -37,16 +52,32 @@ const Query = {
|
|||
|
||||
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 = {
|
||||
async original(root, args, ctx, info) {
|
||||
const imgPath = path.resolve(
|
||||
config.cachePath,
|
||||
'images',
|
||||
root.id,
|
||||
'original.jpg'
|
||||
)
|
||||
function photoResolver(image) {
|
||||
return async (root, args, ctx, info) => {
|
||||
const imgPath = path.resolve(config.cachePath, 'images', root.id, image)
|
||||
|
||||
if (!(await fs.exists(imgPath))) {
|
||||
await ctx.scanner.processImage(root.id)
|
||||
|
@ -54,18 +85,16 @@ const Photo = {
|
|||
|
||||
const { width, height } = await imageSize(imgPath)
|
||||
return {
|
||||
path: `${ctx.endpoint}/images/${root.id}/original.jpg`,
|
||||
path: `${ctx.endpoint}/images/${root.id}/${image}`,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
},
|
||||
async thumbnail(root, args, ctx, info) {
|
||||
return {
|
||||
path: `${ctx.endpoint}/images/${root.id}/thumbnail.jpg`,
|
||||
width: 120,
|
||||
height: 240,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const Photo = {
|
||||
original: photoResolver('original.jpg'),
|
||||
thumbnail: photoResolver('thumbnail.jpg'),
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -74,4 +74,5 @@ type Query {
|
|||
siteInfo: SiteInfo
|
||||
|
||||
myAlbums: [Album] @isAuthenticated
|
||||
album(id: ID): Album @isAuthenticated
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"react-dom": "^16.8.6",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"semantic-ui-react": "^0.87.2",
|
||||
"semantic-ui": "^2.4.2",
|
||||
"styled-components": "^4.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -33,6 +34,7 @@
|
|||
"devDependencies": {
|
||||
"@babel/core": "^7.5.0",
|
||||
"husky": "^3.0.0",
|
||||
"isarray": "^2.0.5",
|
||||
"lint-staged": "^9.1.0"
|
||||
},
|
||||
"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 Albums from '../Albums'
|
||||
|
||||
class HomePage extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
<Albums />
|
||||
</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 {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
@ -57,6 +64,7 @@ class LoginPage extends Component {
|
|||
|
||||
if (success) {
|
||||
localStorage.setItem('token', token)
|
||||
setCookie('token', token, 360)
|
||||
window.location = '/'
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -3,13 +3,15 @@ import { Route, Switch } from 'react-router-dom'
|
|||
|
||||
import LoginPage from './Pages/LoginPage'
|
||||
import HomePage from './Pages/HomePage'
|
||||
import AlbumPage from './Pages/AlbumPage'
|
||||
|
||||
class Routes extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/login" component={LoginPage} />
|
||||
<Route path="/" component={HomePage} />
|
||||
<Route exact path="/" component={HomePage} />
|
||||
<Route path="/album/:id" component={AlbumPage} />
|
||||
</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"
|
||||
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:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
|
|
Loading…
Reference in New Issue