diff --git a/app/javascript/flavours/glitch/blurhash.js b/app/javascript/flavours/glitch/blurhash.js new file mode 100644 index 000000000..5adcc3e77 --- /dev/null +++ b/app/javascript/flavours/glitch/blurhash.js @@ -0,0 +1,112 @@ +const DIGIT_CHARACTERS = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '#', + '$', + '%', + '*', + '+', + ',', + '-', + '.', + ':', + ';', + '=', + '?', + '@', + '[', + ']', + '^', + '_', + '{', + '|', + '}', + '~', +]; + +export const decode83 = (str) => { + let value = 0; + let c, digit; + + for (let i = 0; i < str.length; i++) { + c = str[i]; + digit = DIGIT_CHARACTERS.indexOf(c); + value = value * 83 + digit; + } + + return value; +}; + +export const intToRGB = int => ({ + r: Math.max(0, (int >> 16)), + g: Math.max(0, (int >> 8) & 255), + b: Math.max(0, (int & 255)), +}); + +export const getAverageFromBlurhash = blurhash => { + if (!blurhash) { + return null; + } + + return intToRGB(decode83(blurhash.slice(2, 6))); +}; diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 0f8c94b11..782fd918e 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -378,8 +378,9 @@ class Status extends ImmutablePureComponent { } }; - handleOpenVideo = (media, options) => { - this.props.onOpenVideo(this.props.status.get('id'), media, options); + handleOpenVideo = (options) => { + const { status } = this.props; + this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options); } handleOpenMedia = (media, index) => { diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 40bf370f3..4cc1d1af5 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -68,8 +68,8 @@ export default class DetailedStatus extends ImmutablePureComponent { e.stopPropagation(); } - handleOpenVideo = (media, options) => { - this.props.onOpenVideo(media, options); + handleOpenVideo = (options) => { + this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options); } _measureHeight (heightJustChanged) { diff --git a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js index f9d4bb2f3..fc98cc6af 100644 --- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js @@ -4,12 +4,10 @@ import PropTypes from 'prop-types'; import Audio from 'flavours/glitch/features/audio'; import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; -import Icon from 'flavours/glitch/components/icon'; +import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; -const mapStateToProps = (state, { status }) => ({ - account: state.getIn(['accounts', status.get('account')]), +const mapStateToProps = (state, { statusId }) => ({ + accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']), }); export default @connect(mapStateToProps) @@ -17,27 +15,21 @@ class AudioModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - status: ImmutablePropTypes.map, + statusId: PropTypes.string.isRequired, + accountStaticAvatar: PropTypes.string.isRequired, options: PropTypes.shape({ autoPlay: PropTypes.bool, }), - account: ImmutablePropTypes.map, onClose: PropTypes.func.isRequired, + onChangeBackgroundColor: PropTypes.func.isRequired, }; static contextTypes = { router: PropTypes.object, }; - handleStatusClick = e => { - if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); - } - } - render () { - const { media, status, account } = this.props; + const { media, accountStaticAvatar, statusId, onClose } = this.props; const options = this.props.options || {}; return ( @@ -48,7 +40,7 @@ class AudioModal extends ImmutablePureComponent { alt={media.get('description')} duration={media.getIn(['meta', 'original', 'duration'], 0)} height={150} - poster={media.get('preview_url') || account.get('avatar_static')} + poster={media.get('preview_url') || accountStaticAvatar} backgroundColor={media.getIn(['meta', 'colors', 'background'])} foregroundColor={media.getIn(['meta', 'colors', 'foreground'])} accentColor={media.getIn(['meta', 'colors', 'accent'])} @@ -56,11 +48,9 @@ class AudioModal extends ImmutablePureComponent { /> - {status && ( -
- -
- )} +
+ {statusId &&
); } diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index 7290a914a..e3f3ec6de 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -11,6 +11,7 @@ import ImageLoader from './image_loader'; import Icon from 'flavours/glitch/components/icon'; import GIFV from 'flavours/glitch/components/gifv'; import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; +import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, @@ -18,111 +19,6 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -const digitCharacters = [ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', - 'O', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - 'X', - 'Y', - 'Z', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - '#', - '$', - '%', - '*', - '+', - ',', - '-', - '.', - ':', - ';', - '=', - '?', - '@', - '[', - ']', - '^', - '_', - '{', - '|', - '}', - '~', -]; - -const decode83 = (str) => { - let value = 0; - let c, digit; - - for (let i = 0; i < str.length; i++) { - c = str[i]; - digit = digitCharacters.indexOf(c); - value = value * 83 + digit; - } - - return value; -}; - -const decodeRGB = int => ({ - r: Math.max(0, (int >> 16)), - g: Math.max(0, (int >> 8) & 255), - b: Math.max(0, (int & 255)), -}); - export default @injectIntl class MediaModal extends ImmutablePureComponent { @@ -234,7 +130,7 @@ class MediaModal extends ImmutablePureComponent { const blurhash = media.getIn([index, 'blurhash']); if (blurhash) { - const backgroundColor = decodeRGB(decode83(blurhash.slice(2, 6))); + const backgroundColor = getAverageFromBlurhash(blurhash); onChangeBackgroundColor(backgroundColor); } } diff --git a/app/javascript/flavours/glitch/features/ui/components/video_modal.js b/app/javascript/flavours/glitch/features/ui/components/video_modal.js index 62273f252..6b6e615a6 100644 --- a/app/javascript/flavours/glitch/features/ui/components/video_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/video_modal.js @@ -3,6 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'flavours/glitch/features/video'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; +import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; export default class VideoModal extends ImmutablePureComponent { @@ -19,10 +21,21 @@ export default class VideoModal extends ImmutablePureComponent { defaultVolume: PropTypes.number, }), onClose: PropTypes.func.isRequired, + onChangeBackgroundColor: PropTypes.func.isRequired, }; + componentDidMount () { + const { media, onChangeBackgroundColor, onClose } = this.props; + + const backgroundColor = getAverageFromBlurhash(media.get('blurhash')); + + if (backgroundColor) { + onChangeBackgroundColor(backgroundColor); + } + } + render () { - const { media, onClose } = this.props; + const { media, statusId, onClose } = this.props; const options = this.props.options || {}; return ( @@ -41,6 +54,10 @@ export default class VideoModal extends ImmutablePureComponent { alt={media.get('description')} /> + +
+ {statusId &&
); } diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index 0d267c3a1..6ce4ca85b 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { fromJS, is } from 'immutable'; +import { is } from 'immutable'; import { throttle, debounce } from 'lodash'; import classNames from 'classnames'; import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen'; @@ -509,25 +509,13 @@ class Video extends React.PureComponent { } handleOpenVideo = () => { - const { src, preview, width, height, alt } = this.props; - - const media = fromJS({ - type: 'video', - url: src, - preview_url: preview, - description: alt, - width, - height, - }); + this.video.pause(); - const options = { + this.props.onOpenVideo({ startTime: this.video.currentTime, autoPlay: !this.state.paused, defaultVolume: this.state.volume, - }; - - this.video.pause(); - this.props.onOpenVideo(media, options); + }); } handleCloseVideo = () => {