Commit 4f4842be authored by Matija Obreza's avatar Matija Obreza

Merge branch '132-download-accession-data' into 'master'

Resolve "Download accession data"

Closes #132

See merge request genesys-pgr/genesys-ui!133
parents 5b6145d8 27f1a630
......@@ -5,6 +5,7 @@
"backTo": "Back to {{where, lowercase}}",
"backToDashboard": "Back to dashboard",
"cancel": "Cancel",
"OK": "OK",
"close": "Close",
"collapse": "Collapse",
"delete": "Delete",
......
......@@ -288,6 +288,12 @@
"Admin": "Administration",
"login": "Login",
"register": "Registration"
},
"downloadDialog": {
"button": "Start download",
"title": "Before you download",
"refusing": "Refusing to export",
"warningMessage": "Accession data cannot be exported. There are more than {{value, number}} entries."
}
},
"datasets": {
......@@ -369,7 +375,9 @@
"associatedDatasets": "Associated Datasets",
"associatedSubsets": "Associated Subsets",
"relatedResources": "Related resources",
"imageGallery": "Image gallery"
"imageGallery": "Image gallery",
"MCPD": "MCPD",
"zip": "ZIP"
},
"browse": {
"title": "Accession browser",
......
......@@ -50,7 +50,9 @@
"associatedDatasets": "Associated Datasets",
"associatedSubsets": "Associated Subsets",
"relatedResources": "Related resources",
"imageGallery": "Image gallery"
"imageGallery": "Image gallery",
"MCPD": "MCPD",
"zip": "ZIP"
},
"browse": {
"title": "Accession browser",
......
......@@ -6,6 +6,7 @@ import { parse } from 'query-string';
// Actions
import { applyFilters, loadMoreAccessions, updateRoute } from 'accessions/actions/public';
import {loadArticlePromise} from 'cms/actions/public';
// Models
import Page from 'model/Page';
......@@ -22,9 +23,8 @@ import PaginationComponent from 'ui/common/pagination';
import AccessionCard from 'accessions/ui/c/AccessionCard';
import Tabs, { Tab } from 'ui/common/Tabs';
import AccessionFilters from './c/Filters';
// TODO only for demo
import Button from '@material-ui/core/Button';
import ButtonBar from 'ui/common/buttons/ButtonBar';
import DownloadDialog, { DOWNLOAD_LIMIT } from 'ui/common/download-dialog';
class BrowsePage extends BrowsePageTemplate<Accession> {
protected static needs = [
......@@ -38,6 +38,23 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
super(props, context);
}
public state = {
downloadArticle: null,
authenticated: false,
};
public componentWillMount() {
super.componentWillMount();
const { userRoles, loadArticlePromise, lang } = this.props;
const authenticated: boolean = userRoles.findIndex((role) => role === 'ROLE_USER') !== -1;
this.setState({authenticated});
const slug: string = authenticated ? 'download-authenticated' : 'download-anonymous';
if (!this.state.downloadArticle || (this.state.downloadArticle.slug !== slug)) {
loadArticlePromise(lang, slug).then((data) => this.setState({downloadArticle: data}));
}
}
public render() {
const { paged, loadMoreData, filterCode, currentTab, t} = this.props;
......@@ -61,9 +78,18 @@ class BrowsePage extends BrowsePageTemplate<Accession> {
tab={ currentTab }
actions={
<ButtonBar>
<Button variant="contained">{ t('TODO-Demo Share') }</Button>
<Button variant="contained">{ t('TODO-Demo Select all') }</Button>
<Button variant="contained">{ t('TODO-Demo Delete') }</Button>
{ this.state.authenticated &&
<DownloadDialog downloadUrl="/proxy/api/v1/acn/download"
postParams={ { mcpd: 'mcpd', f: paged && paged.filterCode || '' } }
article={ this.state.downloadArticle }
disabled={ paged && paged.totalElements > DOWNLOAD_LIMIT }
buttonTitle={ `${t('common:action.download')} ${t('accessions.public.p.display.MCPD')}` } />
}
<DownloadDialog downloadUrl="/proxy/api/v1/acn/download"
article={ this.state.downloadArticle }
postParams={ { dwca: 'dwca', f: paged && paged.filterCode || '' } }
disabled={ paged && paged.totalElements > DOWNLOAD_LIMIT }
buttonTitle={ `${t('common:action.download')} ${t('accessions.public.p.display.zip')}` } />
</ButtonBar>
}
>
......@@ -97,12 +123,15 @@ const mapStateToProps = (state, ownProps) => ({
paged: state.accessions.public.paged || undefined,
filterCode: ownProps.match.params.filterCode,
currentTab: ownProps.match.params.tab || 'data', // current tab, or ownProps.location.pathname
userRoles: state.login.authorities,
lang: state.applicationConfig.lang,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
applyFilters,
loadMoreData: loadMoreAccessions,
updateRoute,
loadArticlePromise,
}, dispatch);
......
......@@ -24,6 +24,10 @@ export const loadArticle = (locale: string, slug: string) => (dispatch) => {
.then((article) => dispatch(receiveArticle(slug, article)));
};
export const loadArticlePromise = (locale: string, slug: string) => (dispatch) => {
return CmsService.getGlobalArticle(locale, slug);
};
const receiveDocumentation = (slug: string, documentation: ADoc) => ({
type: RECEIVE_DOCUMENTATION,
payload: {slug, documentation},
......
......@@ -32,6 +32,9 @@ import Button from '@material-ui/core/Button';
import Permissions from 'ui/common/permission/Permissions';
import FaoInstitute from 'model/genesys/FaoInstitute';
import ButtonBar from 'ui/common/buttons/ButtonBar';
import DownloadDialog from 'ui/common/download-dialog';
import {loadArticlePromise} from 'cms/actions/public';
import Article from 'model/cms/Article';
interface IDisplayPageProps extends React.ClassAttributes<any> {
t: any;
......@@ -43,6 +46,9 @@ interface IDisplayPageProps extends React.ClassAttributes<any> {
loadInstitute: any;
applyFilters: any;
applyOverviewFilters: any;
userRoles: string[];
loadArticlePromise: (locale: string, slug: string) => Promise<Article>;
lang: string;
}
const mobile = ['sm', 'xs'] as Breakpoint[];
......@@ -57,6 +63,11 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
super(props, context);
}
public state = {
downloadArticle: null,
authenticated: false,
};
private applyInstituteCodeFilter = () => {
const { institute, applyFilters} = this.props;
const filter = {holder: {code: [ institute.details.code ]}};
......@@ -87,11 +98,18 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
}
public componentWillMount() {
const { institute, code, loadInstitute } = this.props;
const { institute, code, loadInstitute, lang, userRoles, loadArticlePromise } = this.props;
if (code && (! institute || code !== institute.details.code)) {
console.log(`Reloading institute data for code=${code}`, institute);
loadInstitute(code);
}
const authenticated: boolean = userRoles.findIndex((role) => role === 'ROLE_USER') !== -1;
this.setState({authenticated});
const slug: string = authenticated ? 'download-authenticated' : 'download-anonymous';
if (!this.state.downloadArticle || (this.state.downloadArticle.slug !== slug)) {
loadArticlePromise(lang, slug).then((data) => this.setState({downloadArticle: data}));
}
}
public render() {
......@@ -144,9 +162,22 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
<ButtonBar barLabelText={ t('institutes.public.p.display.actions') }>
<Button onClick={ this.applyInstituteCodeFilter }>{ t('institutes.public.p.display.browseAccessions') }</Button>
<Button onClick={ this.applyFilterForOverview }>{ t('accessions.tab.overview') }</Button>
<Button onClick={ () => null }>{ `${t('common:action.download')} ${t('institutes.public.p.display.MCPD')}` }</Button>
<Button onClick={ () => null }>{ `${t('common:action.download')} ${t('institutes.public.p.display.PDCI_short')}` }</Button>
<Button onClick={ () => null }>{ `${t('common:action.download')} ${t('institutes.public.p.display.zip')}` }</Button>
{ this.state.authenticated &&
<DownloadDialog downloadUrl={ `/proxy/api/v1/wiews/${code}/download` }
article={ this.state.downloadArticle }
postParams={ { mcpd: 'mcpd' } }
buttonTitle={ `${t('common:action.download')} ${t('institutes.public.p.display.MCPD')}` } />
}
{ this.state.authenticated &&
<DownloadDialog downloadUrl={ `/proxy/api/v1/wiews/${code}/download` }
postParams={ { pdci: 'pdci' } }
article={ this.state.downloadArticle }
buttonTitle={ `${t('common:action.download')} ${t('institutes.public.p.display.PDCI_short')}` } />
}
<DownloadDialog downloadUrl={ `/proxy/api/v1/wiews/${code}/download` }
postParams={ { dwca: 'dwca' } }
article={ this.state.downloadArticle }
buttonTitle={ `${t('common:action.download')} ${t('institutes.public.p.display.zip')}` } />
{ institute.details._permissions.manage && <Link to={ `/dashboard/wiews/${institute.details.code}/edit` }><Button>{ t('common:action.edit') }</Button></Link> }
{ institute.details._permissions.manage && <Permissions clazz={ FaoInstitute.clazz } id={ institute.details.id }/> }
</ButtonBar>
......@@ -274,12 +305,15 @@ const mapStateToProps = (state, ownProps) => ({
institute: state.institutes.public.institute,
error: state.institutes.public.instituteError,
code: ownProps.match.params.wiewsCode,
userRoles: state.login.authorities,
lang: state.applicationConfig.lang,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadInstitute,
applyFilters,
applyOverviewFilters,
loadArticlePromise,
}, dispatch);
......
......@@ -62,7 +62,7 @@ const InstituteForm = ({ error, handleSubmit, initialValues, wiewsCode, t }) =>
<div className="pt-20">
<Button variant="contained" type="submit">{ t('common:action.saveChanges') }</Button>
<Link to={ `/wiews/${wiewsCode || ''}` }>
<Button variant="flat">{ t('common:action.backTo', { where: `${wiewsCode ? t('institutes.common.instDetails', { instCode: wiewsCode }) : t('institutes.common.instList')}` }) }</Button>
<Button variant="text">{ t('common:action.backTo', { where: `${wiewsCode ? t('institutes.common.instDetails', { instCode: wiewsCode }) : t('institutes.common.instList')}` }) }</Button>
</Link>
</div>
</form>
......
......@@ -37,7 +37,7 @@ interface IDimensionDialogProps extends React.ClassAttributes<any> {
dimTitle: string;
dimension: Dimension<any>;
useLink: boolean;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......
......@@ -33,7 +33,7 @@ interface IExecutionDialogProps extends React.ClassAttributes<any> {
execution: Execution;
buttonLabel?: string;
classes: any;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......
......@@ -34,7 +34,7 @@ interface IParameterDialogProps extends React.ClassAttributes<any> {
useLink: boolean;
classes: any;
parameter: KPIParameter;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......
......@@ -31,7 +31,7 @@ interface ICreateFolderDialogProps extends React.ClassAttributes<any> {
root?: string;
path?: string;
classes: any;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......
......@@ -137,7 +137,7 @@ class RepositoryBrowser extends React.Component<IRepositoryBrowserProps, any> {
{ folder.folder && folder.folder._permissions.delete && <Button onClick={ this.deleteFolder } key="deletef" variant="contained">{ t('repository.admin.p.repositoryBrowser.deleteFolder') }</Button> }
{ (! folder.folder || (folder.folder && folder.folder._permissions.create)) && <CreateFolderDialog root={ root } path={ path } /> }
{ folder.folder && folder.folder._permissions.write && <UpdateFolderDialog/> }
{ folder.folder && folder.folder._permissions.manage && <Permissions clazz={ RepositoryFolder.clazz } id={ folder.folder.id } /> }
{ folder.folder && folder.folder._permissions.manage && <Permissions clazz={ RepositoryFolder.clazz } id={ folder.folder.id } variant="contained" /> }
</span>
} />
<PageContents className={ `container-spacing-horizontal pt-1rem` }>
......
......@@ -29,7 +29,7 @@ interface IUpdateFolderDialogProps extends React.ClassAttributes<any> {
updateFolder: (folder: RepositoryFolder) => Promise<FolderDetails>;
folder: FolderDetails;
classes: any;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......
......@@ -111,7 +111,7 @@ const FileCard = ({file, classes, compact = false, edit = false, deleteFile, edi
<div className={ classes.actions }>
<span><ActionButton action={ () => editFile(file.uuid) } title={ t('common:action.edit') } /></span>
<span><ActionButton action={ () => deleteFile(file.uuid) } title={ t('common:action.delete') } /></span>
{ file && file._permissions.manage && <Permissions clazz={ className } id={ file.id } /> }
{ file && file._permissions.manage && <Permissions clazz={ className } id={ file.id } variant="contained" /> }
</div>
}
/>
......
......@@ -287,6 +287,12 @@
"Admin": "Administration",
"login": "Login",
"register": "Registration"
},
"downloadDialog": {
"button": "Start download",
"title": "Before you download",
"refusing": "Refusing to export",
"warningMessage": "Accession data cannot be exported. There are more than {{value, number}} entries."
}
},
"datasets": {
......
......@@ -2,7 +2,7 @@ import * as React from 'react';
import Button from '@material-ui/core/Button';
interface IActionButtonProps extends React.ClassAttributes<any> {
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
title: string;
action: any;
style?: any;
......
......@@ -30,6 +30,10 @@ const styles = (theme) => ({
},
menuItem: {
padding: '6px 0',
'& > div, & > span': {
width: '100%',
margin: '0 !important',
},
'& button': {
width: '100%',
height: '100%',
......
......@@ -4,7 +4,7 @@ import Button from '@material-ui/core/Button';
interface IUploadButtonProps extends React.ClassAttributes<any> {
handleUploading: (files: File[]) => void;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
title?: string;
multiple?: boolean;
style?: any;
......
import * as React from 'react';
import {translate} from 'react-i18next';
import {withStyles} from '@material-ui/core/styles';
// UI
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContentText from '@material-ui/core/DialogContentText';
import Article from 'model/cms/Article';
import Loading from 'ui/common/Loading';
/*tslint:disable*/
const styles = (theme) => ({
root: {
backgroundColor: 'white',
margin: '0px 8px',
'& > p': {
fontFamily: 'Roboto-Regular',
fontSize: '18px',
color: '#4d4c46',
lineHeight: '30px',
marginTop: '20px',
marginBottom: 0,
},
'& > h3': {
fontSize: '24px',
marginTop: '28px',
paddingBottom: '6px',
},
},
permButton: {
[theme.breakpoints.down('sm')]: {
width: '100%',
margin: '8px 0',
},
},
downloadBlock: {
width: 'auto',
display: 'flex' as 'flex',
justifyContent: 'flex-start' as 'flex-start',
paddingLeft: '24px',
paddingBottom: '24px',
},
title: {
backgroundColor: '#88ba42',
'& > h6': {
color: 'white',
paddingLeft: '8px',
marginBottom: 0,
fontSize: '1.714rem',
fontWeight: 'bold' as 'bold',
}
},
alertDescription: {
color: '#4d4c46',
}
});
/*tslint:enable*/
interface IDownloadProps extends React.ClassAttributes<any> {
classes: any;
t: any;
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
downloadUrl: string;
buttonTitle: string;
postParams: object;
article: Article;
disabled?: boolean;
}
export const DOWNLOAD_LIMIT: number = 200000;
class DownloadDialog extends React.Component<IDownloadProps, any> {
public state = {
open: false,
};
private show = () => {
this.setState({open: true});
}
private hide = () => {
this.setState({open: false});
}
private asyncHide = () => setTimeout(() => this.hide());
public render() {
const {variant = 'text', t, buttonTitle, downloadUrl, postParams, classes, article } = this.props;
let { disabled } = this.props;
disabled = !!disabled || !downloadUrl;
return (
<div style={ { margin: '0 4px' } }>
<Button onClick={ this.show } disabled={ disabled } className={ this.props.classes.permButton } variant={ variant }>{ buttonTitle }</Button>
{ this.state.open &&
<div>
{ !!disabled ?
<Dialog
open={ this.state.open }
onClose={ this.hide }
>
<DialogTitle id="alert-dialog-title">
<p>{ t('common.downloadDialog.refusing') }</p>
</DialogTitle>
<DialogContent>
<DialogContentText className={ classes.alertDescription }>
{ t(`common.downloadDialog.warningMessage`, {value: DOWNLOAD_LIMIT}) }
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={ this.hide } autoFocus>{ t('common:action.OK') }</Button>
</DialogActions>
</Dialog>
:
<Dialog
open={ this.state.open }
onClose={ this.hide }
maxWidth="lg"
fullWidth
>
<DialogTitle className={ classes.title }>{ t('common.downloadDialog.title') }</DialogTitle>
<DialogContent>
{
article && <div className={ classes.root } dangerouslySetInnerHTML={ {__html: article.body} }/>
|| <Loading />
}
</DialogContent>
<DialogActions className={ classes.downloadBlock }>
<form method="post" action={ `${downloadUrl}` } >
{ postParams && Object.keys(postParams).map((key) => (
<input key={ key } type="hidden" name={ key } value={ postParams[key] } />
)) }
<Button variant="text" className={ `back-green` } onClick={ this.asyncHide } type="submit" autoFocus>
<span className="white">{ t('common.downloadDialog.button') }</span>
</Button>
</form>
</DialogActions>
</Dialog>
}
</div>
}
</div>
);
}
}
export default translate()(withStyles(styles)(DownloadDialog));
......@@ -44,7 +44,7 @@ interface IPermissionsProps extends React.ClassAttributes<any> {
updateParentObject: (id: number, parentId: number) => Promise<AclObjectIdentity>;
change: any;
classes: any;
variant?: 'text' | 'flat' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
variant?: 'text' | 'outlined' | 'contained' | 'contained' | 'fab' | 'extendedFab';
t: any;
}
......@@ -123,7 +123,7 @@ class Permissions extends React.Component<IPermissionsProps, any> {
}
public render() {
const {autocomplete, variant = 'flat', t } = this.props;
const {autocomplete, variant = 'text', t } = this.props;
const {aclObjectIdentity, autocompleteObj} = this.state;
return (
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment