1
Fork 0

Completely change sidebar structure + Further work towards sharing

This commit is contained in:
viktorstrate 2019-08-15 18:32:49 +02:00
parent 54f5750ab5
commit c6fcba4083
11 changed files with 323 additions and 128 deletions

View File

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

View File

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

View File

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

View File

@ -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 (
<Container>
<LeftSidebar>
<SideMenu>
<SideButton to="/photos" exact>
<Icon name="image outline" />
<SideButtonLabel>Photos</SideButtonLabel>
@ -110,8 +111,10 @@ class Layout extends Component {
return null
}}
</Query>
</LeftSidebar>
<Content>{this.props.children}</Content>
</SideMenu>
<Sidebar>
<Content>{this.props.children}</Content>
</Sidebar>
<Header>
<Title>Photoview</Title>
</Header>

View File

@ -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 (
<Layout>
<Query query={albumQuery} variables={{ id: albumId }}>
{({ loading, error, data }) => {
if (error) return <div>Error</div>
<SidebarConsumer>
{({ updateSidebar }) => (
<Query query={albumQuery} variables={{ id: albumId }}>
{({ loading, error, data }) => {
if (error) return <div>Error</div>
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 = (
<AlbumGallery
loading={loading}
error={error}
albums={data.album.subAlbums}
/>
)
}
}
return (
<div>
<h1>{data.album && data.album.title}</h1>
{subAlbumElement}
{data.album && data.album.subAlbums.length > 0 && (
<h2>Images</h2>
)}
<PhotoGallery
loading={loading}
photos={data.album && data.album.photos}
activeIndex={this.state.activeImage}
presenting={this.state.presenting}
onSelectImage={index => {
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
if (data.album.subAlbums.length > 0) {
subAlbumElement = (
<AlbumGallery
loading={loading}
error={error}
albums={data.album.subAlbums}
/>
)
}
/>
</div>
)
}}
</Query>
}
return (
<div>
<AlbumTitle album={data && data.album} disableLink />
{subAlbumElement}
{data.album && data.album.subAlbums.length > 0 && (
<h2>Images</h2>
)}
<PhotoGallery
loading={loading}
photos={data.album && data.album.photos}
activeIndex={this.state.activeImage}
presenting={this.state.presenting}
onSelectImage={index => {
updateSidebar(
<PhotoSidebar imageId={this.photos[index].id} />
)
this.setActiveImage(index)
}}
setPresenting={this.setPresenting}
nextImage={this.nextImage}
previousImage={this.previousImage}
/>
</div>
)
}}
</Query>
)}
</SidebarConsumer>
</Layout>
)
}

View File

@ -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 (
<Layout>
<Query query={photoQuery}>
{({ loading, error, data }) => {
if (error) return error
<SidebarConsumer>
{({ updateSidebar }) => (
<Query query={photoQuery}>
{({ 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) => (
<div key={album.id}>
<Link to={`/album/${album.id}`}>
<h1 style={{ color: 'black', margin: '24px 0 12px 0' }}>
{album.title}
</h1>
</Link>
<PhotoGallery
onSelectImage={photoIndex => {
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}
/>
</div>
))
}
if (data.myAlbums) {
galleryGroups = data.myAlbums.map((album, index) => (
<div key={album.id}>
<AlbumTitle album={album} />
<PhotoGallery
onSelectImage={photoIndex => {
updateSidebar(
<PhotoSidebar
imageId={album.photos[photoIndex].id}
/>
)
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}
/>
</div>
))
}
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 (
<div>
{galleryGroups}
<PhotoSidebar imageId={activeImage} />
</div>
)
}}
</Query>
return <div>{galleryGroups}</div>
}}
</Query>
)}
</SidebarConsumer>
</Layout>
)
}

View File

@ -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 <StyledIcon name="edit outline" size="small" {...props} />
}
const AlbumTitle = ({ album, disableLink = false }) => {
if (!album) return null
let title = <span>{album.title}</span>
if (!disableLink) {
title = <Link to={`/album/${album.id}`}>{title}</Link>
}
return (
<SidebarConsumer>
{({ updateSidebar }) => (
<Header>
{title}
<SettingsIcon
onClick={() => {
updateSidebar(<div>Title stuff {album.title}</div>)
}}
/>
</Header>
)}
</SidebarConsumer>
)
}
export default AlbumTitle

