1
Fork 0

Working on animations

This commit is contained in:
viktorstrate 2019-07-18 17:40:54 +02:00
parent f0bf1c2dc4
commit 705aaeba0e
9 changed files with 393 additions and 30 deletions

View File

@ -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 {

View File

@ -74,4 +74,5 @@ type Query {
siteInfo: SiteInfo
myAlbums: [Album] @isAuthenticated
album(id: ID): Album @isAuthenticated
}

View File

@ -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": {

84
ui/src/Albums.js Normal file
View File

@ -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

230
ui/src/Pages/AlbumPage.js Normal file
View File

@ -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

View File

@ -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>
)
}

View File

@ -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 = '/'
}
}}

View File

@ -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>
)
}

View File

@ -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"