Commit 53905d17 authored by Matija Obreza's avatar Matija Obreza
Browse files

Image gallery support

- Added FolderCrumbs
parent 3e78f924
......@@ -74,7 +74,7 @@ module.exports = {
}
if (p.startsWith('/uploads')) {
p = p.replace(/^\/uploads/, '/api/v0/repository/download');
p = p.replace(/^\/uploads/, '/api/v1/repository/download');
}
// If authorization header is not provided, use access_token from cookie
......
......@@ -11,7 +11,7 @@ const httpProxy = proxy(config.apiUrl, {
return true;
}
if (req.url.startsWith('/uploads')) {
req.url = req.url.replace(/^\/uploads/, '/api/v0/repository/download');
req.url = req.url.replace(/^\/uploads/, '/api/v1/repository/download');
}
if (req.url.startsWith('/api')) {
// If authorization header is not provided, use access_token from cookie
......
import RepositoryFolder from 'model/repository/RepositoryFolder';
import RepositoryImage from 'model/repository/RepositoryImage';
import { Permissions, IUserPermissions } from 'model/acl.model';
......@@ -7,13 +6,13 @@ import { Permissions, IUserPermissions } from 'model/acl.model';
*/
class ImageGallery implements IUserPermissions {
public _permissions: Permissions;
public clazz: string = 'org.genesys.filerepository.model.ImageGallery';
public static clazz: string = 'org.genesys.filerepository.model.ImageGallery';
public active: boolean;
public createdBy: number;
public createdDate: Date;
public description: string;
public folder: RepositoryFolder;
public folder: string;
public id: number;
public images: RepositoryImage[];
public lastModifiedBy: number;
......
import { normalize } from 'path';
// Constants
import { RECEIVE_FOLDER_DETAILS, RECEIVE_IMAGE_GALLERY } from 'repository/constants';
// Model
import FolderDetails from 'model/repository/FolderDetails';
// Service
import RepositoryService from 'service/genesys/RepositoryService';
// Util
import { log } from 'utilities/debug';
import RepositoryFile from 'model/repository/RepositoryFile';
import RepositoryFolder from 'model/repository/RepositoryFolder';
import ImageGallery from 'model/repository/ImageGallery';
const receiveFolder = (folder: FolderDetails, error = null) => ({
type: RECEIVE_FOLDER_DETAILS,
payload: { folder, error },
});
const receiveGallery = (gallery: ImageGallery, error = null) => ({
type: RECEIVE_IMAGE_GALLERY,
payload: { gallery, error },
});
// Get folder details
export const getFolder = (path: string = '/') => (dispatch, getState) => {
return RepositoryService.getFolder(getState().login.access_token, normalize(path))
.then((folder: FolderDetails) => {
dispatch(receiveFolder(folder));
})
.catch((error) => {
log('Error', error);
});
};
// Get folder details
export const deleteFolder = (path: string) => (dispatch, getState) => {
return RepositoryService.deleteFolder(getState().login.access_token, normalize(path))
.then((folder: RepositoryFolder) => {
console.log(`Folder deleted ${path}`, folder);
return true;
})
.catch((error) => {
log('Error', error);
return false;
});
};
// Upload file
export const uploadFile = (path: string, file: File) => (dispatch, getState) => {
return RepositoryService.uploadFile(getState().login.access_token, normalize(path), file)
.then((file: RepositoryFile) => {
dispatch(getFolder(file.folder.path));
})
.catch((error) => {
log('Error', error);
});
};
// Image gallery
export const getGallery = (path: string) => (dispatch, getState) => {
return RepositoryService.getGallery(getState().login.access_token, normalize(path))
.then((gallery: ImageGallery) => {
dispatch(receiveGallery(gallery));
})
.catch((error) => {
log('Error', error);
});
};
// Get folder details
export const createGallery = (path: string, title: string, description?: string) => (dispatch, getState) => {
const gallery: ImageGallery = new ImageGallery();
gallery.title = title;
gallery.description = description;
return RepositoryService.createGallery(getState().login.access_token, normalize(path), gallery)
.then((gallery: ImageGallery) => {
console.log(`Gallery created at ${path}`, gallery);
dispatch(receiveGallery(null));
return true;
})
.catch((error) => {
log('Error', error);
return false;
});
};
// Get folder details
export const removeGallery = (path: string) => (dispatch, getState) => {
return RepositoryService.removeGallery(getState().login.access_token, normalize(path))
.then((gallery: ImageGallery) => {
console.log(`Gallery deleted ${path}`, gallery);
dispatch(receiveGallery(null));
return true;
})
.catch((error) => {
log('Error', error);
return false;
});
};
import { normalize } from 'path';
// Constants
import { RECEIVE_FOLDER_DETAILS } from 'repository/constants';
// Model
import FolderDetails from 'model/repository/FolderDetails';
// Service
import RepositoryService from 'service/genesys/RepositoryService';
// Util
import { log } from 'utilities/debug';
import RepositoryFile from 'model/repository/RepositoryFile';
import RepositoryFolder from 'model/repository/RepositoryFolder';
const receiveFolder = (folder: FolderDetails, error = null) => ({
type: RECEIVE_FOLDER_DETAILS,
payload: { folder, error },
});
// Get folder details
export const getFolder = (path: string = '/') => (dispatch, getState) => {
return RepositoryService.getFolder(getState().login.access_token, normalize(path))
.then((folder: FolderDetails) => {
dispatch(receiveFolder(folder));
})
.catch((error) => {
log('Error', error);
});
};
// Get folder details
export const deleteFolder = (path: string) => (dispatch, getState) => {
return RepositoryService.deleteFolder(getState().login.access_token, normalize(path))
.then((folder: RepositoryFolder) => {
console.log(`Folder deleted ${path}`, folder);
return true;
})
.catch((error) => {
log('Error', error);
return false;
});
};
// Upload file
export const uploadFile = (path: string = '/', file: File) => (dispatch, getState) => {
return RepositoryService.uploadFile(getState().login.access_token, normalize(path), file)
.then((file: RepositoryFile) => {
dispatch(getFolder(file.folder.path));
})
.catch((error) => {
log('Error', error);
});
};
export const RECEIVE_FOLDER_DETAILS = 'App/Repository/RECEIVE_FOLDER_DETAILS';
export const RECEIVE_IMAGE_GALLERY = 'App/Repository/RECEIVE_IMAGE_GALLERY';
import update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import { RECEIVE_FOLDER_DETAILS } from 'repository/constants';
import { RECEIVE_FOLDER_DETAILS, RECEIVE_IMAGE_GALLERY } from 'repository/constants';
import RepositoryFile from 'model/repository/RepositoryFile';
import FolderDetails from 'model/repository/FolderDetails';
import ImageGallery from 'model/repository/ImageGallery';
const INITIAL_STATE: {
folder: FolderDetails;
file: RepositoryFile;
gallery: ImageGallery;
loading: boolean;
} = {
folder: null,
file: null,
gallery: null,
loading: false,
};
......@@ -27,6 +30,14 @@ export default function reducer(state = INITIAL_STATE, action: IReducerAction =
break;
}
case RECEIVE_IMAGE_GALLERY: {
return update(state, {
loading: { $set: false},
gallery: { $set: action.payload.gallery },
error: { $set: action.payload.error },
});
}
default:
return state;
}
......
// Admin
import RepositoryBrowser from 'repository/ui/RepositoryBrowser';
import ImageGalleryPage from 'repository/ui/ImageGalleryPage';
// Public
......@@ -15,7 +15,7 @@ export const dashboardRoutes = [
export const adminRoutes = [
// Admin
{
path: '/repository:path(.+)/',
path: '/repository/f:path(.+)/',
component: RepositoryBrowser,
exact: true,
root: '',
......@@ -23,4 +23,13 @@ export const adminRoutes = [
title: 'File repository',
},
},
{
path: '/repository/g:path(.+)/',
component: ImageGalleryPage,
exact: true,
root: '',
extraProps: {
title: 'Image gallery',
},
},
];
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withStyles } from '@material-ui/core/styles';
import { normalize } from 'path';
import { getGallery, removeGallery, uploadFile } from 'repository/actions/admin';
import ImageGallery from 'model/repository/ImageGallery';
// import RepositoryFile from 'model/repository/RepositoryFile';
import Loading from 'ui/common/Loading';
import { PageContents } from 'ui/layout/PageLayout';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import Permissions from 'ui/common/permission/Permissions';
import Button from '@material-ui/core/Button';
import { navigateTo } from 'actions/navigation';
import confirmAlert from 'utilities/confirmAlert';
import ImageGalleryView from 'repository/ui/c/ImageGalleryView';
import { FolderCrumbs } from 'repository/ui/c/FolderCrumbs';
// import Grid from '@material-ui/core/Grid';
interface IRepositoryBrowserProps extends React.ClassAttributes<any> {
classes: any;
t?: any;
root: string;
path: string;
folderPath: string;
gallery: ImageGallery;
getGallery: (path: string) => any;
removeGallery: (path: string) => any;
uploadFile: (path: string, file: File) => any;
navigateTo: (path: string, qs?: any) => any;
}
const styles = (theme) => ({
});
class ImageGalleryPage extends React.Component<IRepositoryBrowserProps, any> {
protected static needs = [
({ root, path }) => getGallery(normalize(root + path).replace(/^(.+)\/$/, '$1')),
];
public componentWillMount() {
const { path, root, folderPath, gallery, getGallery } = this.props;
console.log(`RepositoryBrowser.componentWillMount...`, folderPath);
if (!gallery || (gallery.folder !== folderPath)) {
console.log(`Loading gallery root=${root} path=${path}`);
getGallery(folderPath);
}
}
public componentWillReceiveProps(nextProps) {
const { path, root, gallery, folderPath } = nextProps;
const { getGallery } = this.props;
console.log(`Props root=${root} path=${path} folderPath=${folderPath}`);
if (!gallery || (gallery.folder !== folderPath)) {
console.log(`Loading gallery root=${root} path=${path}`);
getGallery(folderPath);
}
}
protected upload = (e) => {
const { uploadFile, folderPath, getGallery } = this.props;
const file = e.target.files[0];
uploadFile(folderPath, file).then(() => getGallery(folderPath));
e.target.files = null;
e.target.value = null;
}
protected deleteGallery = (e) => {
const { removeGallery, navigateTo, folderPath } = this.props;
removeGallery(folderPath).then((result) => {
if (result) {
navigateTo(`/admin/repository/f${folderPath}`);
} else {
confirmAlert(<p>Gallery at <b>{ folderPath }</b> could not be deleted.</p>);
}
});
}
protected goToFolder = (e) => {
const { navigateTo, folderPath } = this.props;
navigateTo(`/admin/repository/f${folderPath}`);
}
public render() {
const { gallery, root, path } = this.props;
const stillLoading: boolean = !gallery;
console.log(gallery);
return (
stillLoading ? <Loading /> : (
<div>
<ContentHeaderWithButton title={ <FolderCrumbs disabled root={ root } path={ path } /> } buttons={
<span>
<Button key="viewf" variant="raised" onClick={ this.goToFolder }>To folder</Button>
{ gallery && gallery._permissions.delete && <Button onClick={ this.deleteGallery } key="deletef">Delete gallery</Button> }
{ gallery && gallery._permissions.manage && <Permissions clazz={ ImageGallery.clazz } id={ gallery.id } /> }
</span>
} />
<PageContents>
<ImageGalleryView imageGallery={ gallery } />
<div>
<input type="file" onChange={ this.upload } />
</div>
</PageContents>
</div>
)
);
}
}
const mapStateToProps = (state, ownProps) => ({
root: ownProps.route.root || '/',
path: ownProps.match.params.path || '/',
folderPath: normalize((ownProps.route.root || '/') + (ownProps.match.params.path || '/')).replace(/^(.+)\/$/, '$1'),
gallery: state.repository.admin.gallery,
loading: state.repository.admin.loading,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
getGallery,
uploadFile,
removeGallery,
navigateTo,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ImageGalleryPage));
......@@ -4,7 +4,7 @@ import { bindActionCreators } from 'redux';
import { withStyles } from '@material-ui/core/styles';
import { normalize } from 'path';
import { getFolder, uploadFile, deleteFolder } from 'repository/actions/public';
import { getFolder, uploadFile, deleteFolder, createGallery } from 'repository/actions/admin';
import FolderDetails from 'model/repository/FolderDetails';
// import RepositoryFile from 'model/repository/RepositoryFile';
......@@ -20,8 +20,9 @@ import RepositoryFolder from 'model/repository/RepositoryFolder';
import Button from '@material-ui/core/Button';
import { navigateTo } from 'actions/navigation';
import confirmAlert from 'utilities/confirmAlert';
import { FolderCrumbs } from './c/FolderCrumbs';
// import Grid from '@material-ui/core/Grid';
import Grid from '@material-ui/core/Grid';
interface IRepositoryBrowserProps extends React.ClassAttributes<any> {
classes: any;
......@@ -29,10 +30,12 @@ interface IRepositoryBrowserProps extends React.ClassAttributes<any> {
root: string;
path: string;
folderPath: string;
folder: FolderDetails;
getFolder: (path: string) => any;
uploadFile: (path: string, file: File) => any;
deleteFolder: (path: string) => any;
createGallery: (path: string, title: string, description?: string) => any;
navigateTo: (path: string, qs?: any) => any;
}
......@@ -47,8 +50,7 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
];
public componentWillMount() {
const { root, path, folder, getFolder } = this.props;
const folderPath: string = normalize(root + path).replace(/^(.+)\/$/, '$1');
const { root, path, folder, folderPath, getFolder } = this.props;
console.log(`RepositoryBrowser.componentWillMount...`, normalize(root + path));
if (!folder || (folder.folder && folder.folder.path !== folderPath)) {
......@@ -58,9 +60,8 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
}
public componentWillReceiveProps(nextProps) {
const { path, root, folder } = nextProps;
const { path, root, folder, folderPath } = nextProps;
const { folder: oldFolder, getFolder } = this.props;
const folderPath: string = normalize(root + path).replace(/^(.+)\/$/, '$1');
console.log(`Props root=${root} path=${path} folderPath=${folderPath} at=${folder && folder.folder && folder.folder.path}`, oldFolder);
......@@ -71,8 +72,7 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
}
protected upload = (e) => {
const { uploadFile, root, path } = this.props;
const folderPath: string = normalize(root + path).replace(/^(.+)\/$/, '$1');
const { uploadFile, folderPath } = this.props;
const file = e.target.files[0];
uploadFile(folderPath, file);
......@@ -81,8 +81,7 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
}
protected deleteFolder = (e) => {
const { deleteFolder, navigateTo, root, path } = this.props;
const folderPath: string = normalize(root + path).replace(/^(.+)\/$/, '$1');
const { deleteFolder, navigateTo, folderPath } = this.props;
deleteFolder(folderPath).then((result) => {
if (result) {
......@@ -93,6 +92,23 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
});
}
protected createGallery = (e) => {
const { createGallery, navigateTo, folder, folderPath } = this.props;
createGallery(folderPath, folder.folder.name).then((result) => {
if (result) {
navigateTo(`/admin/repository/g${folderPath}`);
} else {
confirmAlert(<p>Image gallery at <b>{ folderPath }</b> could not be created.</p>);
}
});
}
protected goToGallery = (e) => {
const { navigateTo, folderPath } = this.props;
navigateTo(`/admin/repository/g${folderPath}`);
}
public render() {
const { folder, root, path } = this.props;
......@@ -108,10 +124,10 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
return (
stillLoading ? <Loading /> : (
<div>
<ContentHeaderWithButton title={ path } buttons={
<ContentHeaderWithButton title={ <FolderCrumbs root={ root } path={ path } /> } buttons={
<span>
{ ! folder.folder ? null :
folder.gallery ? <Button key="viewg" variant="raised">View gallery</Button> : <Button key="createg">Create gallery</Button> }
folder.gallery ? <Button key="viewg" variant="raised" onClick={ this.goToGallery }>View gallery</Button> : <Button onClick={ this.createGallery } key="createg">Create gallery</Button> }
{ folder.folder && folder.folder._permissions.delete && <Button onClick={ this.deleteFolder } key="deletef">Delete folder</Button> }
{ folder.folder && folder.folder._permissions.manage && <Permissions clazz={ RepositoryFolder.clazz } id={ folder.folder.id } /> }
</span>
......@@ -121,9 +137,11 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
{ folder.folder && root !== `${folder.folder.path}/` && <FolderCard compact key="parent" folder={ parentFolder } /> }
{ folder.subFolders.map((subFolder) => <FolderCard compact key={ subFolder.uuid } folder={ subFolder } />) }
</div>
<div>
{ folder.files.map((file) => <FileCard key={ file.uuid } file={ file } />) }
</div>
<Grid item>
<Grid container spacing={ 16 }>
{ folder.files.map((file) => <Grid key={ file.uuid } item xs={ 12 }><FileCard file={ file } /></Grid>) }
</Grid>
</Grid>
<div>
<input type="file" onChange={ this.upload } />
</div>
......@@ -137,6 +155,7 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
const mapStateToProps = (state, ownProps) => ({
root: ownProps.route.root || '/',
path: ownProps.match.params.path || '/',
folderPath: normalize((ownProps.route.root || '/') + (ownProps.match.params.path || '/')).replace(/^(.+)\/$/, '$1'),
folder: state.repository.admin.folder,
loading: state.repository.admin.loading,
});
......@@ -145,6 +164,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
getFolder,
uploadFile,
deleteFolder,
createGallery,
navigateTo,
}, dispatch);
......
......@@ -28,7 +28,7 @@ const FileCard = ({file, classes, compact = false, edit = false, ...other}: { fi
return compact ? (
<Grid item xs={ 12 }>
<Card className={ classes.root }>
<CardHeader title={ <a href={ `/proxy/api/v1/repository/download/${file.uuid}` }>{ file.originalFilename }</a> } />
<CardHeader title={ <a href={ `/proxy/uploads/${file.uuid}` }>{ file.originalFilename }</a> } />
<CardContent>
{ file.storageFolder }
</CardContent>
......@@ -39,7 +39,7 @@ const FileCard = ({file, classes, compact = false, edit = false, ...other}: { fi
(
<Grid item xs={ 12 }>
<Card className={ classes.root }>
<CardHeader title={ <span><a href={ `/proxy/api/v1/repository/download/${file.uuid}` }><DownloadIcon /></a> <span>{ file.originalFilename }</span></span> } />
<CardHeader title={ <span><a href={ `/proxy/uploads/${file.uuid}` }><DownloadIcon /></a> <span>{ file.originalFilename }</span></span> } />
<CardContent>
<Properties>
<PropertiesItem title="Original name">{ file.originalFilename }</PropertiesItem>
......
import * as React from 'react';
import { Link } from 'react-router-dom';
export function FolderCrumbs({ path, root = '/', disabled = false }: { path: string, root?: string, disabled?: boolean }) {
if (path) {
const parts: string[] = path.split('/').filter((part) => part.length > 0);
const len: number = parts.length;
console.log(`Path ${disabled} parts path=${path}`, parts, len);