mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 21:09:05 +00:00
Work on layout
This commit is contained in:
@@ -1,22 +1,23 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { Redirect } from 'react-router-dom'
|
import { createGlobalStyle } from 'styled-components'
|
||||||
|
|
||||||
|
const GlobalStyle = createGlobalStyle`
|
||||||
|
#root, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
import Routes from './Routes'
|
import Routes from './Routes'
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render() {
|
render() {
|
||||||
const token = localStorage.getItem('token')
|
return (
|
||||||
|
<>
|
||||||
if (!token) {
|
<GlobalStyle />
|
||||||
return (
|
<Routes />
|
||||||
<>
|
</>
|
||||||
<Redirect to="/login" />
|
)
|
||||||
<Routes />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Routes />
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
46
ui/src/Layout.js
Normal file
46
ui/src/Layout.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
/* display: grid;
|
||||||
|
grid-template-columns: 80px 1fr 500px; */
|
||||||
|
`
|
||||||
|
|
||||||
|
const LeftSidebar = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
width: 80px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #eee;
|
||||||
|
`
|
||||||
|
|
||||||
|
const RightSidebar = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
width: 500px;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: #eee;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
margin-left: 80px;
|
||||||
|
margin-right: 500px;
|
||||||
|
padding: 0 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
class Layout extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<LeftSidebar>Left</LeftSidebar>
|
||||||
|
<Content>{this.props.children}</Content>
|
||||||
|
<RightSidebar>Right</RightSidebar>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
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
|
|
||||||
106
ui/src/Pages/AlbumPage/AlbumPage.js
Normal file
106
ui/src/Pages/AlbumPage/AlbumPage.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { Query } from 'react-apollo'
|
||||||
|
import { Gallery, Photo, PhotoFiller } from './styledElements'
|
||||||
|
import Layout from '../../Layout'
|
||||||
|
|
||||||
|
const albumQuery = gql`
|
||||||
|
query albumQuery($id: ID) {
|
||||||
|
album(id: $id) {
|
||||||
|
title
|
||||||
|
photos {
|
||||||
|
id
|
||||||
|
thumbnail {
|
||||||
|
path
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
class AlbumPage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeImage: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setActiveImage = this.setActiveImage.bind(this)
|
||||||
|
|
||||||
|
this.photoAmount = 1
|
||||||
|
this.previousActive = false
|
||||||
|
|
||||||
|
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('keyup', this.keyUpEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('keyup', this.keyUpEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveImage(index) {
|
||||||
|
this.previousActive = this.state.activeImage != -1
|
||||||
|
this.setState({
|
||||||
|
activeImage: index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissPopup() {
|
||||||
|
this.setState({
|
||||||
|
activeImage: -1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const albumId = this.props.match.params.id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<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) => {
|
||||||
|
return <Photo key={photo.id} src={photo.thumbnail.path}></Photo>
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Gallery>
|
||||||
|
{photos}
|
||||||
|
<PhotoFiller />
|
||||||
|
</Gallery>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Query>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumPage
|
||||||
31
ui/src/Pages/AlbumPage/Dimmer.js
Normal file
31
ui/src/Pages/AlbumPage/Dimmer.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useSpring, animated } from 'react-spring'
|
||||||
|
|
||||||
|
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',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dimmer
|
||||||
28
ui/src/Pages/AlbumPage/styledElements.js
Normal file
28
ui/src/Pages/AlbumPage/styledElements.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const Gallery = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const Photo = styled.img`
|
||||||
|
margin: 4px;
|
||||||
|
background-color: #eee;
|
||||||
|
height: 200px;
|
||||||
|
flex-grow: 1;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
${props =>
|
||||||
|
props.active &&
|
||||||
|
`
|
||||||
|
will-change: transform;
|
||||||
|
position: relative;
|
||||||
|
z-index: 999;
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PhotoFiller = styled.div`
|
||||||
|
height: 200px;
|
||||||
|
flex-grow: 999999;
|
||||||
|
`
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import Albums from '../Albums'
|
import Albums from './Albums'
|
||||||
|
import Layout from '../../Layout'
|
||||||
|
|
||||||
class HomePage extends Component {
|
class HomePage extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Layout>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<Albums />
|
<Albums />
|
||||||
</div>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,21 @@ import React, { Component } from 'react'
|
|||||||
import { Route, Switch } from 'react-router-dom'
|
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/HomePage'
|
||||||
import AlbumPage from './Pages/AlbumPage'
|
import AlbumPage from './Pages/AlbumPage/AlbumPage'
|
||||||
|
|
||||||
class Routes extends Component {
|
class Routes extends Component {
|
||||||
render() {
|
render() {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
|
||||||
|
let unauthorizedRedirect = null
|
||||||
|
if (!token) {
|
||||||
|
unauthorizedRedirect = <Redirect to="/login" />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
{unauthorizedRedirect}
|
||||||
<Route path="/login" component={LoginPage} />
|
<Route path="/login" component={LoginPage} />
|
||||||
<Route exact path="/" component={HomePage} />
|
<Route exact path="/" component={HomePage} />
|
||||||
<Route path="/album/:id" component={AlbumPage} />
|
<Route path="/album/:id" component={AlbumPage} />
|
||||||
|
|||||||
Reference in New Issue
Block a user