From 5e41e4d34dc3ba7ee845e0205025d91a5f203c2c Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Mon, 29 Jul 2019 18:06:53 +0200
Subject: [PATCH] Add next/previous keybindings in present view

---
 ui/package.json                               |  1 +
 ui/src/Pages/AlbumPage/AlbumPage.js           | 73 +++++++++++++++--
 ui/src/Pages/AlbumPage/Dimmer.js              | 31 -------
 ui/src/Pages/PhotosPage/PhotosPage.js         | 81 +++++++++++++++----
 ui/src/components/photoGallery/Photo.js       | 18 ++++-
 .../components/photoGallery/PhotoGallery.js   | 75 ++++++++++-------
 ui/src/components/photoGallery/PresentView.js |  4 +-
 7 files changed, 193 insertions(+), 90 deletions(-)
 delete mode 100644 ui/src/Pages/AlbumPage/Dimmer.js

diff --git a/ui/package.json b/ui/package.json
index 2810525..dc85d3b 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -17,6 +17,7 @@
     "graphql-tag": "^2.10.1",
     "parcel-bundler": "^1.12.3",
     "prettier": "^1.18.2",
+    "prop-types": "^15.7.2",
     "react": "^0.14.0 || ^15.0.0 || ^16.0.0",
     "react-apollo": "^2.5.5",
     "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0",
diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js
index 08d44bf..7f85fb1 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.js
+++ b/ui/src/Pages/AlbumPage/AlbumPage.js
@@ -3,7 +3,9 @@ import gql from 'graphql-tag'
 import { Query } from 'react-apollo'
 import Layout from '../../Layout'
 import PhotoSidebar from '../../components/sidebar/PhotoSidebar'
