1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00

Merge branch 'master' into AD-351-refactor-fetch

This commit is contained in:
vrde 2015-06-01 16:45:40 +02:00
commit 699c07fe3f
23 changed files with 508 additions and 113 deletions

View File

@ -9,7 +9,7 @@
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
</head>
<body>
<div id="main" class="container"></div>
<div id="main" class="container clear-margins-and-paddings"></div>
<div id="modal" class="container"></div>
<script src="build/app.js"></script>
</body>

View File

@ -6,7 +6,8 @@ class EditionListActions {
constructor() {
this.generateActions(
'updateEditionList',
'selectEdition'
'selectEdition',
'clearAllEditionSelections'
);
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import ReactAddons from 'react/addons';
let AccordionList = React.createClass({
propTypes: {
className: React.PropTypes.string,
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired
},
render() {
if(this.props.itemList && this.props.itemList.length > 0) {
return (
<div className={this.props.className}>
{this.props.children}
</div>
);
} else {
return (
<p>Loading</p>
);
}
}
});
export default AccordionList;

View File

@ -0,0 +1,33 @@
import React from 'react';
import AccordionListItemTable from './accordion_list_item_table';
let AccordionListItem = React.createClass({
propTypes: {
className: React.PropTypes.string,
content: React.PropTypes.object
},
render() {
return (
<div className="row">
<div className={this.props.className}>
<div className="wrapper">
<div className="thumbnail-wrapper">
<img src={this.props.content.thumbnail} />
</div>
<div className="info-wrapper">
<h1>{this.props.content.title}</h1>
<h3>by {this.props.content.artist_name}</h3>
</div>
<span style={{'clear': 'both'}}></span>
</div>
</div>
{this.props.children}
</div>
);
}
});
export default AccordionListItem;

View File

@ -0,0 +1,87 @@
import React from 'react';
import Table from '../ascribe_table/table';
import TableItem from '../ascribe_table/table_item';
import TableColumnContentModel from '../../models/table_column_content_model';
let AccordionListItemTable = React.createClass({
getInitialState() {
return {
'show': false
};
},
propTypes: {
className: React.PropTypes.string,
parentId: React.PropTypes.number,
fetchData: React.PropTypes.func,
itemList: React.PropTypes.array,
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
numOfTableItems: React.PropTypes.number
},
toggleTable() {
this.props.fetchData();
this.setState({
'show': !this.state.show
});
},
render() {
if(this.props.itemList && this.state.show) {
return (
<div className={this.props.className}>
<Table
columnList={this.props.columnList}
itemList={this.props.itemList}>
{this.props.itemList.map((item, i) => {
return (
<TableItem
className="ascribe-table-item-selectable"
key={i}>
</TableItem>
);
})}
</Table>
<AccordionListItemTableToggle
className="ascribe-accordion-list-table-toggle"
onClick={this.toggleTable}
show={this.state.show}
numOfTableItems={this.props.numOfTableItems} />
</div>
);
} else {
return (
<div className={this.props.className}>
<AccordionListItemTableToggle
className="ascribe-accordion-list-table-toggle"
onClick={this.toggleTable}
show={this.state.show}
numOfTableItems={this.props.numOfTableItems} />
</div>
);
}
}
});
let AccordionListItemTableToggle = React.createClass({
propTypes: {
className: React.PropTypes.string,
onClick: React.PropTypes.func,
show: React.PropTypes.bool,
numOfTableItems: React.PropTypes.number
},
render() {
return (
<span
className={this.props.className}
onClick={this.props.onClick}>
{this.props.show ? 'Hide all ' + this.props.numOfTableItems + ' Editions' : 'Show all ' + this.props.numOfTableItems + ' Editions'}
</span>
);
}
});
export default AccordionListItemTable;

View File

@ -0,0 +1,67 @@
import React from 'react';
import EditionListStore from '../../stores/edition_list_store';
import EditionListActions from '../../actions/edition_list_actions';
import AccordionListItemTable from './accordion_list_item_table';
import TableColumnContentModel from '../../models/table_column_content_model';
import TableItemImg from '../ascribe_table/table_item_img';
import TableItemText from '../ascribe_table/table_item_text';
import TableItemCheckbox from '../ascribe_table/table_item_checkbox';
import TableItemAclFiltered from '../ascribe_table/table_item_acl_filtered';
let AccordionListItemTableEditions = React.createClass({
propTypes: {
className: React.PropTypes.string,
parentId: React.PropTypes.number,
numOfEditions: React.PropTypes.number
},
getInitialState() {
return EditionListStore.getState();
},
onChange(state) {
this.setState(state);
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentDidUnmount() {
EditionListStore.unlisten(this.onChange);
},
getEditionList() {
EditionListActions.fetchEditionList(this.props.parentId);
},
selectItem(pieceId, editionId) {
EditionListActions.selectEdition({pieceId, editionId});
},
render() {
let columnList = [
new TableColumnContentModel((item) => { return { 'editionId': item.id, 'pieceId': this.props.parentId, 'selectItem': this.selectItem, 'selected': item.selected }}, '', '', TableItemCheckbox, 1, false),
new TableColumnContentModel((item) => { return { 'content': item.edition_number }}, 'num_editions', 'Nr', TableItemText, 1, false),
new TableColumnContentModel((item) => { return { 'content': item.bitcoin_id }}, 'bitcoin_id', 'Bitcoin Address', TableItemText, 5, false),
new TableColumnContentModel((item) => { return { 'content': item.acl }}, 'acl', 'Actions', TableItemAclFiltered, 4, false)
];
return (
<AccordionListItemTable
className={this.props.className}
parentId={this.props.parentId}
fetchData={this.getEditionList}
itemList={this.state.editionList[this.props.parentId]}
columnList={columnList}
numOfTableItems={this.props.numOfEditions} />
);
}
});
export default AccordionListItemTableEditions;

View File

@ -1,11 +1,16 @@
import React from 'react';
import EditionListStore from '../../stores/edition_list_store';
import EditionListActions from '../../actions/edition_list_actions';
import AclButton from '../acl_button';
import PieceListToolbarSelectedEditionsWidget from './piece_list_toolbar_selected_editions_widget';
let PieceListToolbar = React.createClass({
propTypes: {
className: React.PropTypes.string
},
getInitialState() {
return EditionListStore.getState();
},
@ -66,25 +71,46 @@ let PieceListToolbar = React.createClass({
return availableAcls;
},
clearAllSelections() {
EditionListActions.clearAllEditionSelections();
},
render() {
let availableAcls = this.getAvailableAcls();
return (
<div className="row no-margin">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 piece-list-toolbar">
<div className="pull-left">
<PieceListToolbarSelectedEditionsWidget
numberOfSelectedEditions={this.fetchSelectedEditionList().length} />
</div>
<div className="pull-right">
<AclButton availableAcls={availableAcls} action="transfer" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="consign" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="share" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="loan" actionFunction={this.bulk} />
if(availableAcls.length > 0) {
return (
<div className={this.props.className}>
<div className="row no-margin">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 piece-list-toolbar">
<p></p>
<div className="row">
<div className="text-center">
<PieceListToolbarSelectedEditionsWidget
numberOfSelectedEditions={this.fetchSelectedEditionList().length} />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<span
className="piece-list-toolbar-clear-all"
onClick={this.clearAllSelections}>clear all</span>
</div>
</div>
<p></p>
<div className="row">
<div className="text-center">
<AclButton availableAcls={availableAcls} action="transfer" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="consign" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="share" actionFunction={this.bulk} />
<AclButton availableAcls={availableAcls} action="loan" actionFunction={this.bulk} />
</div>
</div>
</div>
</div>
</div>
</div>
);
);
} else {
return null;
}
}
});

View File

@ -9,7 +9,7 @@ let Table = React.createClass({
propTypes: {
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
changeOrder: React.PropTypes.func // turn isRequired on again after editions order implemented
changeOrder: React.PropTypes.func
},
renderChildren() {
@ -24,24 +24,20 @@ let Table = React.createClass({
},
render() {
if(this.props.itemList && this.props.itemList.length > 0) {
return (
<div className="ascribe-table">
<TableHeader
columnList={this.props.columnList}
itemList={this.props.itemList}
fetchList={this.props.fetchList}
changeOrder={this.props.changeOrder}
orderAsc={this.props.orderAsc}
orderBy={this.props.orderBy} />
return (
<div className="ascribe-table">
<TableHeader
columnList={this.props.columnList}
itemList={this.props.itemList}
fetchList={this.props.fetchList}
changeOrder={this.props.changeOrder}
orderAsc={this.props.orderAsc}
orderBy={this.props.orderBy} />
<div className="row">
{this.renderChildren()}
</div>
);
} else {
return (
<p>Loading</p>
);
}
</div>
);
}
});

View File

@ -11,13 +11,12 @@ let TableItem = React.createClass({
columnList: React.PropTypes.arrayOf(React.PropTypes.instanceOf(TableColumnContentModel)),
columnContent: React.PropTypes.object,
onClick: React.PropTypes.func, // See: https://facebook.github.io/react/tips/expose-component-functions.html
classNames: React.PropTypes.string
className: React.PropTypes.string
},
render() {
return (
<div
className={this.props.classNames + ' col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item'}
<div className={this.props.className + ' col-xs-12 col-sm-12 col-md-12 col-lg-12 ascribe-table-item'}
onClick={this.props.onClick}>
<div className="row">
<TableItemWrapper

View File

@ -0,0 +1,22 @@
import React from 'react';
let TableItemAclFiltered = React.createClass({
propTypes: {
content: React.PropTypes.array.isRequired
},
render() {
let filteredAcls = this.props.content.filter((v) => {
return v === 'consign' || v === 'loan' || v === 'transfer' || v === 'view';
});
return (
<span>
{filteredAcls.join('/')}
</span>
);
}
});
export default TableItemAclFiltered;

View File

@ -0,0 +1,23 @@
import React from 'react';
let TableItemCheckbox = React.createClass({
propTypes: {
editionId: React.PropTypes.number,
pieceId: React.PropTypes.number,
selectItem: React.PropTypes.func,
selected: React.PropTypes.bool
},
selectItem() {
this.props.selectItem(this.props.pieceId, this.props.editionId);
},
render() {
return (
<input type="checkbox" onChange={this.selectItem} checked={this.props.selected}/>
);
}
});
export default TableItemCheckbox;

View File

@ -26,12 +26,13 @@ let TableItemSelectable = React.createClass({
return (
<TableItem
classNames={tableItemClasses + ' ' + this.props.className}
className={tableItemClasses + ' ' + this.props.className}
columnList={this.props.columnList}
columnContent={this.props.columnContent}
onClick={this.selectItem}>
</TableItem>
);
}
});

View File

@ -17,11 +17,13 @@ let TableItemWrapper = React.createClass({
{this.props.columnList.map((column, i) => {
let TypeElement = column.displayType;
let typeElementProps = column.transformFn(this.props.columnContent);
let columnClass = this.calcColumnClasses(this.props.columnList, i, this.props.columnWidth);
return (
<div className={columnClass + ' ascribe-table-item-column'} key={i}>
<TypeElement content={this.props.columnContent[column.columnName]} width="50" />
<TypeElement {...typeElementProps} />
</div>
);

View File

@ -24,7 +24,7 @@ let Header = React.createClass({
render() {
return (
<nav className="navbar navbar-default navbar-fixed-top">
<nav className="navbar navbar-default">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
@ -35,15 +35,12 @@ let Header = React.createClass({
</button>
<a className="navbar-brand" href="#">
<span>ascribe </span>
<span className="glyph-ascribe-spool-chunked"></span>
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
</a>
</div>
<div id="navbar" className="navbar-collapse collapse">
<ul className="nav navbar-nav navbar-left">
<li>
<Link to="pieces">Pieces</Link>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">

View File

@ -4,14 +4,9 @@ import AltContainer from 'alt/AltContainer';
import PieceListStore from '../stores/piece_list_store';
import PieceListActions from '../actions/piece_list_actions';
import Table from './ascribe_table/table';
import TableItem from './ascribe_table/table_item';
import TableItemImg from './ascribe_table/table_item_img';
import TableItemText from './ascribe_table/table_item_text';
import TableItemSubtable from './ascribe_table/table_item_subtable';
import TableItemSubtableButton from './ascribe_table/table_item_subtable_button';
import TableColumnContentModel from '../models/table_column_content_model';
import AccordionList from './ascribe_accordion_list/accordion_list';
import AccordionListItem from './ascribe_accordion_list/accordion_list_item';
import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_list_item_table_editions';
import Pagination from './ascribe_pagination/pagination';
@ -44,55 +39,48 @@ let PieceList = React.createClass({
this.state.orderAsc);
},
tableChangeOrder(orderBy, orderAsc) {
accordionChangeOrder(orderBy, orderAsc) {
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize,
this.state.search, orderBy, orderAsc);
},
render() {
let columnList = [
new TableColumnContentModel('thumbnail', '', TableItemImg, 2, false),
new TableColumnContentModel('artist_name', 'Artist', TableItemText, 4, true),
new TableColumnContentModel('title', 'Title', TableItemText, 4, true)
];
let currentPage = parseInt(this.props.query.page, 10) || 1;
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize)
if(this.state.pieceList && this.state.pieceList.length > 0) {
return (
<div>
<PieceListToolbar />
<Table
columnList={columnList}
changeOrder={this.tableChangeOrder}
itemList={this.state.pieceList}
itemListCount={this.state.pieceListCount}
orderBy={this.state.orderBy}
orderAsc={this.state.orderAsc}
search={this.state.search}
page={this.state.page}
pageSize={this.state.pageSize}>
{this.state.pieceList.map((item, i) => {
return (
<TableItemSubtable
key={i}>
</TableItemSubtable>
);
})}
</Table>
<Pagination currentPage={currentPage}
totalPages={totalPages}
goToPage={this.paginationGoToPage}>
</Pagination>
</div>
);
} else {
return (
<p>Loading</p>
);
}
return (
<div>
<PieceListToolbar className="ascribe-piece-list-toolbar" />
<AccordionList
className="ascribe-accordion-list"
changeOrder={this.accordionChangeOrder}
itemList={this.state.pieceList}
orderBy={this.state.orderBy}
orderAsc={this.state.orderAsc}
search={this.state.search}
page={this.state.page}
pageSize={this.state.pageSize}>
{this.state.pieceList.map((item, i) => {
return (
<AccordionListItem
className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2 ascribe-accordion-list-item"
content={item}
key={i}>
<AccordionListItemTableEditions
className="ascribe-accordion-list-item-table col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xs-offset-3 col-sm-offset-3 col-md-offset-3 col-lg-offset-3"
parentId={item.id}
numOfEditions={item.num_editions}/>
</AccordionListItem>
);
})}
</AccordionList>
<Pagination
currentPage={currentPage}
totalPages={totalPages}
goToPage={this.paginationGoToPage}>
</Pagination>
</div>
);
}
});

View File

@ -1,6 +1,7 @@
class TableColumnContentModel {
// ToDo: Add validation for all passed-in parameters
constructor(columnName, displayName, displayType, rowWidth, canBeOrdered) {
constructor(transformFn, columnName, displayName, displayType, rowWidth, canBeOrdered) {
this.transformFn = transformFn;
this.columnName = columnName;
this.displayName = displayName;
this.displayType = displayType;

View File

@ -9,8 +9,8 @@ let Route = Router.Route;
let routes = (
<Route name="app" path="/" handler={AscribeApp}>
<Route name="pieces" path="/pieces" handler={PieceList}>
<Route name="app" handler={AscribeApp}>
<Route name="pieces" path="/" handler={PieceList}>
</Route>
<Route name="edition" path="/editions/:editionId" handler={EditionContainer}>

View File

@ -21,9 +21,8 @@ class EditionListStore {
}
onSelectEdition({pieceId, editionId}) {
this.editionList[pieceId].forEach((edition) => {
if(edition.edition_number === editionId) {
if(edition.id === editionId) {
if(edition.selected) {
edition.selected = false;
} else {
@ -31,7 +30,21 @@ class EditionListStore {
}
}
});
}
onClearAllEditionSelections() {
Object
.keys(this.editionList)
.forEach((pieceId) => {
this.editionList[pieceId]
.forEach((edition) => {
try {
delete edition.selected;
} catch(err) {
//just ignore
}
});
});
}
};

View File

@ -0,0 +1,78 @@
$ascribe-accordion-list-item-height: 9em;
$ascribe-accordion-list-font: 'Source Sans Pro';
.ascribe-accordion-list-item {
background-color: rgba(0,0,0,0.004);
border: 1px solid black;
height: $ascribe-accordion-list-item-height;
padding-left:0;
padding-right:0;
overflow:hidden;
border-left: 0.1em solid #E0E0E0;
border-right: 0.1em solid #E0E0E0;
border-top: 0.1em solid #E0E0E0;
border-radius: 5px;
border-bottom: 0.2em solid #E0E0E0;
.wrapper {
width:100%;
height:100%;
// ToDo: Include media queries for thumbnail
.thumbnail-wrapper {
float:left;
height:100%;
width:$ascribe-accordion-list-item-height;
overflow:hidden;
padding-left:0;
padding-right:0;
img {
height: $ascribe-accordion-list-item-height;
}
}
.info-wrapper {
float:left;
font-family: $ascribe-accordion-list-font;
margin-left: 2em;
padding-top: .75em;
h1 {
font-size: 2.25em;
}
h3 {
font-size: 1.1em;
margin: .7em 0 0 0;
}
}
}
}
.ascribe-accordion-list-item-table {
text-align: center;
margin-bottom: 3em;
background-color: rgba(0,0,0,0.004);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: 0.15em solid #E0E0E0;
border-left: 0.1em solid #E0E0E0;
border-right: 0.1em solid #E0E0E0;
}
span.ascribe-accordion-list-table-toggle {
::selection { background: transparent; }
::-moz-selection { background: transparent; }
&:hover {
color: $ascribe-color-dark;
cursor:pointer;
}
}
.ascribe-accordion-list-table-list {
margin-bottom: .5em;
th, td {
font-size:.85em;
text-align: center;
}
}

View File

@ -0,0 +1,20 @@
.ascribe-piece-list-toolbar {
position: fixed;
top:0;
width:1170px;
height:6em;
background-color: #FAFAFA;
border-left: 0.1em solid #E0E0E0;
border-right: 0.1em solid #E0E0E0;
border-top: 0.1em solid #E0E0E0;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom: 0.2em solid #E0E0E0;
z-index:9999;
}
.piece-list-toolbar-clear-all {
text-decoration: underline;
cursor:pointer;
}

View File

@ -0,0 +1,2 @@
$ascribe-color: rgba(2, 182, 163, 0.5);
$ascribe-color-dark: rgba(2, 182, 163, 0.8);

View File

@ -1,16 +1,32 @@
// If you import a new .scss file, make sure to restart gulp
// otherwise it will not be included
@import 'variables';
@import 'ascribe-variables';
@import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap';
@import './ascribe-fonts/style';
@import './ascribe-fonts/ascribe-fonts';
#main {
padding-top: 70px;
}
@import 'ascribe-accordion_list';
@import 'ascribe-piece-list-toolbar';
.hidden {
display: none;
}
.navbar-default {
border-left:0;
border-right:0;
margin-bottom: 3em;
}
.clear-margins-and-paddings {
padding-left:0;
padding-right:0;
}
.ascribe-color {
color: $ascribe-color;
}
/* Taken from http://stackoverflow.com/a/20548578 */
.vcenter {
display: inline-block;
@ -19,8 +35,7 @@
}
.ascribe-table-header-row {
border-bottom: 2px solid rgba(2, 182, 163, 0.5);
border-top: 2px solid rgba(2, 182, 163, 0.5);
border-bottom: 2px solid #E0E0E0;
padding: 0;
}
@ -35,8 +50,7 @@
vertical-align: middle;
font-family: 'Source Sans Pro';
font-weight: 600;
color: rgba(2, 182, 163, 1);
font-size: 1.4em;
color: #424242;
}
.ascribe-table-header-column > span > .glyphicon {
@ -54,13 +68,16 @@
.ascribe-table-item-column {
display: table;
font-family: 'Source Sans Pro';
font-size: 1.2em;
font-size: .8em;
height:3em;
}
.ascribe-table-item-column > * {
display: table-cell;
vertical-align: middle;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.ascribe-table-item-selected {
@ -71,10 +88,6 @@
cursor: default;
}
.piece-list-toolbar {
height:3em;
}
.no-margin {
margin-right: 0;
margin-left: 0;

View File

@ -359,13 +359,13 @@ $container-lg: $container-large-desktop !default;
// Basics of a navbar
$navbar-height: 50px !default;
$navbar-margin-bottom: $line-height-computed !default;
$navbar-border-radius: $border-radius-base !default;
$navbar-border-radius: 0 !default;
$navbar-padding-horizontal: floor(($grid-gutter-width / 2)) !default;
$navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2) !default;
$navbar-collapse-max-height: 340px !default;
$navbar-default-color: #777 !default;
$navbar-default-bg: #f8f8f8 !default;
$navbar-default-bg: white !default;
$navbar-default-border: darken($navbar-default-bg, 6.5%) !default;
// Navbar links
@ -378,7 +378,7 @@ $navbar-default-link-disabled-color: #ccc !default;
$navbar-default-link-disabled-bg: transparent !default;
// Navbar brand label
$navbar-default-brand-color: $navbar-default-link-color !default;
$navbar-default-brand-color: black !default;
$navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%) !default;
$navbar-default-brand-hover-bg: transparent !default;