From 730de0dfd65a5aa36fdecc9c9bb50aa070fea7b5 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 10:26:40 +0000 Subject: [PATCH 1/6] rm console.log --- src/components/presentational/CardSource.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/presentational/CardSource.js b/src/components/presentational/CardSource.js index 9eab0af..6d4aacc 100644 --- a/src/components/presentational/CardSource.js +++ b/src/components/presentational/CardSource.js @@ -42,9 +42,6 @@ const CardSource = ({ source, isLoading, onClickHandler }) => { thumbnail = imgs.length > 0 ? imgs[0] : null } - console.log(!!thumbnail) - console.log(thumbnail) - return (
{isLoading From cf03cef3f7c4284cda759319df1516fc838f7442 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 11:10:36 +0000 Subject: [PATCH 2/6] small loading spinner for thumbnail; restructure source overlay content --- src/components/SourceOverlay.jsx | 53 ++++++++++++++++----- src/components/presentational/CardSource.js | 28 +++++++---- src/components/presentational/Spinner.js | 4 +- src/scss/loading.scss | 6 +++ 4 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx index 8f6da4e..2999bfe 100644 --- a/src/components/SourceOverlay.jsx +++ b/src/components/SourceOverlay.jsx @@ -62,6 +62,12 @@ class SourceOverlay extends React.Component { ) } + renderNoSupport(ext) { + return ( + + ) + } + renderTestimony() { return (
@@ -70,21 +76,44 @@ class SourceOverlay extends React.Component { ) } - _renderSwitch() { - switch(this.props.source.type) { - case 'Video': - return this.renderVideo() - case 'Photo': - return this.renderPhoto() - case 'Photobook': - return this.renderPhotobook() - case 'Eyewitness Testimony': - return this.renderTestimony() + _renderPath(path) { + // render conditionally based on the extension + switch (true) { + case /\.(png|jpg)$/.test(path): + console.log('image') + return
{path}
+ case /\.(mp4)$/.test(path): + console.log('video') + return
{path}
+ case /\.(md)$/.test(path): + console.log('text') + return
{path}
default: - return this.renderError() + console.log('unsupported extension') + return this.renderNoSupport(path.split('.')[1]) } } + _renderContent() { + return ( +
+ {this.props.source.paths.map(this._renderPath)} +
+ ) + // switch(this.props.source.type) { + // case 'Video': + // return this.renderVideo() + // case 'Photo': + // return this.renderPhoto() + // case 'Photobook': + // return this.renderPhotobook() + // case 'Eyewitness Testimony': + // return this.renderTestimony() + // default: + // return this.renderError() + // } + } + render() { if (typeof(this.props.source) !== 'object') { return this.renderError() @@ -100,7 +129,7 @@ class SourceOverlay extends React.Component {
{this.props.source.id}
- {this._renderSwitch()} + {this._renderContent()}
diff --git a/src/components/presentational/CardSource.js b/src/components/presentational/CardSource.js index 6d4aacc..b3bca21 100644 --- a/src/components/presentational/CardSource.js +++ b/src/components/presentational/CardSource.js @@ -35,12 +35,21 @@ const CardSource = ({ source, isLoading, onClickHandler }) => { ) } + const isImgUrl = /\.(jpg|png)$/ let thumbnail = source.thumbnail - if (!thumbnail || thumbnail === '') { + + if (!thumbnail || thumbnail === '' || !thumbnail.match(isImgUrl)) { // default to first image in paths, null if no images - const imgs = source.paths.filter(p => p.match(/\.(jpg|png)$/)) + const imgs = source.paths.filter(p => p.match(isImgUrl)) thumbnail = imgs.length > 0 ? imgs[0] : null } + console.log(thumbnail) + + const fallbackIcon = ( + + {renderIconText(source.type)} + + ) return (
@@ -49,12 +58,15 @@ const CardSource = ({ source, isLoading, onClickHandler }) => { : (
onClickHandler(source)}> {!!thumbnail ? ( - - ) : ( - - {renderIconText(source.type)} - - )} + } + unloader={fallbackIcon} + width={30} + height={30} + /> + ) : fallbackIcon}

{source.id}

)} diff --git a/src/components/presentational/Spinner.js b/src/components/presentational/Spinner.js index 062bd83..f3c483c 100644 --- a/src/components/presentational/Spinner.js +++ b/src/components/presentational/Spinner.js @@ -1,8 +1,8 @@ import React from 'react'; -const Spinner = () => { +const Spinner = ({ small }) => { return ( -
+
diff --git a/src/scss/loading.scss b/src/scss/loading.scss index 1fdcff6..063c622 100644 --- a/src/scss/loading.scss +++ b/src/scss/loading.scss @@ -42,6 +42,12 @@ https://github.com/tobiasahlin/SpinKit/blob/master/LICENSE position: relative; margin: 10px auto; + + &.small { + width: 15px; + height: 15px; + margin: 5px 20px 5px 10px; + } } .double-bounce, .double-bounce-overlay { From c98d4cc73df5473d9815482d16a254c0eba6ef79 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 11:48:40 +0000 Subject: [PATCH 3/6] render source overlay content by path ext, rather than type --- src/components/SourceOverlay.jsx | 170 ++++++++------------ src/components/presentational/CardSource.js | 1 - src/scss/mediaoverlay.scss | 31 ++-- 3 files changed, 92 insertions(+), 110 deletions(-) diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx index 2999bfe..e5a79f6 100644 --- a/src/components/SourceOverlay.jsx +++ b/src/components/SourceOverlay.jsx @@ -4,148 +4,118 @@ import { Player } from 'video-react' import Spinner from './presentational/Spinner' import NoSource from './presentational/NoSource' -class SourceOverlay extends React.Component { - constructor(props) { - super(props) - this.renderVideo = this.renderVideo.bind(this) - this.renderPhoto = this.renderPhoto.bind(this) - this.renderPhotobook = this.renderPhotobook.bind(this) - this.renderTestimony = this.renderTestimony.bind(this) +function SourceOverlay ({ source, onCancel }) { + function renderImage(path) { + return ( +
+ } + unloader={} + /> +
+ ) } - renderVideo() { + function renderVideo(path) { // NB: assume only one video return (
) } - renderPhoto() { - return ( -
- } - unloader={} - /> -
- ) + function renderText(path) { + return (
{path}
) } - renderPhotobook() { - return ( -
- {this.props.source.paths.map((url, idx) => ( - } - unloader={} - /> + // renderImagebook() { + // return ( + //
+ // {source.paths.map((url, idx) => ( + // } + // unloader={} + // /> + // + // ))} + //
+ // ) + // } - ))} -
- ) - } - - renderError() { + function renderError() { return ( ) } - renderNoSupport(ext) { + function renderNoSupport(ext) { return ( - + ) } - renderTestimony() { - return ( - - ) - } - - _renderPath(path) { - // render conditionally based on the extension + function _renderPath(path) { switch (true) { case /\.(png|jpg)$/.test(path): - console.log('image') - return
{path}
+ return renderImage(path) case /\.(mp4)$/.test(path): - console.log('video') - return
{path}
+ return renderVideo(path) case /\.(md)$/.test(path): - console.log('text') - return
{path}
+ return renderText(path) default: console.log('unsupported extension') - return this.renderNoSupport(path.split('.')[1]) + return renderNoSupport(path.split('.')[1]) } } - _renderContent() { + function _renderContent() { return ( -
- {this.props.source.paths.map(this._renderPath)} -
+ + {source.paths.map(_renderPath)} + ) - // switch(this.props.source.type) { - // case 'Video': - // return this.renderVideo() - // case 'Photo': - // return this.renderPhoto() - // case 'Photobook': - // return this.renderPhotobook() - // case 'Eyewitness Testimony': - // return this.renderTestimony() - // default: - // return this.renderError() - // } } - render() { - if (typeof(this.props.source) !== 'object') { - return this.renderError() - } - const {id, url, title, date, type, affil_1, affil_2} = this.props.source - return ( -
-
-
-
- -
-
{this.props.source.id}
+ if (typeof(source) !== 'object') { + return renderError() + } + const {id, url, title, date, type, affil_1, affil_2} = source + return ( +
+
+
+
+
-
- {this._renderContent()} -
-
-
- {id ?
{id}
: null} - {title?
{title}
: null} -
- {type ?
Type: {type}
: null} - {date ?
Date:{date}
: null} -
- {url ? : null} -
+
{source.id}
+
+
+ {_renderContent()} +
+
+
+ {id ?
{id}
: null} + {title?
{title}
: null} +
+ {type ?
Type: {type}
: null} + {date ?
Date:{date}
: null} +
+ {url ? : null}
- ) - } +
+ ) } export default SourceOverlay diff --git a/src/components/presentational/CardSource.js b/src/components/presentational/CardSource.js index b3bca21..9b3da5b 100644 --- a/src/components/presentational/CardSource.js +++ b/src/components/presentational/CardSource.js @@ -43,7 +43,6 @@ const CardSource = ({ source, isLoading, onClickHandler }) => { const imgs = source.paths.filter(p => p.match(isImgUrl)) thumbnail = imgs.length > 0 ? imgs[0] : null } - console.log(thumbnail) const fallbackIcon = ( diff --git a/src/scss/mediaoverlay.scss b/src/scss/mediaoverlay.scss index 9046ba7..891280f 100644 --- a/src/scss/mediaoverlay.scss +++ b/src/scss/mediaoverlay.scss @@ -48,34 +48,45 @@ $header-inset: 10px; .mo-container { background-color: rgba(239, 239, 239, 0.9); - max-width: $panel-width; - min-width: $panel-width; - max-height: $panel-height; - min-height: $panel-height; + // max-width: $panel-width; + // min-width: $panel-width; + // max-height: $panel-height; + // min-height: $panel-height; display: flex; flex-direction: column; - justify-content: flex-start; align-items: center; + height: 80vh; + max-width: 90vw; .mo-media-container { flex: 1; display: flex; + flex-direction: column; justify-content: center; align-items: center; } } .mo-media-container { - // padding-top: 3*$header-inset; font-family: "Lato", Helvetica, sans-serif; - // max-height: $vimeo-height; min-width: 100%; - max-height: 500px; + max-height: 60vh; + overflow-y: auto; .media-player { width: 100%; max-width: $vimeo-width; } + + .media-content { + display: flex; + flex-direction: column; + } + + // NB: topcushion seems to be necessary with certain overflows.. + &.topcushion { + padding-top: 150px; + } } .mo-meta-container { @@ -146,8 +157,10 @@ $header-inset: 10px; .source-image-container { padding: 0 25px; - overflow-y: scroll; height: 100%; + display: flex; + justify-content: center; + align-items: center; } .source-image, .source-video { From 43a861ca6f9396041b61d18d9e596ad37a5d16be Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 12:30:20 +0000 Subject: [PATCH 4/6] describe contents of source in overlay --- src/components/Dashboard.jsx | 11 +++++ src/components/SourceOverlay.jsx | 71 +++++++++++++++++++++++------ src/reducers/schema/sourceSchema.js | 1 + src/scss/mediaoverlay.scss | 3 +- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index d734de0..11263d6 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -154,7 +154,18 @@ function mapDispatchToProps(dispatch) { }; } +function injectSource(id) { + return state => ({ + ...state, + app: { + ...state.app, + source: state.domain.sources[id] + } + }) +} + export default connect( state => state, + // injectSource('src7'), mapDispatchToProps, )(Dashboard); diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx index e5a79f6..02f40bf 100644 --- a/src/components/SourceOverlay.jsx +++ b/src/components/SourceOverlay.jsx @@ -64,24 +64,65 @@ function SourceOverlay ({ source, onCancel }) { ) } - function _renderPath(path) { + function toMedia(path) { + let type; switch (true) { case /\.(png|jpg)$/.test(path): - return renderImage(path) + type = 'Image'; break case /\.(mp4)$/.test(path): - return renderVideo(path) + type = 'Video'; break case /\.(md)$/.test(path): + type = 'Text'; break + default: + type = 'Unknown'; break + } + return { type, path } + } + + function getTypeCounts(media) { + let counts = { Image: 0, Video: 0, Text: 0 } + media.forEach(m => { + counts[m.type] += 1 + }) + return counts + } + + function _renderPath(media) { + const { path, type } = media + switch (type) { + case 'Image': + return renderImage(path) + case 'Video': + return renderVideo(path) + case 'Text': return renderText(path) default: - console.log('unsupported extension') return renderNoSupport(path.split('.')[1]) } } - function _renderContent() { + function _renderCounts(counts) { + const strFor = type => + counts[type] > 0 ? + `${counts[type]} ${type.toLowerCase()}${counts[type] > 1 ? 's': ''}` + : '' + const img = strFor('Image') + const vid = strFor('Video') + const txt = strFor('Text') + + return ( +
+ {img ? `${img}, `: ''} + {(vid && txt) ? `${vid}, `: vid} + {txt} +
+ ) + } + + function _renderContent(media) { return ( - {source.paths.map(_renderPath)} + {media.map(_renderPath)} ) } @@ -89,7 +130,11 @@ function SourceOverlay ({ source, onCancel }) { if (typeof(source) !== 'object') { return renderError() } - const {id, url, title, date, type, affil_1, affil_2} = source + const {id, url, title, paths, date, type, desc} = source + const media = paths.map(toMedia) + const counts = getTypeCounts(media) + + return (
@@ -97,20 +142,20 @@ function SourceOverlay ({ source, onCancel }) {
-
{source.id}
+
{source.title}
- {_renderContent()} + {_renderContent(media)}
- {id ?
{id}
: null} {title?
{title}
: null} -
- {type ?
Type: {type}
: null} +
{_renderCounts(counts)}
+ {type ?
{type}
: null} {date ?
Date:{date}
: null} -
{url ? : null} +
+ {desc ?
{desc}
: null}
diff --git a/src/reducers/schema/sourceSchema.js b/src/reducers/schema/sourceSchema.js index c465ba5..523bf9d 100644 --- a/src/reducers/schema/sourceSchema.js +++ b/src/reducers/schema/sourceSchema.js @@ -2,6 +2,7 @@ import Joi from 'joi'; const sourceSchema = Joi.object().keys({ id: Joi.string().required(), + title: Joi.string().allow(''), thumbnail: Joi.string().allow(''), paths: Joi.array().required(), type: Joi.string().allow(''), diff --git a/src/scss/mediaoverlay.scss b/src/scss/mediaoverlay.scss index 891280f..ebc4a62 100644 --- a/src/scss/mediaoverlay.scss +++ b/src/scss/mediaoverlay.scss @@ -157,7 +157,6 @@ $header-inset: 10px; .source-image-container { padding: 0 25px; - height: 100%; display: flex; justify-content: center; align-items: center; @@ -166,7 +165,7 @@ $header-inset: 10px; .source-image, .source-video { max-width: calc(100% - 20px); max-height: 350px !important; - height: 100%; + // height: 100%; padding: 10px; } From b60d3053c73b07506bcbef7897ef13fce8045c2e Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 12:36:36 +0000 Subject: [PATCH 5/6] fix media describing logic --- src/components/SourceOverlay.jsx | 6 +++--- src/reducers/schema/sourceSchema.js | 4 ++-- src/scss/mediaoverlay.scss | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx index 02f40bf..0ab9db8 100644 --- a/src/components/SourceOverlay.jsx +++ b/src/components/SourceOverlay.jsx @@ -112,9 +112,9 @@ function SourceOverlay ({ source, onCancel }) { return (
- {img ? `${img}, `: ''} - {(vid && txt) ? `${vid}, `: vid} - {txt} + {img ? img : ''} + {vid ? `, ${vid}`: ''} + {txt ? `, ${txt}`: ''}
) } diff --git a/src/reducers/schema/sourceSchema.js b/src/reducers/schema/sourceSchema.js index 523bf9d..1b61ac0 100644 --- a/src/reducers/schema/sourceSchema.js +++ b/src/reducers/schema/sourceSchema.js @@ -6,8 +6,8 @@ const sourceSchema = Joi.object().keys({ thumbnail: Joi.string().allow(''), paths: Joi.array().required(), type: Joi.string().allow(''), - affil_1: Joi.string().allow(''), - affil_2: Joi.string().allow(''), + // affil_1: Joi.string().allow(''), + // affil_2: Joi.string().allow(''), url: Joi.string().allow(''), desc: Joi.string().allow(''), parent: Joi.string().allow(''), diff --git a/src/scss/mediaoverlay.scss b/src/scss/mediaoverlay.scss index ebc4a62..0fa1270 100644 --- a/src/scss/mediaoverlay.scss +++ b/src/scss/mediaoverlay.scss @@ -57,6 +57,7 @@ $header-inset: 10px; align-items: center; height: 80vh; max-width: 90vw; + box-shadow: 0 19px 19px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22); .mo-media-container { flex: 1; From 5552aa563f4e8a858265bc32387adee0db357fd0 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Thu, 3 Jan 2019 13:04:55 +0000 Subject: [PATCH 6/6] add support for markdown text files --- package.json | 1 + src/components/Dashboard.jsx | 1 - src/components/Md.jsx | 41 ++++++++++++++++++++++++++++++++ src/components/SourceOverlay.jsx | 29 +++++++++------------- src/js/utilities.js | 15 ++++++++++++ src/scss/mediaoverlay.scss | 4 ++-- yarn.lock | 7 +++--- 7 files changed, 74 insertions(+), 24 deletions(-) create mode 100644 src/components/Md.jsx diff --git a/package.json b/package.json index b4a1c53..c5d556e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "es6-promise": "^4.1.1", "joi": "^14.0.1", "leaflet": "^1.0.3", + "marked": "^0.6.0", "normalizr": "^3.2.3", "object-hash": "^1.3.0", "react": "^16.6.3", diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 11263d6..066cffd 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -166,6 +166,5 @@ function injectSource(id) { export default connect( state => state, - // injectSource('src7'), mapDispatchToProps, )(Dashboard); diff --git a/src/components/Md.jsx b/src/components/Md.jsx new file mode 100644 index 0000000..2510359 --- /dev/null +++ b/src/components/Md.jsx @@ -0,0 +1,41 @@ +import React from 'react' +import PropTypes from 'prop-types' +import marked from 'marked' + +class Md extends React.Component { + constructor(props) { + super(props) + this.state = { md: null, error: null } + } + + componentDidMount() { + fetch(this.props.path) + .then(resp => resp.text()) + .then(text => { + this.setState({ md: marked(text) }) + }) + .catch(err => { + this.setState({ error: true }) + }) + } + + render() { + if (this.state.md && !this.state.error) { + return ( +
+ ) + } else if (this.state.error) { + return this.props.unloader ||
Error: couldn't load source
+ } else { + return this.props.loader + } + } +} + +Md.propTypes = { + loader: PropTypes.func, + unloader: PropTypes.func, + path: PropTypes.string.isRequired +} + +export default Md diff --git a/src/components/SourceOverlay.jsx b/src/components/SourceOverlay.jsx index 0ab9db8..452eebf 100644 --- a/src/components/SourceOverlay.jsx +++ b/src/components/SourceOverlay.jsx @@ -1,8 +1,10 @@ import React from 'react' import Img from 'react-image' import { Player } from 'video-react' +import Md from './Md.jsx' import Spinner from './presentational/Spinner' import NoSource from './presentational/NoSource' +// TODO: move render functions into presentational components function SourceOverlay ({ source, onCancel }) { function renderImage(path) { @@ -32,26 +34,17 @@ function SourceOverlay ({ source, onCancel }) { } function renderText(path) { - return (
{path}
) + return ( +
+ } + unloader={renderError()} + /> +
+ ) } - // renderImagebook() { - // return ( - //
- // {source.paths.map((url, idx) => ( - // } - // unloader={} - // /> - // - // ))} - //
- // ) - // } - function renderError() { return ( diff --git a/src/js/utilities.js b/src/js/utilities.js index e9692e2..4ba3755 100644 --- a/src/js/utilities.js +++ b/src/js/utilities.js @@ -72,3 +72,18 @@ export function formatterWithYear(datetime) { export function formatter(datetime) { return d3.timeFormat("%d %b, %H:%M")(datetime); } + +/** + * Debugging function: put in place of a mapStateToProps function to + * view that source modal by default + */ +function injectSource(id) { + return state => ({ + ...state, + app: { + ...state.app, + source: state.domain.sources[id] + } + }) +} + diff --git a/src/scss/mediaoverlay.scss b/src/scss/mediaoverlay.scss index 0fa1270..ae72493 100644 --- a/src/scss/mediaoverlay.scss +++ b/src/scss/mediaoverlay.scss @@ -156,8 +156,8 @@ $header-inset: 10px; } } -.source-image-container { - padding: 0 25px; +.source-image-container, .source-text-container { + padding: 0 10em; display: flex; justify-content: center; align-items: center; diff --git a/yarn.lock b/yarn.lock index a455221..9f5e536 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3962,7 +3962,6 @@ lodash.tail@^4.1.1: lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" - integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@~4.17.10: version "4.17.11" @@ -4036,6 +4035,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.0.tgz#a18d01cfdcf8d15c3c455b71c8329e5e0f01faa1" + matcher@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" @@ -5244,7 +5247,6 @@ redux@^3.6.0: redux@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" - integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg== dependencies: loose-envify "^1.4.0" symbol-observable "^1.2.0" @@ -6403,7 +6405,6 @@ verror@1.10.0: video-react@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/video-react/-/video-react-0.13.1.tgz#5d0dc68748f9b12e118beea1998d6ae5f6cbd6ba" - integrity sha512-AeGSpddfHv0UxeJztWUALYEjCdzXM1QdtQ5GD1VUd3vxcgwgIfB7EzFKcewRevSHHK8TDmjNksbvbWRobF/QeA== dependencies: classnames "^2.2.3" lodash.throttle "^4.1.1"