mirror of
https://github.com/ascribe/onion.git
synced 2025-02-14 21:10:27 +01:00
Merged in AD-894-group-collection-by-status (pull request #61)
Ad 894 group collection by status
This commit is contained in:
commit
05fe507a3f
@ -21,7 +21,7 @@ let AccordionList = React.createClass({
|
|||||||
);
|
);
|
||||||
} else if(this.props.count === 0) {
|
} else if(this.props.count === 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="ascribe-accordion-list-placeholder">
|
||||||
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
||||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,20 @@ let PieceListToolbar = React.createClass({
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
searchFor: React.PropTypes.func,
|
searchFor: React.PropTypes.func,
|
||||||
filterParams: React.PropTypes.array,
|
filterParams: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.shape({
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
items: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.shape({
|
||||||
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
filterBy: React.PropTypes.object,
|
filterBy: React.PropTypes.object,
|
||||||
applyFilterBy: React.PropTypes.func,
|
applyFilterBy: React.PropTypes.func,
|
||||||
orderParams: React.PropTypes.array,
|
orderParams: React.PropTypes.array,
|
||||||
|
@ -3,20 +3,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
|
||||||
|
|
||||||
import { getLangText } from '../../utils/lang_utils.js';
|
import { getLangText } from '../../utils/lang_utils.js';
|
||||||
|
|
||||||
|
|
||||||
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
let PieceListToolbarFilterWidgetFilter = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// An array of either strings (which represent acl enums) or objects of the form
|
filterParams: React.PropTypes.arrayOf(
|
||||||
//
|
React.PropTypes.shape({
|
||||||
// {
|
label: React.PropTypes.string,
|
||||||
// key: <acl enum>,
|
items: React.PropTypes.arrayOf(
|
||||||
// label: <a human readable string>
|
React.PropTypes.oneOfType([
|
||||||
// }
|
React.PropTypes.string,
|
||||||
//
|
React.PropTypes.shape({
|
||||||
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired,
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
filterBy: React.PropTypes.object,
|
filterBy: React.PropTypes.object,
|
||||||
applyFilterBy: React.PropTypes.func
|
applyFilterBy: React.PropTypes.func
|
||||||
},
|
},
|
||||||
@ -79,23 +85,38 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
|||||||
<DropdownButton
|
<DropdownButton
|
||||||
title={filterIcon}
|
title={filterIcon}
|
||||||
className="ascribe-piece-list-toolbar-filter-widget">
|
className="ascribe-piece-list-toolbar-filter-widget">
|
||||||
<li style={{'textAlign': 'center'}}>
|
{/* We iterate over filterParams, to receive the label and then for each
|
||||||
<em>{getLangText('Show works I can')}:</em>
|
label also iterate over its items, to get all filterable options */}
|
||||||
|
{this.props.filterParams.map(({ label, items }, i) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<li
|
||||||
|
style={{'textAlign': 'center'}}
|
||||||
|
key={i}>
|
||||||
|
<em>{label}:</em>
|
||||||
</li>
|
</li>
|
||||||
{this.props.filterParams.map((param, i) => {
|
{items.map((param, j) => {
|
||||||
let label;
|
|
||||||
|
|
||||||
|
// As can be seen in the PropTypes, a param can either
|
||||||
|
// be a string or an object of the shape:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// key: <String>,
|
||||||
|
// label: <String>
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is why we need to distinguish between both here.
|
||||||
if(typeof param !== 'string') {
|
if(typeof param !== 'string') {
|
||||||
label = param.label;
|
label = param.label;
|
||||||
param = param.key;
|
param = param.key;
|
||||||
} else {
|
} else {
|
||||||
param = param;
|
param = param;
|
||||||
label = param.split('_')[1];
|
label = param.split('acl_')[1].replace(/_/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<li
|
||||||
key={i}
|
key={j}
|
||||||
onClick={this.filterBy(param)}
|
onClick={this.filterBy(param)}
|
||||||
className="filter-widget-item">
|
className="filter-widget-item">
|
||||||
<div className="checkbox-line">
|
<div className="checkbox-line">
|
||||||
@ -107,7 +128,10 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={this.props.filterBy[param]} />
|
checked={this.props.filterBy[param]} />
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
@ -15,6 +15,8 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l
|
|||||||
|
|
||||||
import Pagination from './ascribe_pagination/pagination';
|
import Pagination from './ascribe_pagination/pagination';
|
||||||
|
|
||||||
|
import PieceListFilterDisplay from './piece_list_filter_display';
|
||||||
|
|
||||||
import GlobalAction from './global_action';
|
import GlobalAction from './global_action';
|
||||||
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
|
||||||
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
||||||
@ -22,6 +24,8 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
|
|||||||
import AppConstants from '../constants/application_constants';
|
import AppConstants from '../constants/application_constants';
|
||||||
|
|
||||||
import { mergeOptions } from '../utils/general_utils';
|
import { mergeOptions } from '../utils/general_utils';
|
||||||
|
import { getLangText } from '../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let PieceList = React.createClass({
|
let PieceList = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -31,7 +35,6 @@ let PieceList = React.createClass({
|
|||||||
filterParams: React.PropTypes.array,
|
filterParams: React.PropTypes.array,
|
||||||
orderParams: React.PropTypes.array,
|
orderParams: React.PropTypes.array,
|
||||||
orderBy: React.PropTypes.string
|
orderBy: React.PropTypes.string
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [Router.Navigation, Router.State],
|
mixins: [Router.Navigation, Router.State],
|
||||||
@ -40,12 +43,13 @@ let PieceList = React.createClass({
|
|||||||
return {
|
return {
|
||||||
accordionListItemType: AccordionListItemWallet,
|
accordionListItemType: AccordionListItemWallet,
|
||||||
orderParams: ['artist_name', 'title'],
|
orderParams: ['artist_name', 'title'],
|
||||||
filterParams: [
|
filterParams: [{
|
||||||
|
label: getLangText('Show works I can'),
|
||||||
|
items: [
|
||||||
'acl_transfer',
|
'acl_transfer',
|
||||||
'acl_consign',
|
'acl_consign',
|
||||||
{
|
'acl_create_editions'
|
||||||
key: 'acl_create_editions',
|
]
|
||||||
label: 'create editions'
|
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -149,9 +153,6 @@ let PieceList = React.createClass({
|
|||||||
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
||||||
let AccordionListItemType = this.props.accordionListItemType;
|
let AccordionListItemType = this.props.accordionListItemType;
|
||||||
|
|
||||||
//<GlobalAction requestActions={this.state.requestActions} />
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PieceListToolbar
|
<PieceListToolbar
|
||||||
@ -166,6 +167,9 @@ let PieceList = React.createClass({
|
|||||||
{this.props.customSubmitButton}
|
{this.props.customSubmitButton}
|
||||||
</PieceListToolbar>
|
</PieceListToolbar>
|
||||||
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
|
||||||
|
<PieceListFilterDisplay
|
||||||
|
filterBy={this.state.filterBy}
|
||||||
|
filterParams={this.props.filterParams}/>
|
||||||
<AccordionList
|
<AccordionList
|
||||||
className="ascribe-accordion-list"
|
className="ascribe-accordion-list"
|
||||||
changeOrder={this.accordionChangeOrder}
|
changeOrder={this.accordionChangeOrder}
|
||||||
|
118
js/components/piece_list_filter_display.js
Normal file
118
js/components/piece_list_filter_display.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
|
let PieceListFilterDisplay = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
filterBy: React.PropTypes.object,
|
||||||
|
filterParams: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.shape({
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
items: React.PropTypes.arrayOf(
|
||||||
|
React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.shape({
|
||||||
|
key: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the above described filterParams prop,
|
||||||
|
* assigns it it's true filterBy value that is derived from the filterBy prop
|
||||||
|
* and also - if there wasn't already one defined - generates a label
|
||||||
|
* @return {object}
|
||||||
|
*/
|
||||||
|
transformFilterParamsItemsToBools() {
|
||||||
|
let { filterParams, filterBy } = this.props;
|
||||||
|
|
||||||
|
return filterParams.map((filterParam) => {
|
||||||
|
return {
|
||||||
|
label: filterParam.label,
|
||||||
|
items: filterParam.items.map((item) => {
|
||||||
|
if(typeof item !== 'string' && typeof item.key === 'string' && typeof item.label === 'string') {
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
value: filterBy[item.key] || false
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
key: item,
|
||||||
|
label: item.split('acl_')[1].replace(/_/g, ' '),
|
||||||
|
value: filterBy[item] || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the list of filters generated in transformFilterParamsItemsToBools and
|
||||||
|
* transforms them into human readable text.
|
||||||
|
* @param {Object} filtersWithLabel An object of the shape {key: <String>, label: <String>, value: <Bool>}
|
||||||
|
* @return {string} A human readable string
|
||||||
|
*/
|
||||||
|
getFilterText(filtersWithLabel) {
|
||||||
|
let filterTextList = filtersWithLabel
|
||||||
|
// Iterate over all provided filterLabels and generate a list
|
||||||
|
// of human readable strings
|
||||||
|
.map((filterWithLabel) => {
|
||||||
|
let activeFilterWithLabel = filterWithLabel
|
||||||
|
.items
|
||||||
|
// If the filter is active (which it is when its value is true),
|
||||||
|
// we're going to include it's label into a list,
|
||||||
|
// otherwise we'll just return nothing
|
||||||
|
.map((filter) => {
|
||||||
|
if(filter.value) {
|
||||||
|
return filter.label;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// if nothing is returned, that index is 'undefined'.
|
||||||
|
// As we only want active filter, we filter out all falsy values e.g. undefined
|
||||||
|
.filter((filterName) => !!filterName)
|
||||||
|
// and join the result to a string
|
||||||
|
.join(', ');
|
||||||
|
|
||||||
|
// If this actually didn't generate an empty string,
|
||||||
|
// we take the label and concat it to the result.
|
||||||
|
if(activeFilterWithLabel) {
|
||||||
|
return filterWithLabel.label + ': ' + activeFilterWithLabel;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// filter out strings that are undefined, as their filter's were not activated
|
||||||
|
.filter((filterText) => !!filterText)
|
||||||
|
// if there are multiple sentences, capitalize the first one and lowercase the others
|
||||||
|
.map((filterText, i) => i === 0 ? filterText.charAt(0).toUpperCase() + filterText.substr(1) : filterText.charAt(0).toLowerCase() + filterText.substr(1))
|
||||||
|
.join(' and ');
|
||||||
|
|
||||||
|
return filterTextList;
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { filterBy } = this.props;
|
||||||
|
let filtersWithLabel = this.transformFilterParamsItemsToBools();
|
||||||
|
|
||||||
|
// do not show the FilterDisplay if there are no filters applied
|
||||||
|
if(filterBy && Object.keys(filterBy).length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="ascribe-piece-list-filter-display col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
|
||||||
|
{this.getFilterText(filtersWithLabel)}
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PieceListFilterDisplay;
|
@ -8,6 +8,8 @@ import UserStore from '../../../../../stores/user_store';
|
|||||||
|
|
||||||
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
|
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
|
||||||
|
|
||||||
|
import { getLangText } from '../../../../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let CylandPieceList = React.createClass({
|
let CylandPieceList = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
@ -33,6 +35,13 @@ let CylandPieceList = React.createClass({
|
|||||||
<PieceList
|
<PieceList
|
||||||
redirectTo="register_piece"
|
redirectTo="register_piece"
|
||||||
accordionListItemType={CylandAccordionListItem}
|
accordionListItemType={CylandAccordionListItem}
|
||||||
|
filterParams={[{
|
||||||
|
label: getLangText('Show works I have'),
|
||||||
|
items: [{
|
||||||
|
key: 'acl_loaned',
|
||||||
|
label: getLangText('loaned to Cyland')
|
||||||
|
}]
|
||||||
|
}]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,9 @@ import UserStore from '../../../../../stores/user_store';
|
|||||||
|
|
||||||
import IkonotvAccordionListItem from './ascribe_accordion_list/ikonotv_accordion_list_item';
|
import IkonotvAccordionListItem from './ascribe_accordion_list/ikonotv_accordion_list_item';
|
||||||
|
|
||||||
|
import { getLangText } from '../../../../../utils/lang_utils';
|
||||||
|
|
||||||
|
|
||||||
let IkonotvPieceList = React.createClass({
|
let IkonotvPieceList = React.createClass({
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return UserStore.getState();
|
return UserStore.getState();
|
||||||
@ -32,7 +35,13 @@ let IkonotvPieceList = React.createClass({
|
|||||||
<PieceList
|
<PieceList
|
||||||
redirectTo="register_piece"
|
redirectTo="register_piece"
|
||||||
accordionListItemType={IkonotvAccordionListItem}
|
accordionListItemType={IkonotvAccordionListItem}
|
||||||
/>
|
filterParams={[{
|
||||||
|
label: getLangText('Show works I have'),
|
||||||
|
items: [{
|
||||||
|
key: 'acl_loaned',
|
||||||
|
label: getLangText('loaned to IkonoTV')
|
||||||
|
}]
|
||||||
|
}]}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ $ascribe-accordion-list-item-height: 8em;
|
|||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ascribe-accordion-list-placeholder {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.ascribe-accordion-list-item {
|
.ascribe-accordion-list-item {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
@ -54,8 +54,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-widget-item {
|
.filter-widget-item {
|
||||||
|
a {
|
||||||
> a {
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
@ -64,6 +63,13 @@
|
|||||||
.checkbox-line {
|
.checkbox-line {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
color: #333333;
|
||||||
|
|
||||||
|
/* Fuck you react-bootstrap */
|
||||||
|
&:hover {
|
||||||
|
background-color: $dropdown-link-hover-bg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -487,3 +487,23 @@ hr {
|
|||||||
.ascribe-progress-bar-xs {
|
.ascribe-progress-bar-xs {
|
||||||
height: 12px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ascribe-piece-list-filter-display {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #616161;
|
||||||
|
|
||||||
|
padding-left: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
margin-top: .15em;
|
||||||
|
margin-bottom: .1em;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user