View File

@ -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 = ({
<PreventScroll />
<Query query={imageQuery} variables={{ id: image }}>
{({ loading, error, data }) => {
if (error) return error
if (error) {
alert(error)
return <div>{error.message}</div>
}
let original = null
if (!loading) {
const { photo } = data
original = (
<PresentImage
// style={{ display: 'none' }}
src={photo && photo.original.url}
onLoad={imageLoadedCallback && imageLoadedCallback()}
onLoad={e => {
// e.target.style.display = 'initial'
imageLoadedCallback && imageLoadedCallback()
}}
/>
)
}
return (
<div>
<PresentImage src={thumbnail} />
{original}
<PresentImage src={thumbnail} />
</div>
)
}}

View File

@ -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 <RightSidebar />
return null
}
return (
<RightSidebar>
<div>
<Query query={photoQuery} variables={{ id: imageId }}>
{({ loading, error, data }) => {
if (error) return error
@ -129,9 +118,9 @@ class AlbumSidebar extends Component {
)
}}
</Query>
</RightSidebar>
</div>
)
}
}
export default AlbumSidebar
export default PhotoSidebar

View File

@ -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 }) => {
<div>
<h2>Sharing options</h2>
<Query query={shareQuery} variables={{ photoId: photo.id }}>
{({ loading, error, data }) => {
{({ loading, error, data, refetch }) => {
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
@ -32,7 +52,29 @@ const SidebarShare = ({ photo }) => {
<Button icon="chain" content="Copy" />
<Dropdown button text="More">
<Dropdown.Menu>
<Dropdown.Item text="Delete" icon="delete" />
<Mutation
mutation={deleteShareMutation}
onCompleted={() => {
refetch()
}}
>
{(deleteShare, { loading, error, data }) => {
return (
<Dropdown.Item
text="Delete"
icon="delete"
disabled={loading}
onClick={() => {
deleteShare({
variables: {
token: share.token,
},
})
}}
/>
)
}}
</Mutation>
</Dropdown.Menu>
</Dropdown>
</Button.Group>
@ -42,7 +84,7 @@ const SidebarShare = ({ photo }) => {
if (rows.length == 0) {
rows.push(
<Table.Row>
<Table.Row key="no-shares">
<Table.Cell colSpan="2">No shares found</Table.Cell>
</Table.Row>
)
@ -62,7 +104,32 @@ const SidebarShare = ({ photo }) => {
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan="2">
<Button content="New" floated="right" positive />
<Mutation
mutation={addShareMutation}
onCompleted={() => {
refetch()
}}
>
{(sharePhoto, { loading, error, data }) => {
return (
<Button
content="Add share"
icon="add"
floated="right"
positive
loading={loading}
disabled={loading}
onClick={() => {
sharePhoto({
variables: {
photoId: photo.id,
},
})
}}
/>
)
}}
</Mutation>
</Table.HeaderCell>
</Table.Row>
</Table.Footer>

View File

@ -0,0 +1,48 @@
import React, { createContext } from 'react'
import styled from 'styled-components'
import { Route } from 'react-router-dom'
const SidebarContainer = styled.div`
height: 100%;
width: 500px;
position: fixed;
overflow-y: scroll;
right: 0;
top: 60px;
background-color: white;
padding: 12px 12px 100px;
border-left: 1px solid #eee;
`
export const SidebarContext = createContext()
export const SidebarConsumer = SidebarContext.Consumer
const { Consumer, Provider } = SidebarContext
class Sidebar extends React.Component {
constructor(props) {
super(props)
this.state = {
content: 'Start value',
}
this.update = content => this.setState({ content })
}
render() {
return (
<Provider
value={{ updateSidebar: this.update, content: this.state.content }}
>
{this.props.children}
<Consumer>
{value => <SidebarContainer>{value.content}</SidebarContainer>}
</Consumer>
</Provider>
)
}
}
export default Sidebar