diff --git a/index.html b/index.html index 8d16b099..320dfa89 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,7 @@ -
+
diff --git a/js/actions/edition_list_actions.js b/js/actions/edition_list_actions.js index 83b9abfe..ca653323 100644 --- a/js/actions/edition_list_actions.js +++ b/js/actions/edition_list_actions.js @@ -35,4 +35,4 @@ class EditionListActions { } } -export default alt.createActions(EditionListActions); \ No newline at end of file +export default alt.createActions(EditionListActions); diff --git a/js/components/ascribe_media/media_player.js b/js/components/ascribe_media/media_player.js new file mode 100644 index 00000000..55188568 --- /dev/null +++ b/js/components/ascribe_media/media_player.js @@ -0,0 +1,132 @@ +'use strict'; + +import React from 'react'; +import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; +import Panel from 'react-bootstrap/lib/Panel'; + +/** + * This is the component that implements display-specific functionality. + * + * ResourceViewer handles the following mimetypes: + * - image + * - video + * - audio + * - pdf + * - other + */ + + +let Other = React.createClass({ + propTypes: { + url: React.PropTypes.string.isRequired + }, + + render() { + let ext = this.props.url.split('.').pop(); + + return ( + +

+ .{ext} +

+
+ ); + } +}); + +let Image = React.createClass({ + propTypes: { + url: React.PropTypes.string.isRequired, + preview: React.PropTypes.string.isRequired + }, + + mixins: [InjectInHeadMixin], + + componentDidMount() { + this.inject('http://code.jquery.com/jquery-2.1.4.min.js') + .then(() => + Promise.all([ + this.inject('node_modules/shmui/shmui.css'), + this.inject('node_modules/shmui/jquery.shmui.js') + ]).then(() => { window.jQuery('.shmui-ascribe').shmui(); })); + }, + + render() { + return ( + + ); + } +}); + +let Video = React.createClass({ + propTypes: { + preview: React.PropTypes.string.isRequired, + url: React.PropTypes.string.isRequired, + extraData: React.PropTypes.array.isRequired + }, + + mixins: [InjectInHeadMixin], + + getInitialState() { + return { ready: false }; + }, + + componentDidMount() { + this.inject('http://code.jquery.com/jquery-2.1.4.min.js') + .then(() => + Promise.all([ + this.inject('https://cdnjs.cloudflare.com/ajax/libs/mediaelement/2.17.0/mediaelement-and-player.min.js'), + this.inject('https://cdnjs.cloudflare.com/ajax/libs/mediaelement/2.17.0/mediaelementplayer.min.css') + ]).then(this.ready)); + }, + + ready() { + this.setState({ready: true}); + }, + + render() { + if (this.state.ready) { + return ( + + ); + } else { + return ( + + ); + } + } +}); + + +let resourceMap = { + 'image': Image, + 'video': Video, + 'other': Other +}; + +let MediaPlayer = React.createClass({ + propTypes: { + mimetype: React.PropTypes.oneOf(['image', 'video', 'audio', 'pdf', 'other']).isRequired, + preview: React.PropTypes.string.isRequired, + url: React.PropTypes.string.isRequired, + extraData: React.PropTypes.array + }, + + render() { + let Component = resourceMap[this.props.mimetype] || Other; + + return ( +
+ +
+ ); + } +}); + +export default MediaPlayer; diff --git a/js/components/ascribe_media/resource_viewer.js b/js/components/ascribe_media/resource_viewer.js deleted file mode 100644 index 012228c6..00000000 --- a/js/components/ascribe_media/resource_viewer.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -import React from 'react'; -import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; - -/** - * This is the component that implements display-specific functionality. - * - * ResourceViewer handles the following mimetypes: - * - image - * - video - * - audio - * - pdf - * - other - */ - -let resourceMap = { - 'image': 1 -}; - -let ResourceViewer = React.createClass({ - propTypes: { - thumbnail: React.PropTypes.string.isRequired, - mimetype: React.PropTypes.oneOf(['image', 'video', 'audio', 'pdf', 'other']).isRequired - }, - - mixins: [InjectInHeadMixin], - - componentDidMount() { - //this.inject('http://antani.com'); - }, - - render() { - return ( -
- resourceviewer {this.props.thumbnail} {this.props.mimetype} -
- ); - } -}); - -export default ResourceViewer; diff --git a/js/components/edition.js b/js/components/edition.js index 56f2b472..8574b3dc 100644 --- a/js/components/edition.js +++ b/js/components/edition.js @@ -1,11 +1,13 @@ 'use strict'; import React from 'react'; +import MediaPlayer from './ascribe_media/media_player'; import CollapsibleMixin from 'react-bootstrap/lib/CollapsibleMixin'; +import Row from 'react-bootstrap/lib/Row'; +import Col from 'react-bootstrap/lib/Col'; import Button from 'react-bootstrap/lib/Button'; - -import ResourceViewer from './ascribe_media/resource_viewer'; +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import EditionActions from '../actions/edition_actions'; import AclButtonList from './ascribe_buttons/acl_button_list'; @@ -25,6 +27,11 @@ let Edition = React.createClass({ render() { let thumbnail = this.props.edition.thumbnail; let mimetype = this.props.edition.digital_work.mime; + let extraData = null; + + if (this.props.edition.digital_work.encoding_urls) { + extraData = this.props.edition.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label } }); + } let bitcoinIdValue = ( {this.props.edition.bitcoin_id} @@ -35,12 +42,19 @@ let Edition = React.createClass({ ); return ( -
-
- -
-
+ + + +

+ +

+ + -
- -
+ + ); } }); diff --git a/js/mixins/inject_in_head_mixin.js b/js/mixins/inject_in_head_mixin.js index 42e3b6dc..b537246b 100644 --- a/js/mixins/inject_in_head_mixin.js +++ b/js/mixins/inject_in_head_mixin.js @@ -2,11 +2,11 @@ let mapAttr = { link: 'href', - source: 'src' + script: 'src' }; -let mapExt = { - js: 'source', +let mapTag = { + js: 'script', css: 'link' }; @@ -24,35 +24,46 @@ let InjectInHeadMixin = { return document.querySelector(query); }, - injectTag(tag, src){ - if (InjectInHeadMixin.isPresent(tag, src)) { - return; - } + injectTag(tag, src) { + let promise = new Promise((resolve, reject) => { + if (InjectInHeadMixin.isPresent(tag, src)) { + resolve(); + } else { + let attr = mapAttr[tag]; + let element = document.createElement(tag); + if (tag === 'script') { + element.onload = () => resolve(); + element.onerror = () => reject(); + } else { + resolve(); + } + document.head.appendChild(element); + element[attr] = src; + if (tag === 'link') { + element.rel = 'stylesheet'; + } + } + }); - let attr = mapAttr[tag]; - let element = document.createElement(tag); - document.head.appendChild(element); - element[attr] = src; + return promise; }, injectStylesheet(src) { - this.injectTag('link', src); + return InjectInHeadMixin.injectTag('link', src); }, injectScript(src) { - this.injectTag('source', src); + return InjectInHeadMixin.injectTag('source', src); }, inject(src) { let ext = src.split('.').pop(); - try { - let tag = mapAttr(src); - } catch (e) { - throw new Error(`Cannot inject ${src} in the DOM, cannot guess the tag name from extension ${ext}. Valid extensions are "js" and "css".`); + let tag = mapTag[ext]; + if (!tag) { + throw new Error(`Cannot inject ${src} in the DOM, cannot guess the tag name from extension "${ext}". Valid extensions are "js" and "css".`); } - // ES6Lint says tag is not defined, pls fix - // - Tim - InjectInHeadMixin.injectTag(tag, src); + + return InjectInHeadMixin.injectTag(tag, src); } }; diff --git a/package.json b/package.json index 5de03b4e..2e9d596b 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "object-assign": "^2.0.0", "react": "^0.13.2", "react-bootstrap": "~0.22.6", + "react-datepicker": "~0.8.0", "react-router": "^0.13.3", + "shmui": "^0.1.0", "uglifyjs": "^2.4.10", "react-datepicker": "~0.8.0" }, diff --git a/sass/ascribe_media_player.scss b/sass/ascribe_media_player.scss new file mode 100644 index 00000000..9dada7f6 --- /dev/null +++ b/sass/ascribe_media_player.scss @@ -0,0 +1,18 @@ +.ascribe-media-player { + margin-bottom: 1em; + video { + width: 100%; + height: 100%; + } + + img { + width: 100%; + height: 100%; + } + + .media-other { + font-size: 500%; + color: #cccccc; + } +} + diff --git a/sass/main.scss b/sass/main.scss index f8fa5791..5dd95ade 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -10,6 +10,7 @@ @import 'ascribe_piece_list_bulk_modal'; @import 'ascribe_piece_list_toolbar'; @import 'ascribe_edition'; +@import 'ascribe_media_player'; @import 'offset_right'; .hidden {