+ );
+ }
+});
+
+
+
+export default DetailProperty;
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js
index 5a25401c..8aef5de6 100644
--- a/js/components/ascribe_detail/edition.js
+++ b/js/components/ascribe_detail/edition.js
@@ -7,21 +7,23 @@ import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
-import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin';
import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store';
import CoaActions from '../../actions/coa_actions';
import CoaStore from '../../stores/coa_store';
-import MediaPlayer from './../ascribe_media/media_player';
+import MediaContainer from './media_container';
import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph';
import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property';
+import EditionDetailProperty from './detail_property';
import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable';
+import EditionHeader from './header';
+
import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
import RequestActionForm from './../ascribe_forms/form_request_action';
@@ -35,7 +37,6 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import apiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
-import classNames from 'classnames';
import { getCookie } from '../../utils/fetch_api_utils';
@@ -72,10 +73,10 @@ let Edition = React.createClass({
- );
- }
-});
-
let EditionDetailHistoryIterator = React.createClass({
propTypes: {
history: React.PropTypes.array
diff --git a/js/components/edition_container.js b/js/components/ascribe_detail/edition_container.js
similarity index 89%
rename from js/components/edition_container.js
rename to js/components/ascribe_detail/edition_container.js
index dfefa69e..6eebaeb8 100644
--- a/js/components/edition_container.js
+++ b/js/components/ascribe_detail/edition_container.js
@@ -2,10 +2,10 @@
import React from 'react';
-import EditionActions from '../actions/edition_actions';
-import EditionStore from '../stores/edition_store';
+import EditionActions from '../../actions/edition_actions';
+import EditionStore from '../../stores/edition_store';
-import Edition from './ascribe_detail/edition';
+import Edition from './edition';
/**
* This is the component that implements resource/data specific functionality
diff --git a/js/components/ascribe_detail/further_details.js b/js/components/ascribe_detail/further_details.js
new file mode 100644
index 00000000..c1024cbc
--- /dev/null
+++ b/js/components/ascribe_detail/further_details.js
@@ -0,0 +1,174 @@
+'use strict';
+
+import React from 'react';
+
+import Row from 'react-bootstrap/lib/Row';
+import Col from 'react-bootstrap/lib/Col';
+
+
+import Form from './../ascribe_forms/form';
+import Property from './../ascribe_forms/property';
+
+import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
+
+import ReactS3FineUploader from './../ascribe_uploader/react_s3_fine_uploader';
+
+import GlobalNotificationModel from '../../models/global_notification_model';
+import GlobalNotificationActions from '../../actions/global_notification_actions';
+
+import apiUrls from '../../constants/api_urls';
+import AppConstants from '../../constants/application_constants';
+
+import { getCookie } from '../../utils/fetch_api_utils';
+
+let FurtherDetails = React.createClass({
+ propTypes: {
+ content: React.PropTypes.object,
+ handleSuccess: React.PropTypes.func
+ },
+
+ getInitialState() {
+ return {
+ loading: false
+ };
+ },
+
+ showNotification(){
+ this.props.handleSuccess();
+ let notification = new GlobalNotificationModel('Details updated', 'success');
+ GlobalNotificationActions.appendGlobalNotification(notification);
+ },
+
+ submitKey(key){
+ this.setState({
+ otherDataKey: key
+ });
+ },
+
+ setIsUploadReady(isReady) {
+ this.setState({
+ isUploadReady: isReady
+ });
+ },
+
+ isReadyForFormSubmission(files) {
+ files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
+ if(files.length > 0 && files[0].status === 'upload successful') {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ render() {
+ let editable = this.props.content.acl.indexOf('edit') > -1;
+ return ();
+ //return (
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //);
+ }
+});
+
+let FileUploader = React.createClass({
+ propTypes: {
+ content: React.PropTypes.object,
+ setIsUploadReady: React.PropTypes.func,
+ submitKey: React.PropTypes.func,
+ isReadyForFormSubmission: React.PropTypes.func,
+ editable: React.PropTypes.bool
+ },
+
+ render() {
+ // Essentially there a three cases important to the fileuploader
+ //
+ // 1. there is no other_data => do not show the fileuploader at all
+ // 2. there is other_data, but user has no edit rights => show fileuploader but without action buttons
+ // 3. both other_data and editable are defined or true => show fileuploade with all action buttons
+ if (!this.props.editable && !this.props.content.other_data){
+ return null;
+ }
+ return (
+
+ );
+ }
+});
+
+export default FurtherDetails;
diff --git a/js/components/ascribe_detail/header.js b/js/components/ascribe_detail/header.js
new file mode 100644
index 00000000..e9226a85
--- /dev/null
+++ b/js/components/ascribe_detail/header.js
@@ -0,0 +1,26 @@
+'use strict';
+
+import React from 'react';
+
+import EditionDetailProperty from './detail_property';
+
+
+let Header = React.createClass({
+ propTypes: {
+ content: React.PropTypes.object
+ },
+
+ render() {
+ var titleHtml =
{this.props.content.title}
;
+ return (
+
+
+
+
+
+
+ );
+ }
+});
+
+export default Header;
\ No newline at end of file
diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js
new file mode 100644
index 00000000..65d7b96b
--- /dev/null
+++ b/js/components/ascribe_detail/media_container.js
@@ -0,0 +1,62 @@
+'use strict';
+
+import React from 'react';
+
+import Button from 'react-bootstrap/lib/Button';
+import Glyphicon from 'react-bootstrap/lib/Glyphicon';
+
+import MediaPlayer from './../ascribe_media/media_player';
+
+import CollapsibleButton from './../ascribe_collapsible/collapsible_button';
+
+
+let MediaContainer = React.createClass({
+ propTypes: {
+ content: React.PropTypes.object
+ },
+
+ render() {
+ let thumbnail = this.props.content.thumbnail;
+ let mimetype = this.props.content.digital_work.mime;
+ let embed = null;
+ let extraData = null;
+
+ if (this.props.content.digital_work.encoding_urls) {
+ extraData = this.props.content.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label }; });
+ }
+
+ if (['video', 'audio'].indexOf(mimetype) > -1){
+ embed = (
+
+ Embed
+
+ }
+ panel={
+
+ {''
+ }
+
+ }/>
+ );
+ }
+ return (
+
+
+
+
+ {embed}
+
+
+ );
+ }
+});
+
+export default MediaContainer;
diff --git a/js/components/ascribe_detail/piece.js b/js/components/ascribe_detail/piece.js
new file mode 100644
index 00000000..73f9eb67
--- /dev/null
+++ b/js/components/ascribe_detail/piece.js
@@ -0,0 +1,71 @@
+'use strict';
+
+import React from 'react';
+
+import Row from 'react-bootstrap/lib/Row';
+import Col from 'react-bootstrap/lib/Col';
+
+import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph';
+
+import FurtherDetails from './further_details';
+//import UserActions from '../../actions/user_actions';
+//import UserStore from '../../stores/user_store';
+
+import MediaContainer from './media_container';
+
+import Header from './header';
+
+/**
+ * This is the component that implements display-specific functionality
+ */
+let Piece = React.createClass({
+ propTypes: {
+ piece: React.PropTypes.object,
+ loadPiece: React.PropTypes.func
+ },
+
+ //getInitialState() {
+ // return UserStore.getState();
+ //},
+ //
+ //componentDidMount() {
+ // UserStore.listen(this.onChange);
+ // UserActions.fetchCurrentUser();
+ //},
+ //
+ //componentWillUnmount() {
+ // UserStore.unlisten(this.onChange);
+ //},
+ //
+ //onChange(state) {
+ // this.setState(state);
+ //},
+
+ render() {
+
+ return (
+
+
+
+
+
+
+ -1
+ || Object.keys(this.props.piece.extra_data).length > 0
+ || this.props.piece.other_data !== null}>
+
+
+
+
+
+ );
+ }
+});
+
+export default Piece;
diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js
new file mode 100644
index 00000000..dfd36622
--- /dev/null
+++ b/js/components/ascribe_detail/piece_container.js
@@ -0,0 +1,57 @@
+'use strict';
+
+import React from 'react';
+
+import PieceActions from '../../actions/piece_actions';
+import PieceStore from '../../stores/piece_store';
+
+import Piece from './piece';
+
+/**
+ * This is the component that implements resource/data specific functionality
+ */
+let PieceContainer = React.createClass({
+ getInitialState() {
+ return PieceStore.getState();
+ },
+
+ onChange(state) {
+ this.setState(state);
+ },
+
+ componentDidMount() {
+ PieceStore.listen(this.onChange);
+ PieceActions.fetchOne(this.props.params.pieceId);
+ },
+
+ componentWillUnmount() {
+ // Every time we're leaving the piece detail page,
+ // just reset the piece that is saved in the piece store
+ // as it will otherwise display wrong/old data once the user loads
+ // the piece detail a second time
+ PieceActions.updatePiece({});
+
+ PieceStore.unlisten(this.onChange);
+ },
+
+
+ loadPiece() {
+ PieceActions.fetchOne(this.props.params.pieceId);
+ },
+
+ render() {
+ if('title' in this.state.piece) {
+ return (
+
+ );
+ } else {
+ return (
+
Loading
+ );
+ }
+ }
+});
+
+export default PieceContainer;
diff --git a/js/components/register_piece.js b/js/components/register_piece.js
index cda4bf4b..00d37432 100644
--- a/js/components/register_piece.js
+++ b/js/components/register_piece.js
@@ -91,7 +91,7 @@ let RegisterPiece = React.createClass( {
this.state.orderBy,
this.state.orderAsc);
- this.transitionTo('edition', {editionId: response.piece.bitcoin_id});
+ this.transitionTo('piece', {editionId: response.piece.id});
},
getFormData(){
diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js
index de8d4abf..873dadd0 100644
--- a/js/constants/api_urls.js
+++ b/js/constants/api_urls.js
@@ -30,7 +30,7 @@ let apiUrls = {
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',
'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/',
'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/',
- 'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}',
+ 'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}/',
'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/',
'piece_first_edition_id': AppConstants.apiEndpoint + 'pieces/${piece_id}/edition_index/',
'pieces_list': AppConstants.apiEndpoint + 'pieces/',
diff --git a/js/fetchers/piece_fetcher.js b/js/fetchers/piece_fetcher.js
index c7629d89..481b79fe 100644
--- a/js/fetchers/piece_fetcher.js
+++ b/js/fetchers/piece_fetcher.js
@@ -5,11 +5,10 @@ import requests from '../utils/requests';
let PieceFetcher = {
/**
- * Fetch one user from the API.
- * If no arg is supplied, load the current user
+ * Fetch a piece from the API.
*/
- fetchOne() {
- return requests.get('piece');
+ fetchOne(id) {
+ return requests.get('piece', {'piece_id': id});
}
};
diff --git a/js/routes.js b/js/routes.js
index 7dc34687..db8ed171 100644
--- a/js/routes.js
+++ b/js/routes.js
@@ -5,7 +5,8 @@ import Router from 'react-router';
import AscribeApp from './components/ascribe_app';
import PieceList from './components/piece_list';
-import EditionContainer from './components/edition_container';
+import PieceContainer from './components/ascribe_detail/piece_container';
+import EditionContainer from './components/ascribe_detail/edition_container';
import LoginContainer from './components/login_container';
import SignupContainer from './components/signup_container';
@@ -26,6 +27,7 @@ let routes = (
+