diff --git a/api/src/resolvers/sharing.js b/api/src/resolvers/sharing.js index fb4b3e85..0cb97e89 100644 --- a/api/src/resolvers/sharing.js +++ b/api/src/resolvers/sharing.js @@ -1,3 +1,4 @@ +import { neo4jgraphql } from 'neo4j-graphql-js' import generateID from '../id-generator' import { replaceMatch } from './neo4j-helpers' @@ -94,6 +95,27 @@ const Mutation = { ...createResult.records[0].get('share').properties, } }, + async deleteShareToken(root, args, ctx, info) { + if (!ctx.user.admin) { + const session = ctx.driver.session() + const result = await session.run( + `MATCH (u:User { id: {userId} })-[:SHARE_TOKEN]->(token:ShareToken { token: {token} }) + RETURN token`, + { + userId: ctx.user.id, + token: args.token, + } + ) + + session.close() + + if (result.records.length == 0) { + throw new Error('User is not allowed to delete this share') + } + } + + return neo4jgraphql(root, args, ctx, info) + }, } const Query = { diff --git a/api/src/scanner/processImage.js b/api/src/scanner/processImage.js index 6c43f15d..01cc0c1e 100644 --- a/api/src/scanner/processImage.js +++ b/api/src/scanner/processImage.js @@ -143,8 +143,8 @@ export default async function processImage({ driver, markFinishedImage }, id) { // Resize image const thumbnailPath = path.resolve(imagePath, 'thumbnail.jpg') await sharp(originalPath) - .jpeg({ quality: 80 }) - .resize(1440, 1080, { fit: 'inside', withoutEnlargement: true }) + .jpeg({ quality: 70 }) + .resize(720, 480, { fit: 'inside', withoutEnlargement: true }) .toFile(thumbnailPath) try { diff --git a/api/src/schema.graphql b/api/src/schema.graphql index c3d3b452..dbed8c4c 100644 --- a/api/src/schema.graphql +++ b/api/src/schema.graphql @@ -117,6 +117,8 @@ type Mutation { sharePhoto(photoId: ID!, expire: Date, password: String): ShareToken @isAuthenticated + deleteShareToken(token: ID!): ShareToken @isAuthenticated + setAdmin(userId: ID!, admin: Boolean!): Result! @hasRole(roles: [admin]) @neo4j_ignore diff --git a/ui/src/Layout.js b/ui/src/Layout.js index f58bd6a2..571f9727 100644 --- a/ui/src/Layout.js +++ b/ui/src/Layout.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import styled from 'styled-components' import { NavLink } from 'react-router-dom' import { Icon } from 'semantic-ui-react' +import Sidebar from './components/sidebar/Sidebar' import { Query } from 'react-apollo' import gql from 'graphql-tag' @@ -20,7 +21,7 @@ const Container = styled.div` grid-template-columns: 80px 1fr 500px; */ ` -const LeftSidebar = styled.div` +const SideMenu = styled.div` height: 100%; width: 80px; position: fixed; @@ -87,7 +88,7 @@ class Layout extends Component { render() { return ( - + Photos @@ -110,8 +111,10 @@ class Layout extends Component { return null }} - - {this.props.children} + + + {this.props.children} +
Photoview
diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js index 043df226..bd308cd2 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.js +++ b/ui/src/Pages/AlbumPage/AlbumPage.js @@ -7,10 +7,13 @@ import PhotoGallery, { presentIndexFromHash, } from '../../components/photoGallery/PhotoGallery' import AlbumGallery from '../AllAlbumsPage/AlbumGallery' +import AlbumTitle from '../../components/AlbumTitle' +import { SidebarConsumer } from '../../components/sidebar/Sidebar' const albumQuery = gql` - query albumQuery($id: ID) { + query albumQuery($id: ID!) { album(id: $id) { + id title subAlbums(orderBy: title_asc) { id @@ -111,56 +114,56 @@ class AlbumPage extends Component { return ( - - {({ loading, error, data }) => { - if (error) return
Error
+ + {({ updateSidebar }) => ( + + {({ loading, error, data }) => { + if (error) return
Error
- let subAlbumElement = null + let subAlbumElement = null - if (data.album) { - this.photos = data.album.photos + if (data.album) { + this.photos = data.album.photos - if (data.album.subAlbums.length > 0) { - subAlbumElement = ( - - ) - } - } - - return ( -
-

{data.album && data.album.title}

- {subAlbumElement} - {data.album && data.album.subAlbums.length > 0 && ( -

Images

- )} - { - this.setActiveImage(index) - }} - setPresenting={this.setPresenting} - nextImage={this.nextImage} - previousImage={this.previousImage} - /> - 0 && this.state.activeImage != -1 - ? this.photos[this.state.activeImage].id - : null + if (data.album.subAlbums.length > 0) { + subAlbumElement = ( + + ) } - /> -
- ) - }} -
+ } + + return ( +
+ + {subAlbumElement} + {data.album && data.album.subAlbums.length > 0 && ( +

Images

+ )} + { + updateSidebar( + + ) + this.setActiveImage(index) + }} + setPresenting={this.setPresenting} + nextImage={this.nextImage} + previousImage={this.previousImage} + /> +
+ ) + }} +
+ )} +
) } diff --git a/ui/src/Pages/PhotosPage/PhotosPage.js b/ui/src/Pages/PhotosPage/PhotosPage.js index 16963736..357b7ca3 100644 --- a/ui/src/Pages/PhotosPage/PhotosPage.js +++ b/ui/src/Pages/PhotosPage/PhotosPage.js @@ -2,9 +2,10 @@ import React, { Component } from 'react' import Layout from '../../Layout' import gql from 'graphql-tag' import { Query } from 'react-apollo' -import { Link } from 'react-router-dom' import PhotoGallery from '../../components/photoGallery/PhotoGallery' import PhotoSidebar from '../../components/sidebar/PhotoSidebar' +import AlbumTitle from '../../components/AlbumTitle' +import { SidebarConsumer } from '../../components/sidebar/Sidebar' const photoQuery = gql` query allPhotosPage { @@ -85,60 +86,60 @@ class PhotosPage extends Component { render() { return ( - - {({ loading, error, data }) => { - if (error) return error + + {({ updateSidebar }) => ( + + {({ loading, error, data }) => { + if (error) return error - let galleryGroups = [] + let galleryGroups = [] - this.albums = data.myAlbums + this.albums = data.myAlbums - if (data.myAlbums) { - galleryGroups = data.myAlbums.map((album, index) => ( -
- -

- {album.title} -

- - { - this.setActiveImage(index, photoIndex) - }} - 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} - /> -
- )) - } + if (data.myAlbums) { + galleryGroups = data.myAlbums.map((album, index) => ( +
+ + { + updateSidebar( + + ) + this.setActiveImage(index, photoIndex) + }} + 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} + /> +
+ )) + } - let activeImage = null - if (this.state.activeAlbumIndex != -1) { - activeImage = - data.myAlbums[this.state.activeAlbumIndex].photos[ - this.state.activePhotoIndex - ].id - } + let activeImage = null + if (this.state.activeAlbumIndex != -1) { + activeImage = + data.myAlbums[this.state.activeAlbumIndex].photos[ + this.state.activePhotoIndex + ].id + } - return ( -
- {galleryGroups} - -
- ) - }} -
+ return
{galleryGroups}
+ }} +
+ )} +
) } diff --git a/ui/src/components/AlbumTitle.js b/ui/src/components/AlbumTitle.js new file mode 100644 index 00000000..45ebf254 --- /dev/null +++ b/ui/src/components/AlbumTitle.js @@ -0,0 +1,52 @@ +import React from 'react' +import { Link } from 'react-router-dom' +import styled from 'styled-components' +import { Icon } from 'semantic-ui-react' +import { SidebarConsumer } from './sidebar/Sidebar' + +const Header = styled.h1` + color: black; + margin: 24px 0 12px 0; +` + +const SettingsIcon = props => { + const StyledIcon = styled(Icon)` + margin-left: 8px !important; + display: inline-block; + color: #888; + cursor: pointer; + + &:hover { + color: #1e70bf; + } + ` + + return +} + +const AlbumTitle = ({ album, disableLink = false }) => { + if (!album) return null + + let title = {album.title} + + if (!disableLink) { + title = {title} + } + + return ( + + {({ updateSidebar }) => ( +
+ {title} + { + updateSidebar(
Title stuff {album.title}
) + }} + /> +
+ )} +
+ ) +} + +export default AlbumTitle diff --git a/ui/src/components/photoGallery/PresentView.js b/ui/src/components/photoGallery/PresentView.js index 8b2af036..a092ceea 100644 --- a/ui/src/components/photoGallery/PresentView.js +++ b/ui/src/components/photoGallery/PresentView.js @@ -23,8 +23,9 @@ const PreventScroll = createGlobalStyle` ` const imageQuery = gql` - query presentImage($id: ID) { + query presentImage($id: ID!) { photo(id: $id) { + id title original { width @@ -57,23 +58,30 @@ const PresentView = ({ {({ loading, error, data }) => { - if (error) return error + if (error) { + alert(error) + return
{error.message}
+ } let original = null if (!loading) { const { photo } = data original = ( { + // e.target.style.display = 'initial' + imageLoadedCallback && imageLoadedCallback() + }} /> ) } return (
- {original} +
) }} diff --git a/ui/src/components/sidebar/PhotoSidebar.js b/ui/src/components/sidebar/PhotoSidebar.js index 59a0c4e5..1061874e 100644 --- a/ui/src/components/sidebar/PhotoSidebar.js +++ b/ui/src/components/sidebar/PhotoSidebar.js @@ -35,17 +35,6 @@ const photoQuery = gql` } ` -const RightSidebar = styled.div` - height: 100%; - width: 500px; - position: fixed; - right: 0; - top: 60px; - background-color: white; - padding: 12px; - border-left: 1px solid #eee; -` - const PreviewImage = styled(ProtectedImage)` width: 100%; height: 333px; @@ -71,16 +60,16 @@ const exifNameLookup = { flash: 'Flash', } -class AlbumSidebar extends Component { +class PhotoSidebar extends Component { render() { const { imageId } = this.props if (!imageId) { - return + return null } return ( - +
{({ loading, error, data }) => { if (error) return error @@ -129,9 +118,9 @@ class AlbumSidebar extends Component { ) }} - +
) } } -export default AlbumSidebar +export default PhotoSidebar diff --git a/ui/src/components/sidebar/Sharing.js b/ui/src/components/sidebar/Sharing.js index 43acede4..f90757bf 100644 --- a/ui/src/components/sidebar/Sharing.js +++ b/ui/src/components/sidebar/Sharing.js @@ -1,5 +1,5 @@ import React from 'react' -import { Query } from 'react-apollo' +import { Query, Mutation } from 'react-apollo' import gql from 'graphql-tag' import { Table, Button, Icon, Dropdown } from 'semantic-ui-react' @@ -11,6 +11,26 @@ const shareQuery = gql` } ` +const addShareMutation = gql` + mutation sidebarAddShare( + $photoId: ID! + $password: String + $expire: _Neo4jDateInput + ) { + sharePhoto(photoId: $photoId, password: $password, expire: $expire) { + token + } + } +` + +const deleteShareMutation = gql` + mutation sidebareDeleteShare($token: ID!) { + deleteShareToken(token: $token) { + token + } + } +` + const SidebarShare = ({ photo }) => { if (!photo || !photo.id) return null @@ -18,7 +38,7 @@ const SidebarShare = ({ photo }) => {

Sharing options

- {({ loading, error, data }) => { + {({ loading, error, data, refetch }) => { if (loading) return
Loading...
if (error) return
Error: {error}
@@ -32,7 +52,29 @@ const SidebarShare = ({ photo }) => {