Commit 15f7efda authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '78-file-repository-admin' into 'master'

Resolve "File Repository: Admin"

Closes #78

See merge request genesys-pgr/genesys-ui!72
parents a3cfd20d 53905d17
......@@ -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
......
......@@ -6,6 +6,8 @@ const origin = typeof window !== 'undefined' ?
export const API_ROOT = `${origin}/proxy`;
export const API_BASE_URL = `${API_ROOT}/api/v0`;
export const APIv1_BASE_URL = `${API_ROOT}/api/v1`;
export const SERVER_INFO_URL = `${API_ROOT}/api/v1/info/version`;
export const LOGIN_URL = `${API_ROOT}/oauth/token`;
......
import RepositoryFile from 'model/repository/RepositoryFile';
import RepositoryFolder from 'model/repository/RepositoryFolder';
import ImageGallery from 'model/repository/ImageGallery';
/*
* Defined in Swagger as '#/definitions/FolderDetails'
*/
class FolderDetails {
public files: RepositoryFile[];
public folder: RepositoryFolder;
public subFolders: RepositoryFolder[];
public gallery: ImageGallery;
}
export default FolderDetails;
import RepositoryImage from 'model/repository/RepositoryImage';
import { Permissions, IUserPermissions } from 'model/acl.model';
/*
* Defined in Swagger as '#/definitions/ImageGallery'
*/
class ImageGallery implements IUserPermissions {
public _permissions: Permissions;
public static clazz: string = 'org.genesys.filerepository.model.ImageGallery';
public active: boolean;
public createdBy: number;
public createdDate: Date;
public description: string;
public folder: string;
public id: number;
public images: RepositoryImage[];
public lastModifiedBy: number;
public lastModifiedDate: Date;
public title: string;
public version: number;
}
export default ImageGallery;
import RepositoryFolder from 'model/repository/RepositoryFolder';
import { Permissions, IUserPermissions } from 'model/acl.model';
/*
* Defined in Swagger as '#/definitions/RepositoryFile'
*/
class RepositoryFile implements IUserPermissions {
public _permissions: Permissions;
public clazz: string = 'org.genesys.filerepository.model.RepositoryFile';
public accessRights: string;
public active: boolean;
public bibliographicCitation: string;
public contentType: string;
public created: string;
public createdBy: number;
public createdDate: Date;
public creator: string;
public dateRetrieved: Date;
public dateSubmitted: Date;
public description: string;
public extension: string;
public extent: string;
public filename: string;
public folder: RepositoryFolder;
public format: string;
public id: number;
public identifier: string;
public lastModifiedBy: number;
public lastModifiedDate: Date;
public license: string;
public md5Sum: string;
public metadataFilename: string;
public modified: Date;
public originalFilename: string;
public originalUrl: string;
public rightsHolder: string;
public sha1Sum: string;
public size: number;
public storageFolder: string;
public storagePath: string;
public subject: string;
public title: string;
public uuid: string;
public version: number;
}
export default RepositoryFile;
import { IUserPermissions, Permissions } from 'model/acl.model';
/*
* Defined in Swagger as '#/definitions/RepositoryFolder'
*/
class RepositoryFolder implements IUserPermissions {
public _permissions: Permissions;
public static clazz: string = 'org.genesys.filerepository.model.RepositoryFolder';
public active: boolean;
public children: string[]; // uuids
public createdBy: number;
public createdDate: Date;
public description: string;
public id: number;
public lastModifiedBy: number;
public lastModifiedDate: Date;
public name: string;
public path: string;
public title: string;
public uuid: string;
public version: number;
}
export default RepositoryFolder;
import RepositoryFile from 'model/repository/RepositoryFile';
/*
* Defined in Swagger as '#/definitions/RepositoryImage'
*/
class RepositoryImage extends RepositoryFile {
public format: string;
public height: number;
public orientation: string;
public thumbnailPath: string;
public width: number;
}
export default RepositoryImage;
import { UuidModel } from 'model/common.model';
class RepositoryFile extends UuidModel {
public title: string;
public description: string;
public path: string;
public originalFilename: string;
public contentType: string;
public size: number;
public format: string;
public constructor(obj?) {
super(obj);
}
public getClassname(): string {
return 'org.genesys.filerepository.model.RepositoryFile';
}
}
export { RepositoryFile };
......@@ -14,25 +14,30 @@ import institutes from './institute';
import applicationConfig from './applicationConfig';
import crop from 'crop/reducers';
import user from 'user/reducers';
import repository from 'repository/reducers';
import requests from './requests';
const rootReducer = combineReducers({
login,
serverInfo,
routing: routerReducer,
form: formReducer,
appMounted,
history,
login,
serverInfo,
pageTitle,
applicationConfig,
snackbar,
filterCode,
routing: routerReducer,
form: formReducer,
crop,
user,
repository,
subsets,
accessions,
institutes,
applicationConfig,
crop,
requests,
user,
});
export default rootReducer;
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;
});
};
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, 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,
};
export default function reducer(state = INITIAL_STATE, action: IReducerAction = { type: '' }) {
switch (action.type) {
case RECEIVE_FOLDER_DETAILS: {
return update(state, {
loading: { $set: false},
folder: { $set: action.payload.folder },
error: { $set: action.payload.error },
});
break;
}
case RECEIVE_IMAGE_GALLERY: {
return update(state, {
loading: { $set: false},
gallery: { $set: action.payload.gallery },
error: { $set: action.payload.error },
});
}
default:
return state;
}
}
import { combineReducers } from 'redux';
// import dashboard from './dashboard';
// import publicState from './public';
import admin from './admin';
const rootReducer = combineReducers({
// dashboard,
// public: publicState,
admin,
});
export default rootReducer;
// Admin
import RepositoryBrowser from 'repository/ui/RepositoryBrowser';
import ImageGalleryPage from 'repository/ui/ImageGalleryPage';
// Public
export const publicRoutes = [
// Root
];
export const dashboardRoutes = [
// Dashboard
];
export const adminRoutes = [
// Admin
{
path: '/repository/f:path(.+)/',
component: RepositoryBrowser,
exact: true,
root: '',
extraProps: {
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));
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 { getFolder, uploadFile, deleteFolder, createGallery } from 'repository/actions/admin';
import FolderDetails from 'model/repository/FolderDetails';
// 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 FileCard<