-import PhotoGallery from '../../components/photoGallery/PhotoGallery'
+import PhotoGallery, {
+  presentIndexFromHash,
+} from '../../components/photoGallery/PhotoGallery'
 import AlbumGallery from '../AllAlbumsPage/AlbumGallery'
 
 const albumQuery = gql`
@@ -37,18 +39,63 @@ class AlbumPage extends Component {
 
     this.state = {
       activeImage: -1,
-      activeImageId: null,
+      presenting: false,
+    }
+
+    const presentIndex = presentIndexFromHash(location.hash)
+    if (presentIndex) {
+      this.state.activeImage = presentIndex
+      this.state.presenting = true
     }
 
     this.setActiveImage = this.setActiveImage.bind(this)
+    this.nextImage = this.nextImage.bind(this)
+    this.previousImage = this.previousImage.bind(this)
+    this.setPresenting = this.setPresenting.bind(this)
 
-    this.photoAmount = 1
+    this.photos = []
   }
 
-  setActiveImage(index, id) {
+  setActiveImage(index) {
     this.setState({
       activeImage: index,
-      activeImageId: id,
+    })
+  }
+
+  nextImage() {
+    this.setState({
+      activeImage: (this.state.activeImage + 1) % this.photos.length,
+    })
+  }
+
+  previousImage() {
+    if (this.state.activeImage <= 0) {
+      this.setState({
+        activeImage: this.photos.length - 1,
+      })
+    } else {
+      this.setState({
+        activeImage: this.state.activeImage - 1,
+      })
+    }
+  }
+
+  componentDidUpdate(prevProps, prevState) {
+    if (this.state.presenting) {
+      history.replaceState(
+        null,
+        null,
+        document.location.pathname + '#' + `present=${this.state.activeImage}`
+      )
+    } else if (presentIndexFromHash(location.hash)) {
+      history.replaceState(null, null, document.location.pathname.split('#')[0])
+    }
+  }
+
+  setPresenting(presenting) {
+    console.log('Presenting', presenting, this)
+    this.setState({
+      presenting,
     })
   }
 
@@ -64,7 +111,7 @@ class AlbumPage extends Component {
             let subAlbumElement = null
 
             if (data.album) {
-              this.photoAmount = data.album.photos.length
+              this.photos = data.album.photos
 
               if (data.album.subAlbums.length > 0) {
                 subAlbumElement = (
@@ -88,11 +135,21 @@ class AlbumPage extends Component {
                   loading={loading}
                   photos={data.album && data.album.photos}
                   activeIndex={this.state.activeImage}
+                  presenting={this.state.presenting}
                   onSelectImage={index => {
-                    this.setActiveImage(index, data.album.photos[index].id)
+                    this.setActiveImage(index)
                   }}
+                  setPresenting={this.setPresenting}
+                  nextImage={this.nextImage}
+                  previousImage={this.previousImage}
+                />
+                <PhotoSidebar
+                  imageId={
+                    this.photos.length > 0 && this.state.activeImage != -1
+                      ? this.photos[this.state.activeImage].id
+                      : null
+                  }
                 />
-                <PhotoSidebar imageId={this.state.activeImageId} />
               </div>
             )
           }}
diff --git a/ui/src/Pages/AlbumPage/Dimmer.js b/ui/src/Pages/AlbumPage/Dimmer.js
deleted file mode 100644
index 96f863a..0000000
--- a/ui/src/Pages/AlbumPage/Dimmer.js
+++ /dev/null
@@ -1,31 +0,0 @@
-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
diff --git a/ui/src/Pages/PhotosPage/PhotosPage.js b/ui/src/Pages/PhotosPage/PhotosPage.js
index 8fc117c..e87496c 100644
--- a/ui/src/Pages/PhotosPage/PhotosPage.js
+++ b/ui/src/Pages/PhotosPage/PhotosPage.js
@@ -10,7 +10,7 @@ const photoQuery = gql`
     myAlbums(orderBy: title_asc) {
       title
       id
-      photos(orderBy: title_desc) {
+      photos(orderBy: title_desc, first: 12) {
         id
         title
         thumbnail {
@@ -28,16 +28,54 @@ class PhotosPage extends Component {
     super(props)
 
     this.state = {
-      activeAlbum: null,
-      activeIndex: null,
+      activeAlbumIndex: -1,
+      activePhotoIndex: -1,
+      presenting: false,
+    }
+
+    this.setPresenting = this.setPresenting.bind(this)
+    this.nextImage = this.nextImage.bind(this)
+    this.previousImage = this.previousImage.bind(this)
+
+    this.albums = []
+  }
+
+  setActiveImage(album, photo) {
+    this.setState({
+      activePhotoIndex: photo,
+      activeAlbumIndex: album,
+    })
+  }
+
+  setPresenting(presenting, index) {
+    if (presenting) {
+      this.setState({
+        presenting: index,
+      })
+    } else {
+      this.setState({
+        presenting: false,
+      })
     }
   }
 
-  setActiveImage(album, index) {
-    this.setState({
-      activeIndex: index,
-      activeAlbum: album,
-    })
+  nextImage() {
+    const albumImageCount = this.albums[this.state.activeAlbumIndex].photos
+      .length
+
+    if (this.state.activePhotoIndex + 1 < albumImageCount) {
+      this.setState({
+        activePhotoIndex: this.state.activePhotoIndex + 1,
+      })
+    }
+  }
+
+  previousImage() {
+    if (this.state.activePhotoIndex > 0) {
+      this.setState({
+        activePhotoIndex: this.state.activePhotoIndex - 1,
+      })
+    }
   }
 
   render() {
@@ -49,31 +87,40 @@ class PhotosPage extends Component {
 
             let galleryGroups = []
 
+            this.albums = data.myAlbums
+
             if (data.myAlbums) {
-              galleryGroups = data.myAlbums.map(album => (
+              galleryGroups = data.myAlbums.map((album, index) => (
                 <div key={album.id}>
                   <h1>{album.title}</h1>
                   <PhotoGallery
-                    onSelectImage={index => {
-                      this.setActiveImage(album.id, index)
+                    onSelectImage={photoIndex => {
+                      this.setActiveImage(index, photoIndex)
                     }}
                     activeIndex={
-                      this.state.activeAlbum == album.id
-                        ? this.state.activeIndex
+                      this.state.activeAlbumIndex == index
+                        ? this.state.activePhotoIndex
                         : -1
                     }
+                    presenting={this.state.presenting === index}
+                    setPresenting={presenting =>
+                      this.setPresenting(presenting, index)
+                    }
                     loading={loading}
                     photos={album.photos}
+                    nextImage={this.nextImage}
+                    previousImage={this.previousImage}
                   />
                 </div>
               ))
             }
 
             let activeImage = null
-            if (this.state.activeAlbum) {
-              activeImage = data.myAlbums.find(
-                album => album.id == this.state.activeAlbum
-              ).photos[this.state.activeIndex].id
+            if (this.state.activeAlbumIndex != -1) {
+              activeImage =
+                data.myAlbums[this.state.activeAlbumIndex].photos[
+                  this.state.activePhotoIndex
+                ].id
             }
 
             return (
diff --git a/ui/src/components/photoGallery/Photo.js b/ui/src/components/photoGallery/Photo.js
index a0f7c21..5c9e53f 100644
--- a/ui/src/components/photoGallery/Photo.js
+++ b/ui/src/components/photoGallery/Photo.js
@@ -58,6 +58,11 @@ const PhotoOverlay = styled.div`
     props.active &&
     `
       border: 4px solid rgba(65, 131, 196, 0.6);
+
+      & ${HoverIcon} {
+        top: -4px !important;
+        left: -4px !important;
+      }
     `}
 `
 
@@ -67,6 +72,7 @@ const HoverIcon = styled(Icon)`
   color: white !important;
   text-shadow: 0 0 4px black;
   opacity: 0 !important;
+  position: relative;
 
   border-radius: 50%;
   width: 34px !important;
@@ -84,7 +90,14 @@ const HoverIcon = styled(Icon)`
   transition: opacity 100ms, background-color 100ms;
 `
 
-export const Photo = ({ photo, onSelectImage, minWidth, index, active }) => (
+export const Photo = ({
+  photo,
+  onSelectImage,
+  minWidth,
+  index,
+  active,
+  setPresenting,
+}) => (
   <PhotoContainer
     key={photo.id}
     style={{
@@ -100,7 +113,8 @@ export const Photo = ({ photo, onSelectImage, minWidth, index, active }) => (
       <HoverIcon
         name="expand"
         onClick={() => {
-          window.location.hash = `present=${photo.id}`
+          // window.location.hash = `present=${photo.id}`
+          setPresenting(true)
         }}
       />
       <HoverIcon name="heart outline" />
diff --git a/ui/src/components/photoGallery/PhotoGallery.js b/ui/src/components/photoGallery/PhotoGallery.js
index 96b1cc6..c602dde 100644
--- a/ui/src/components/photoGallery/PhotoGallery.js
+++ b/ui/src/components/photoGallery/PhotoGallery.js
@@ -3,6 +3,7 @@ import styled from 'styled-components'
 import { Loader } from 'semantic-ui-react'
 import { Photo } from './Photo'
 import PresentView from './PresentView'
+import PropTypes from 'prop-types'
 
 const Gallery = styled.div`
   display: flex;
@@ -15,47 +16,47 @@ const PhotoFiller = styled.div`
   flex-grow: 999999;
 `
 
-const presentIdFromHash = hash => {
-  let match = hash.match(/present=([a-z0-9\-]+)/)
-  return match && match[1]
+export const presentIndexFromHash = hash => {
+  let match = hash.match(/present=(\d+)/)
+  return match && parseInt(match[1])
 }
 
 class PhotoGallery extends React.Component {
   constructor(props) {
     super(props)
 
-    // this.keyDownEvent = e => {
-    //   if (!this.props.onSelectImage) {
-    //     return
-    //   }
+    this.keyDownEvent = e => {
+      if (!this.props.onSelectImage || this.props.activeIndex == -1) {
+        return
+      }
 
-    //   const activeImage = this.state.activeImage
-    //   if (activeImage != -1) {
-    //     if (e.key == 'ArrowRight') {
-    //       this.setActiveImage((activeImage + 1) % this.photoAmount)
-    //     }
+      if (e.key == 'ArrowRight') {
+        this.props.nextImage && this.props.nextImage()
+      }
 
-    //     if (e.key == 'ArrowLeft') {
-    //       if (activeImage <= 0) {
-    //         this.setActiveImage(this.photoAmount - 1)
-    //       } else {
-    //         this.setActiveImage(activeImage - 1)
-    //       }
-    //     }
-    //   }
-    // }
+      if (e.key == 'ArrowLeft') {
+        this.props.nextImage && this.props.previousImage()
+      }
+
+      if (e.key == 'Escape') {
+        this.props.setPresenting(false)
+      }
+    }
   }
 
-  // componentDidMount() {
-  //   document.addEventListener('keydown', this.keyDownEvent)
-  // }
+  componentDidMount() {
+    document.addEventListener('keydown', this.keyDownEvent)
+  }
 
-  // componentWillUnmount() {
-  //   document.removeEventListener('keydown', this.keyDownEvent)
-  // }
+  componentWillUnmount() {
+    document.removeEventListener('keydown', this.keyDownEvent)
+  }
 
   render() {
     const { activeIndex = -1, photos, loading, onSelectImage } = this.props
+
+    const activeImage = photos && activeIndex != -1 && photos[activeIndex]
+
     let photoElements = null
     if (photos) {
       photoElements = photos.map((photo, index) => {
@@ -73,6 +74,7 @@ class PhotoGallery extends React.Component {
             key={photo.id}
             photo={photo}
             onSelectImage={onSelectImage}
+            setPresenting={this.props.setPresenting}
             minWidth={minWidth}
             index={index}
             active={active}
@@ -81,8 +83,6 @@ class PhotoGallery extends React.Component {
       })
     }
 
-    console.log(presentIdFromHash(location.hash))
-
     return (
       <div>
         <Gallery>
@@ -90,9 +90,24 @@ class PhotoGallery extends React.Component {
           {photoElements}
           <PhotoFiller />
         </Gallery>
-        <PresentView image={presentIdFromHash(location.hash)} />
+        <PresentView
+          presenting={this.props.presenting}
+          image={activeImage && activeImage.id}
+        />
       </div>
     )
   }
 }
+
+PhotoGallery.propTypes = {
+  loading: PropTypes.bool,
+  photos: PropTypes.array,
+  activeIndex: PropTypes.number,
+  presenting: PropTypes.bool,
+  onSelectImage: PropTypes.func,
+  setPresenting: PropTypes.func,
+  nextImage: PropTypes.func,
+  previousImage: PropTypes.func,
+}
+
 export default PhotoGallery
diff --git a/ui/src/components/photoGallery/PresentView.js b/ui/src/components/photoGallery/PresentView.js
index ef305d4..3f96a67 100644
--- a/ui/src/components/photoGallery/PresentView.js
+++ b/ui/src/components/photoGallery/PresentView.js
@@ -43,9 +43,9 @@ const PresentImage = styled.img`
 
 export default class PresentView extends React.Component {
   render() {
-    const { image } = this.props
+    const { image, presenting } = this.props
 
-    if (!image) {
+    if (!image || !presenting) {
       return null
     }
 
