Commit c8310d25 authored by Maxym Borodenko's avatar Maxym Borodenko
Browse files

UX/UI for Descriptors and Descriptor Lists

parent b74376e0
......@@ -12365,9 +12365,9 @@
}
},
"react": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz",
"integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==",
"version": "16.3.2",
"resolved": "https://registry.npmjs.org/react/-/react-16.3.2.tgz",
"integrity": "sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
......
......@@ -116,8 +116,6 @@ export const saveDescriptorList = (descriptorList: DescriptorList) => (dispatch,
.then((descriptorList) => {
// receive the updated descriptor list
dispatch(receiveDescriptorList(descriptorList));
// and redirect to proper edit page
return dispatch(push(`/descriptorlists/${descriptorList.uuid}/edit`));
}).catch((error) => {
log(`Error saving descriptor list`, error, descriptorList);
});
......@@ -129,8 +127,6 @@ export const deleteDescriptorList = (descriptorList: DescriptorList) => (dispatc
.then((descriptorList) => {
// receive updates
dispatch(receiveDescriptorList(descriptorList));
// go to the published descriptor list page
dispatch(push(`/descriptorlist`));
});
};
......
......@@ -14,7 +14,8 @@ import {log} from 'utilities/debug';
interface IDescriptorPickerProps extends React.ClassAttributes<any> {
classes: any;
router: any;
history: any;
location: any;
loadDescriptors: (page?: number, results?: number, sortBy?: string, filter?: IDescriptorFilter, order?: string) => void;
matchingDescriptors: Page<Descriptor>; // results from loadDescriptors
onAddDescriptor: (descriptor: Descriptor) => void; // event handler
......@@ -74,14 +75,17 @@ class DescriptorPicker extends React.Component<IDescriptorPickerProps, any> {
}
protected onPaginationChange = (page, results, sortBy, dir) => {
log('onPaginationChange', page, results, sortBy);
const {router, router: { location }} = this.props;
location.query.p = page;
location.query.l = results;
location.query.s = sortBy;
location.query.d = dir;
router.push(location);
const { history, location } = this.props;
const params = new URLSearchParams();
params.append('p', page);
params.append('l', results);
params.append('s', sortBy);
if (dir) {
params.append('d', dir);
}
location.search = params.toString();
history.push(location);
}
protected applyFilters = (newFilters) => {
......
import * as React from 'react';
// import {connect} from 'react-redux';
// import {bindActionCreators} from 'redux';
import {log} from 'utilities/debug';
......@@ -8,13 +6,10 @@ import { CSV, ICsvConfiguration } from 'utilities/CSV';
import { Descriptor } from 'model/descriptor.model';
import { VocabularyTerm } from 'model/vocabulary.model';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import CSVConfiguration from 'ui/common/csv-configuration/CSVConfiguration';
import DescriptorCard from 'ui/catalog/descriptor/DescriptorCard';
import Grid from 'material-ui/Grid';
import Paper from 'material-ui/Paper';
import Button from 'material-ui/Button';
import FormControl from 'material-ui/Form/FormControl';
import Input from 'material-ui/Input';
import InputLabel from 'material-ui/Input/InputLabel';
......@@ -22,7 +17,7 @@ import InputLabel from 'material-ui/Input/InputLabel';
interface IDescriptorUpload extends React.ClassAttributes<any> {
className?: any;
onImport: (descriptors: Descriptor[]) => any;
onImportDescriptors: any;
}
// Page to edit a descriptor list
......@@ -133,73 +128,48 @@ class DescriptorUpload extends React.Component<IDescriptorUpload, any> {
}).on('end', () => {
log('All CSV parsed');
this.setState({ ...this.state, uploader: true, uploadedDescriptors: newDescriptors });
// this.props.onAccessionsUpdated(newIdentifiers);
this.props.onImportDescriptors(newDescriptors);
});
}
public clickImport = (e) => {
// log('Sending', this.state.uploadedDescriptors);
if (this.state.uploadedDescriptors && this.state.uploadedDescriptors.length > 0) {
this.props.onImport(this.state.uploadedDescriptors);
} else {
log('Nothing to import');
}
}
public render() {
return (
<div>
<Paper className={ `${this.props.className} p-20 mb-20` }>
<div className="p-20 even-row">
<h4>INSTRUCTIONS FOR USE</h4>
<ul>
<li>Use "Descriptors" sheet from template: <a href="/templates/genesys-catalog-template.xlsx" target="_blank">Genesys Catalog template</a>.</li>
<li>Do not change header names in the template!</li>
<li>Fill the template with the descriptor information, save it.</li>
<li>Copy and paste the table from Excel into the text field below.</li>
</ul>
</div>
<CSVConfiguration onChange={ this.onUpdateCsvConfig } config={ this.state.csvConfig } />
<FormControl fullWidth>
<InputLabel>Descriptor definitions</InputLabel>
<Input onPaste={ this.dataPasted } placeholder="Paste descriptor data here (tab separated)" onBlur={ this.textBlurred }/>
</FormControl>
</Paper>
{ this.state.uploadedDescriptors ? (
<div>
<ContentHeaderWithButton title={ `Uploaded ${this.state.uploadedDescriptors.length} descriptor definitions` } buttons={
<Button raised onClick={ this.clickImport }>Import descriptors</Button>
} />
<Grid container spacing={ 24 }>
{ this.state.uploadedDescriptors.map((d, index) => (
<Grid item key={ index } xs={ 12 } md={ 6 } xl={ 4 }>
<DescriptorCard descriptor={ d } />
</Grid>
)) }
</Grid>
</div>
) : (
<ContentHeaderWithButton title="No descriptors uploaded" />
) }
</div>
<div className={ `${this.props.className} m-20 p-20 even-row` }>
<div>
<h4>INSTRUCTIONS FOR USE</h4>
<ul>
<li>Use "Descriptors" sheet from template: <a href="/templates/genesys-catalog-template.xlsx" target="_blank">Genesys Catalog template</a>.</li>
<li>Do not change header names in the template!</li>
<li>Fill the template with the descriptor information, save it.</li>
<li>Copy and paste the table from Excel into the text field below.</li>
</ul>
</div>
<CSVConfiguration onChange={ this.onUpdateCsvConfig } config={ this.state.csvConfig } />
<FormControl fullWidth>
<InputLabel>Descriptor definitions</InputLabel>
<Input onPaste={ this.dataPasted } placeholder="Paste descriptor data here (tab separated)" onBlur={ this.textBlurred }/>
</FormControl>
{ this.state.uploadedDescriptors ? (
<div style={ { 'margin-top': '23px' } }>
<h3>{ `Uploaded ${this.state.uploadedDescriptors.length} descriptor definitions` }</h3>
<Grid container spacing={ 24 }>
{ this.state.uploadedDescriptors.map((d, index) => (
<Grid item key={ index } xs={ 12 } md={ 6 } xl={ 4 }>
<DescriptorCard descriptor={ d } />
</Grid>
)) }
</Grid>
</div>
) : (
<h3>No descriptors uploaded</h3>
) }
</div>
);
}
}
//
// const mapStateToProps = (state, ownProps) => ({
// descriptorList: ownProps.descriptorList,
// });
//
// const mapDispatchToProps = (dispatch) => bindActionCreators({
// addDescriptorToDescriptorList,
// removeDescriptorFromDescriptorList,
// }, dispatch);
//
// export default connect(mapStateToProps, mapDispatchToProps)(DescriptorListEditPage);
export default DescriptorUpload;
import * as React from 'react';
import Paper from 'material-ui/Paper';
import Button from 'material-ui/Button';
import List from 'material-ui/List';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
......@@ -11,8 +9,7 @@ import Card from './Card';
interface IDescriptorOrderProps extends React.ClassAttributes<any> {
descriptors: Descriptor[];
onDone: (descriptors: Descriptor[]) => void;
onCancel?: () => void;
onRef?: any;
}
class DescriptorOrder extends React.PureComponent<IDescriptorOrderProps, any> {
......@@ -27,16 +24,17 @@ class DescriptorOrder extends React.PureComponent<IDescriptorOrderProps, any> {
};
}
public componentDidMount() {
const { onRef } = this.props;
onRef(this);
}
public componentWillReceiveProps(nextProps) {
const { descriptors = [] } = nextProps;
this.setState(() => ({ descriptors: [ ...descriptors ] }));
}
protected saveOrder = () => {
this.props.onDone(this.state.descriptors);
}
public moveCard = (dragIndex: number, hoverIndex: number) => {
const { descriptors } = this.state;
const dragCard = descriptors[dragIndex];
......@@ -51,10 +49,9 @@ class DescriptorOrder extends React.PureComponent<IDescriptorOrderProps, any> {
}
public render() {
const {onCancel} = this.props;
const {descriptors} = this.state;
return (
<Paper className="p-20">
<div className="p-20">
<List>
{ descriptors.map((descriptor, i) => (
<Card
......@@ -66,9 +63,7 @@ class DescriptorOrder extends React.PureComponent<IDescriptorOrderProps, any> {
/>
)) }
</List>
<Button raised onClick={ this.saveOrder }>Save order</Button>
<Button onClick={ onCancel }>Cancel</Button>
</Paper>
</div>
);
}
}
......
......@@ -86,8 +86,8 @@ class StepNavigation extends React.Component<IStepNavigationProps, any> {
</h3>
<div className={ classes.flexGrow }/>
{ this.state.id === 1 && (
<Button onClick={ onDelete } className={ classes.btnDelete }>
Delete dataset
<Button disabled={ disabled } onClick={ onDelete } className={ classes.btnDelete }>
Delete data
</Button>
)
}
......
......@@ -39,7 +39,7 @@ const styles = (theme) => ({
},
});
const TopSection = ({classes, pageTitle}) => (
const TopSection = ({classes, pageTitle, subTitle}) => (
<Grid container spacing={ 0 } className={ classes.root }>
<Grid item xs={ 12 } className={ classes.header }>
<h1 className="white mb-5 font-bold" style={ { marginBottom: 0, fontSize: '1.714rem'} }>
......@@ -47,13 +47,13 @@ const TopSection = ({classes, pageTitle}) => (
</h1>
<Hidden implementation="css" smDown>
<h3 className="font-medium white m-0" style={ { marginTop: '.5rem', fontSize: '0.8571rem' } }>
Publish your datasets
{ subTitle }
</h3>
</Hidden>
</Grid>
<Grid item xs={ 12 } className={ classes.subHeader }>
<h4 className="font-bold m-0 lh-35">
Dataset publication
Data publication
</h4>
<div className={ classes.flexGrow }/>
<div className={ classes.guide }>
......
......@@ -6,14 +6,14 @@ import Grid from 'material-ui/Grid';
import { log } from 'utilities/debug';
import confirm from 'utilities/confirmAlert';
import ProgressMenu from './progress-menu';
import TopSection from './TopSection';
import BottomSection from './BottomSection';
import ProgressMenu from 'ui/common/stepper/progress-menu';
import TopSection from 'ui/common/stepper/TopSection';
import BottomSection from 'ui/common/stepper/BottomSection';
import StepNavigation from 'ui/common/stepper/StepNavigation';
import { navigateTo } from 'actions/navigation';
import { setPageTitle } from 'actions/pageTitle';
import { loadDataset, publishDataset, deleteDataset } from 'actions/dataset';
import { Dataset } from 'model/dataset.model';
import StepNavigation from './StepNavigation';
import Loading from 'ui/common/Loading';
import renderRoutes from 'ui/renderRoutes';
......@@ -148,7 +148,7 @@ class DatasetStepper extends React.Component<IDatasetProps, any> {
return (
<Grid container spacing={ 0 }>
<TopSection pageTitle={ pageTitle } />
<TopSection pageTitle={ pageTitle } subTitle="Publish your datasets" />
<Grid item xs={ 12 } md={ 9 } xl={ 10 } className="back-gray p-20">
<Grid container spacing={ 0 } className="back-white">
<StepNavigation disabled={ !(dataset && dataset.uuid) } onGotoStep={ this.gotoStep } onDelete={ this.onDelete } steps={ steps } location={ location } showStepName bottomDivider onPublish={ this.onPublish } />
......
......@@ -42,7 +42,7 @@ class Traits extends React.Component<any, any> {
}
public render() {
const { matchingDescriptors, loadDescriptors, dataset, router, listCrops, pagination} = this.props;
const { matchingDescriptors, loadDescriptors, dataset, history, location, listCrops, pagination} = this.props;
return (
<DescriptorPicker
......@@ -51,7 +51,8 @@ class Traits extends React.Component<any, any> {
currentDescriptors={ dataset.descriptors }
onAddDescriptor={ this.addDescriptor }
onRemoveDescriptor={ this.removeDescriptor }
router={ router }
history={ history }
location={ location }
pagination={ pagination }
listCrops={ listCrops }
classes={ {} }
......
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { withStyles } from 'material-ui/styles';
import {log} from 'utilities/debug';
import {
loadDescriptorList,
publishDescriptorList,
......@@ -13,25 +11,12 @@ import {
setDescriptorsToDescriptorList,
} from 'actions/descriptorList';
import { copyDescriptor } from 'actions/descriptors';
import { DescriptorList, Descriptor } from 'model/descriptor.model';
import confirm from 'utilities/confirmAlert';
import Authorize from 'ui/common/authorized/Authorize';
import { DescriptorList} from 'model/descriptor.model';
import Loading from 'ui/common/Loading';
import Markdown from 'ui/catalog/markdown';
import DescriptorCard from 'ui/catalog/descriptor/DescriptorCard';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import {PartnerLink, DescriptorListLink, CropLink, DescriptorLink, ExternalLink} from 'ui/catalog/Links';
import { Properties, PropertiesItem } from 'ui/catalog/Properties';
import BackButton from 'ui/common/buttons/BackButton';
import { ScrollToTopOnMount } from 'ui/common/page/scrollers';
import Permissions from 'ui/common/permission/Permissions';
import Grid from 'material-ui/Grid';
import Card, {CardHeader, CardContent, CardActions } from 'ui/common/Card';
import Section from 'ui/common/layout/Section';
import Divider from 'material-ui/Divider';
import Button from 'material-ui/Button';
import DescriptorListDisplay from './c/DescriptorListDisplay';
interface IDescriptorListPageProps extends React.ClassAttributes<any> {
classes: any;
......@@ -46,225 +31,49 @@ interface IDescriptorListPageProps extends React.ClassAttributes<any> {
}
const styles = (theme) => ({
filterSection: theme.leftPanel.root,
contentContainer: {
backgroundColor: '#E8E5E0',
padding: '1.5rem',
},
card: {
marginBottom: '1.5rem',
},
propertiesContainer: {
marginTop: '20px',
marginBottom: '20px',
},
propertiesRow: {
/* tslint:disable */
'marginTop': '1px',
'marginBottom': '1px',
'& > *:first-child': {
borderRight: 'solid 1px white',
},
'&:nth-child(even)': {
backgroundColor: '#f8f7f5',
},
'&:nth-child(odd)': {
backgroundColor: '#f3f2ee',
},
/* tslint:enable */
},
contentContainer: {
backgroundColor: '#E8E5E0',
padding: '1.5rem',
},
});
// Page to edit a descriptor list
class DescriptorListPage extends React.Component<IDescriptorListPageProps, any> {
protected static needs = [
({ params: { uuid } }) => loadDescriptorList(uuid),
];
public componentWillMount() {
const {uuid, loading, loadDescriptorList} = this.props;
if (uuid && (! loading || loading.uuid !== uuid)) {
loadDescriptorList(uuid);
}
}
private onPublish = (e) => {
const {descriptorList, publishDescriptorList} = this.props;
confirm(<span>Publish <b>{ descriptorList.title }</b>?</span>, {
description: `After publishing the descriptor list no changes are permitted, a new version must be created.`,
confirmLabel: 'Publish',
abortLabel: 'Cancel',
}).then(() => {
log('Publishing descriptor list', descriptorList);
publishDescriptorList(descriptorList);
}).catch(() => {
// don't
});
}
private onUnpublish = (e) => {
const {descriptorList, publishDescriptorList} = this.props;
confirm(<span>Unpublish <b>{ descriptorList.title }</b>?</span>, {
// description: `Deleting the descriptor is only possible when there is no associated data.`,
confirmLabel: 'Unpublish',
abortLabel: 'Cancel',
}).then(() => {
log('Publishing descriptor list', descriptorList);
publishDescriptorList(descriptorList, false);
}).catch(() => {
// don't
});
}
private onDelete = (e) => {
const {descriptorList, deleteDescriptorList} = this.props;
confirm(<span>Delete <b>{ descriptorList.title }</b>?</span>, {
description: `Deleting the descriptor is only possible when there is no associated data.`,
confirmLabel: 'Delete',
abortLabel: 'Cancel',
}).then(() => {
deleteDescriptorList(descriptorList);
}).catch(() => {
// don't
});
}
private createNewDescriptorVersion = (descriptor: Descriptor) => async () => {
const {descriptorList, copyDescriptor, setDescriptorsToDescriptorList} = this.props;
try {
await confirm(<span>Create a new version of the descriptor <b>{ descriptor.title }</b>?</span>, {
description: `Creating the new descriptor version will replace current descriptor from the descriptor list.`,
confirmLabel: 'Create new version',
abortLabel: 'Cancel',
});
protected static needs = [
({ params: { uuid } }) => loadDescriptorList(uuid),
];
const copy = new Descriptor({
...descriptor,
owner: descriptorList.owner,
});
public componentWillMount() {
const {uuid, loading, loadDescriptorList} = this.props;
const newDescriptor = await copyDescriptor(copy);
const descriptorsUuid = descriptorList.descriptors.map((d) => d.uuid === descriptor.uuid ? newDescriptor.uuid : d.uuid);
await setDescriptorsToDescriptorList(descriptorList, descriptorsUuid);
} catch (e) {
// Cancel
}
if (uuid && (! loading || loading.uuid !== uuid)) {
loadDescriptorList(uuid);
}
private getDescriptorActionButton = (descriptor: Descriptor) => {
const {descriptorList} = this.props;
if (!descriptorList.published && descriptorList._permissions.write) {
if (descriptor.published) {
return <Button onClick={ this.createNewDescriptorVersion(descriptor) } type="button">Create new version</Button>;
}
public render() {
const {classes, uuid, descriptorList, publishDescriptorList, deleteDescriptorList, setDescriptorsToDescriptorList, copyDescriptor} = this.props;
const stillLoading: boolean = (! descriptorList || (uuid && (descriptorList.uuid !== uuid)));
return (
<div>
<ScrollToTopOnMount />
<ContentHeaderWithButton title="Descriptor list details" buttons={ <BackButton defaultTarget="/descriptorlists" /> } />
{ stillLoading ? <Loading className={ classes.contentContainer } /> :
<span>
<DescriptorListDisplay
descriptorList={ descriptorList }
publishDescriptorList={ publishDescriptorList }
deleteDescriptorList={ deleteDescriptorList }
setDescriptorsToDescriptorList={ setDescriptorsToDescriptorList }
copyDescriptor={ copyDescriptor }
/>
</span>
}
return (
<DescriptorLink to={ descriptor } edit>
<Button>Edit</Button>
</DescriptorLink>
);
}
return null;
}
public render() {
const {classes, uuid, descriptorList } = this.props;
const stillLoading: boolean = (! descriptorList || (uuid && (descriptorList.uuid !== uuid)));
return (
<div>
<ScrollToTopOnMount />
<ContentHeaderWithButton title="Descriptor list details" buttons={ <BackButton defaultTarget="/descriptorlists" /> } />
{ stillLoading ? <Loading className={ classes.contentContainer } /> :
<span>
<Grid container className={ classes.contentContainer }>
<Grid item xs={ 12 }>
<Card className={ classes.card } square>
<CardHeader className={ classes.cardHeader } title={ (
<span><Markdown basic source={ descriptorList.title } /> <small>{ descriptorList.versionTag }</small></span>
) } />
<Divider />