1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-28 00:28:00 +02:00

Merge remote-tracking branches 'remotes/origin/AD-391-add-filter-functions-to-pieces-an', 'remotes/origin/AD-589-Form-Cancel-doesnt-reset' and 'remotes/origin/AD-619-due-1208-create-jury-dashboard' into AD-686-in-sluice-art-prize-i-can-submit-

This commit is contained in:
diminator 2015-08-05 14:52:18 +02:00
commit 36356dd5c5
22 changed files with 263 additions and 207 deletions

View File

@ -17,21 +17,21 @@ class EditionListActions {
);
}
fetchEditionList(pieceId, page, pageSize, orderBy, orderAsc) {
if(!orderBy && typeof orderAsc === 'undefined') {
fetchEditionList(pieceId, page, pageSize, orderBy, orderAsc, filterBy) {
if((!orderBy && typeof orderAsc === 'undefined') || !orderAsc) {
orderBy = 'edition_number';
orderAsc = true;
}
// Taken from: http://stackoverflow.com/a/519157/1263876
if(typeof page === 'undefined' && typeof pageSize === 'undefined') {
if((typeof page === 'undefined' || !page) && (typeof pageSize === 'undefined' || !pageSize)) {
page = 1;
pageSize = 10;
}
return Q.Promise((resolve, reject) => {
EditionListFetcher
.fetch(pieceId, page, pageSize, orderBy, orderAsc)
.fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy)
.then((res) => {
this.actions.updateEditionList({
pieceId,
@ -39,6 +39,7 @@ class EditionListActions {
pageSize,
orderBy,
orderAsc,
filterBy,
'editionListOfPiece': res.editions,
'count': res.count
});

View File

@ -14,7 +14,7 @@ class PieceListActions {
);
}
fetchPieceList(page, pageSize, search, orderBy, orderAsc) {
fetchPieceList(page, pageSize, search, orderBy, orderAsc, filterBy) {
// To prevent flickering on a pagination request,
// we overwrite the piecelist with an empty list before
// pieceListCount === -1 defines the loading state
@ -24,6 +24,7 @@ class PieceListActions {
search,
orderBy,
orderAsc,
filterBy,
'pieceList': [],
'pieceListCount': -1
});
@ -32,7 +33,7 @@ class PieceListActions {
return Q.Promise((resolve, reject) => {
PieceListFetcher
.fetch(page, pageSize, search, orderBy, orderAsc)
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
.then((res) => {
this.actions.updatePieceList({
page,
@ -40,6 +41,7 @@ class PieceListActions {
search,
orderBy,
orderAsc,
filterBy,
'pieceList': res.pieces,
'pieceListCount': res.count
});

View File

@ -87,14 +87,16 @@ let AccordionListItem = React.createClass({
},
handleSubmitPrizeSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
onPollingSuccess(pieceId) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
EditionListActions.toggleEditionList(pieceId);
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);

View File

@ -6,10 +6,14 @@ import classNames from 'classnames';
import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store';
import PieceListActions from '../../actions/piece_list_actions';
import PieceListStore from '../../stores/piece_list_store';
import Button from 'react-bootstrap/lib/Button';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { mergeOptions } from '../../utils/general_utils';
import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({
@ -21,15 +25,20 @@ let AccordionListItemEditionWidget = React.createClass({
},
getInitialState() {
return EditionListStore.getState();
return mergeOptions(
EditionListStore.getState(),
PieceListStore.getState()
);
},
componentDidMount() {
EditionListStore.listen(this.onChange);
PieceListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
PieceListStore.unlisten(this.onChange);
},
onChange(state) {
@ -47,7 +56,7 @@ let AccordionListItemEditionWidget = React.createClass({
EditionListActions.toggleEditionList(pieceId);
} else {
EditionListActions.toggleEditionList(pieceId);
EditionListActions.fetchEditionList(pieceId);
EditionListActions.fetchEditionList(pieceId, null, null, null, null, this.state.filterBy);
}
},

View File

@ -76,13 +76,9 @@ let AccordionListItemTableEditions = React.createClass({
});
let editionList = this.state.editionList[this.props.parentId];
EditionListActions.fetchEditionList(this.props.parentId, editionList.page + 1, editionList.pageSize);
EditionListActions.fetchEditionList(this.props.parentId, editionList.page + 1, editionList.pageSize,
editionList.orderBy, editionList.orderAsc, editionList.filterBy);
},
changeEditionListOrder(orderBy, orderAsc) {
EditionListActions.fetchEditionList(this.props.parentId, orderBy, orderAsc);
},
render() {
let selectedEditionsCount = 0;
let allEditionsCount = 0;

View File

@ -44,7 +44,9 @@ let CreateEditionsButton = React.createClass({
startPolling() {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.piece.id)
let editionsForPiece = this.state.editionList[this.props.piece.id];
EditionListActions.fetchEditionList(this.props.piece.id, null, null, null, null, editionsForPiece.filterBy)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);

View File

@ -86,7 +86,8 @@ let Edition = React.createClass({
},
handleDeleteSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
EditionListActions.refreshEditionList(this.props.edition.parent);
EditionListActions.closeAllEditionLists();

View File

@ -79,12 +79,14 @@ let Piece = React.createClass({
handleEditionCreationSuccess() {
PieceActions.updateProperty({key: 'num_editions', value: 0});
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.toggleCreateEditionsDialog();
},
handleDeleteSuccess(response) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
// since we're deleting a piece, we just need to close
// all editions dialogs and not reload them
@ -124,7 +126,8 @@ let Piece = React.createClass({
// btw.: It's not sufficient to just set num_editions to numEditions, since a single accordion
// list item also uses the firstEdition property which we can only get from the server in that case.
// Therefore we need to at least refetch the changed piece from the server or on our case simply all
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
let notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);

View File

@ -70,23 +70,22 @@ let Property = React.createClass({
});
}
if(!this.state.initialValue) {
if(!this.state.initialValue && childInput.props.defaultValue) {
this.setState({
initialValue: childInput.defaultValue
initialValue: childInput.props.defaultValue
});
}
},
reset(){
reset() {
// maybe do reset by reload instead of front end state?
this.setState({value: this.state.initialValue});
if (this.refs.input.state){
// This is probably not the right way but easy fix
this.refs.input.state.value = this.state.initialValue;
}
else{
this.refs.input.getDOMNode().value = this.state.initialValue;
}
// resets the value of a custom react component input
this.refs.input.state.value = this.state.initialValue;
// resets the value of a plain HTML5 input
this.refs.input.getDOMNode().value = this.state.initialValue;
},

View File

@ -76,7 +76,8 @@ let PieceListBulkModal = React.createClass({
},
handleSuccess() {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
this.fetchSelectedPieceEditionList()
.forEach((pieceId) => {

View File

@ -1,7 +1,7 @@
'use strict';
import React from 'react';
import { getLangText } from '../../utils/lang_utils.js'
import { getLangText } from '../../utils/lang_utils.js';
let PieceListBulkModalSelectedEditionsWidget = React.createClass({
propTypes: {

View File

@ -2,6 +2,8 @@
import React from 'react';
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
import Input from 'react-bootstrap/lib/Input';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import { getLangText } from '../../utils/lang_utils';
@ -41,6 +43,13 @@ let PieceListToolbar = React.createClass({
onChange={this.searchFor}
addonAfter={searchIcon} />
</span>
<span className="pull-right">
<PieceListToolbarFilterWidget
filterParams={['acl_transfer', 'acl_consign', {
key: 'acl_editions',
label: 'create editions'
}]} />
</span>
</div>
</div>
</div>

View File

@ -2,30 +2,132 @@
import React from 'react';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import PieceListStore from '../../stores/piece_list_store';
import PieceListActions from '../../actions/piece_list_actions';
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({
propTypes: {
// An array of either strings (which represent acl enums) or objects of the form
//
// {
// key: <acl enum>,
// label: <a human readable string>
// }
//
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired
},
getInitialState() {
return PieceListStore.getState();
},
componentDidMount() {
PieceListStore.listen(this.onChange);
},
componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
generateFilterByStatement(param) {
let filterBy = this.state.filterBy;
if(filterBy) {
// we need hasOwnProperty since the values are all booleans
if(filterBy.hasOwnProperty(param)) {
filterBy[param] = !filterBy[param];
// if the parameter is false, then we want to remove it again
// from the list of queryParameters as this component is only about
// which actions *CAN* be done and not what *CANNOT*
if(!filterBy[param]) {
delete filterBy[param];
}
} else {
filterBy[param] = true;
}
}
return filterBy;
},
/**
* We need overloading here to find the correct parameter of the label
* the user is clicking on.
*/
filterBy(param) {
return () => {
let filterBy = this.generateFilterByStatement(param);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, filterBy);
};
},
isFilterActive() {
let trueValuesOnly = Object.keys(this.state.filterBy).filter((acl) => acl);
// We're hiding the star in that complicated matter so that,
// the surrounding button is not resized up on appearance
if(trueValuesOnly.length > 0) {
return { visibility: 'visible'};
} else {
return { visibility: 'hidden' };
}
},
render() {
let filterIcon = <Glyphicon glyph='filter' className="filter-glyph"/>;
let filterIcon = (
<span>
<span className="glyphicon glyphicon-filter" aria-hidden="true"></span>
<span style={this.isFilterActive()}>*</span>
</span>
);
return (
<DropdownButton title={filterIcon}>
<DropdownButton
title={filterIcon}
className="ascribe-piece-list-toolbar-filter-widget">
<li style={{'textAlign': 'center'}}>
<em>{getLangText('Show Pieces that')}:</em>
<em>{getLangText('Show works that')}:</em>
</li>
<MenuItem eventKey='1'>
<div className="checkbox">
{getLangText('I can transfer')} <input type="checkbox" />
</div>
</MenuItem>
<MenuItem eventKey='2'>
<div className="checkbox">
{getLangText('I can consign')} <input type="checkbox" />
</div>
</MenuItem>
{this.props.filterParams.map((param, i) => {
let label;
if(typeof param !== 'string') {
label = param.label;
param = param.key;
} else {
param = param;
label = param.split('_')[1];
}
return (
<MenuItem
key={i}
onClick={this.filterBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
<span>
{getLangText('I can') + ' ' + getLangText(label)}
</span>
<input
readOnly
type="checkbox"
checked={this.state.filterBy[param]} />
</div>
</MenuItem>
);
})}
</DropdownButton>
);
}

View File

@ -1,113 +0,0 @@
'use strict';
import React from 'react';
import { ColumnModel } from './models/table_models';
import EditionListStore from '../../stores/edition_list_store';
import EditionListActions from '../../actions/edition_list_actions';
import Table from './table';
import TableItemWrapper from './table_item_wrapper';
import TableItemText from './table_item_text';
import TableItemAcl from './table_item_acl';
import TableItemSelectable from './table_item_selectable';
import TableItemSubtableButton from './table_item_subtable_button';
let TableItemSubtable = React.createClass({
propTypes: {
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(ColumnModel)),
columnContent: React.PropTypes.object
},
getInitialState() {
return {
'open': false
};
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
loadEditionList() {
if(this.state.open) {
this.setState({
'open': false
});
} else {
EditionListActions.fetchEditionList(this.props.columnContent.id);
this.setState({
'open': true,
'editionList': EditionListStore.getState()
});
}
},
selectItem(parentId, itemId) {
EditionListActions.selectEdition({
'pieceId': parentId,
'editionId': itemId
});
},
render() {
let renderEditionListTable = () => {
let columnList = [
new ColumnModel('edition_number', 'Number', TableItemText, 2, false),
new ColumnModel('user_registered', 'User', TableItemText, 4, true),
new ColumnModel('acl', 'Actions', TableItemAcl, 4, true)
];
if(this.state.open && this.state.editionList[this.props.columnContent.id] && this.state.editionList[this.props.columnContent.id].length) {
return (
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<Table itemList={this.state.editionList[this.props.columnContent.id]} columnList={columnList}>
{this.state.editionList[this.props.columnContent.id].map((edition, i) => {
return (
<TableItemSelectable
className="ascribe-table-item-selectable"
selectItem={this.selectItem}
parentId={this.props.columnContent.id}
key={i} />
);
})}
</Table>
</div>
</div>
);
}
};
return (
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item">
<div className="row">
<TableItemWrapper
columnList={this.props.columnList}
columnContent={this.props.columnContent}
columnWidth={12} />
<div className="col-xs-1 col-sm-1 col-md-1 col-lg-1 ascribe-table-item-column">
<TableItemSubtableButton content="+" onClick={this.loadEditionList} />
</div>
</div>
{renderEditionListTable()}
</div>
);
}
});
export default TableItemSubtable;

View File

@ -1,23 +0,0 @@
'use strict';
import React from 'react';
let TableItemSubtableButton = React.createClass({
propTypes: {
content: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired
},
render() {
return (
<span>
<button type="button" className="btn btn-default btn-sm ascribe-table-expand-button" onClick={this.props.onClick}>
{this.props.content}
</button>
</span>
);
}
});
export default TableItemSubtableButton;

View File

@ -34,7 +34,8 @@ let PieceList = React.createClass({
let page = this.getQuery().page || 1;
PieceListStore.listen(this.onChange);
if (this.state.pieceList.length === 0){
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc)
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy)
.then(PieceListActions.fetchPieceRequestActions());
}
},
@ -59,9 +60,9 @@ let PieceList = React.createClass({
// the site should go to the top
document.body.scrollTop = document.documentElement.scrollTop = 0;
return () => PieceListActions.fetchPieceList(page, this.state.pageSize,
this.state.search, this.state.orderBy,
this.state.orderAsc);
return () => PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc,
this.state.filterBy);
},
getPagination() {
@ -79,13 +80,14 @@ let PieceList = React.createClass({
},
searchFor(searchTerm) {
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc);
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
this.state.orderAsc, this.state.filterBy);
this.transitionTo(this.getPathname(), {page: 1});
},
accordionChangeOrder(orderBy, orderAsc) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize,
this.state.search, orderBy, orderAsc);
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
orderBy, orderAsc, this.state.filterBy);
},
render() {

View File

@ -101,7 +101,8 @@ let RegisterPiece = React.createClass( {
this.state.pageSize,
this.state.searchTerm,
this.state.orderBy,
this.state.orderAsc
this.state.orderAsc,
this.state.filterBy
);
this.transitionTo('piece', {pieceId: response.piece.id});

View File

@ -3,20 +3,26 @@
import requests from '../utils/requests';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
import { mergeOptions } from '../utils/general_utils';
let EditionListFetcher = {
/**
* Fetches a list of editions from the API.
*/
fetch(pieceId, page, pageSize, orderBy, orderAsc) {
fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy) {
let ordering = generateOrderingQueryParams(orderBy, orderAsc);
return requests.get('editions_list', {
'piece_id': pieceId,
page,
pageSize,
ordering
});
let queryParams = mergeOptions(
{
page,
pageSize,
ordering,
piece_id: pieceId
},
filterBy
);
return requests.get('editions_list', queryParams);
}
};

View File

@ -1,17 +1,31 @@
'use strict';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
import requests from '../utils/requests';
import { mergeOptions } from '../utils/general_utils';
import { generateOrderingQueryParams } from '../utils/fetch_api_utils';
let PieceListFetcher = {
/**
* Fetches a list of pieces from the API.
* Can be called with all supplied queryparams the API.
*/
fetch(page, pageSize, search, orderBy, orderAsc) {
fetch(page, pageSize, search, orderBy, orderAsc, filterBy) {
let ordering = generateOrderingQueryParams(orderBy, orderAsc);
return requests.get('pieces_list', { page, pageSize, search, ordering });
// filterBy is an object of acl key-value pairs.
// The values are booleans
let queryParams = mergeOptions(
{
page,
pageSize,
search,
ordering
},
filterBy
);
return requests.get('pieces_list', queryParams);
},
fetchRequestActions() {

View File

@ -12,7 +12,7 @@ class EditionListStore {
this.bindActions(EditionsListActions);
}
onUpdateEditionList({pieceId, editionListOfPiece, page, pageSize, orderBy, orderAsc, count}) {
onUpdateEditionList({pieceId, editionListOfPiece, page, pageSize, orderBy, orderAsc, count, filterBy}) {
/*
Basically there are two modes an edition list can be updated.
@ -54,6 +54,7 @@ class EditionListStore {
this.editionList[pieceId].orderBy = orderBy;
this.editionList[pieceId].orderAsc = orderAsc;
this.editionList[pieceId].count = count;
this.editionList[pieceId].filterBy = filterBy;
}
/**
@ -80,7 +81,10 @@ class EditionListStore {
this.editionList[pieceId].length = 0;
// refetch editions with adjusted page size
EditionsListActions.fetchEditionList(pieceId, 1, prevEditionListLength, this.editionList[pieceId].orderBy, this.editionList[pieceId].orderAsc)
EditionsListActions.fetchEditionList(pieceId, 1, prevEditionListLength,
this.editionList[pieceId].orderBy,
this.editionList[pieceId].orderAsc,
this.editionList[pieceId].filterBy)
.then(() => {
// reset back to the normal pageSize and page
this.editionList[pieceId].page = prevEditionListPage;

View File

@ -26,16 +26,18 @@ class PieceListStore {
this.search = '';
this.orderBy = 'artist_name';
this.orderAsc = true;
this.filterBy = {};
this.bindActions(PieceListActions);
}
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount }) {
onUpdatePieceList({ page, pageSize, search, pieceList, orderBy, orderAsc, pieceListCount, filterBy }) {
this.page = page;
this.pageSize = pageSize;
this.search = search;
this.orderAsc = orderAsc;
this.orderBy = orderBy;
this.pieceListCount = pieceListCount;
this.filterBy = filterBy;
/**
* Pagination - Known Issue:

View File

@ -5,5 +5,41 @@
}
.search-bar {
max-width: 160px;
max-width: 200px;
input {
height: 33px;
}
}
.ascribe-piece-list-toolbar-filter-widget {
margin-right: 1em;
.filter-widget-item {
> a {
padding-left: 0;
padding-right: 0;
}
}
.checkbox-line {
position: relative;
height: 25px;
span {
position: absolute;
left: 9px;
top: 3px;
cursor: pointer;
margin-right: 10px;
}
input {
position: absolute;
top: 2px;
right: 9px;
margin-left: 10px;
}
}
}