diff --git a/app/javascript/mastodon/blurhash.js b/app/javascript/mastodon/blurhash.js
new file mode 100644
index 00000000000..5adcc3e7702
--- /dev/null
+++ b/app/javascript/mastodon/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/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 27d96e588d5..22098d57ed0 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -192,8 +192,9 @@ class Status extends ImmutablePureComponent {
return
;
}
- handleOpenVideo = (media, options) => {
- this.props.onOpenVideo(this._properStatus().get('id'), media, options);
+ handleOpenVideo = (options) => {
+ const status = this._properStatus();
+ this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
}
handleOpenMedia = (media, index) => {
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index cd29b548950..e20557eb335 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -58,8 +58,8 @@ 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);
}
handleExpandedToggle = () => {
diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js
index a80776b2232..0676bd9cf1c 100644
--- a/app/javascript/mastodon/features/ui/components/audio_modal.js
+++ b/app/javascript/mastodon/features/ui/components/audio_modal.js
@@ -4,13 +4,11 @@ import PropTypes from 'prop-types';
import Audio from 'mastodon/features/audio';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
import { previewState } from './video_modal';
-import classNames from 'classnames';
-import Icon from 'mastodon/components/icon';
+import Footer from 'mastodon/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)
@@ -18,12 +16,13 @@ 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 = {
@@ -52,15 +51,8 @@ class AudioModal extends ImmutablePureComponent {
}
}
- 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 (
@@ -71,7 +63,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'])}
@@ -79,11 +71,9 @@ class AudioModal extends ImmutablePureComponent {
/>
- {status && (
-
- )}
+
+ {statusId && }
+
);
}
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index 58cef1e9da5..7fe7ed09473 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -12,6 +12,7 @@ import Icon from 'mastodon/components/icon';
import GIFV from 'mastodon/components/gifv';
import { disableSwiping } from 'mastodon/initial_state';
import Footer from 'mastodon/features/picture_in_picture/components/footer';
+import { getAverageFromBlurhash } from 'mastodon/blurhash';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -21,111 +22,6 @@ const messages = defineMessages({
export const previewState = 'previewMediaModal';
-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 {
@@ -224,7 +120,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/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index 2c3c026c8f5..2f13a175a1d 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -3,6 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Video from 'mastodon/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component';
+import Footer from 'mastodon/features/picture_in_picture/components/footer';
+import { getAverageFromBlurhash } from 'mastodon/blurhash';
export const previewState = 'previewVideoModal';
@@ -17,6 +19,7 @@ export default class VideoModal extends ImmutablePureComponent {
defaultVolume: PropTypes.number,
}),
onClose: PropTypes.func.isRequired,
+ onChangeBackgroundColor: PropTypes.func.isRequired,
};
static contextTypes = {
@@ -24,29 +27,35 @@ export default class VideoModal extends ImmutablePureComponent {
};
componentDidMount () {
- if (this.context.router) {
- const history = this.context.router.history;
+ const { router } = this.context;
+ const { media, onChangeBackgroundColor, onClose } = this.props;
- history.push(history.location.pathname, previewState);
+ if (router) {
+ router.history.push(router.history.location.pathname, previewState);
+ this.unlistenHistory = router.history.listen(() => onClose());
+ }
- this.unlistenHistory = history.listen(() => {
- this.props.onClose();
- });
+ const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
+
+ if (backgroundColor) {
+ onChangeBackgroundColor(backgroundColor);
}
}
componentWillUnmount () {
- if (this.context.router) {
+ const { router } = this.context;
+
+ if (router) {
this.unlistenHistory();
- if (this.context.router.history.location.state === previewState) {
- this.context.router.history.goBack();
+ if (router.history.location.state === previewState) {
+ router.history.goBack();
}
}
}
render () {
- const { media, onClose } = this.props;
+ const { media, statusId, onClose } = this.props;
const options = this.props.options || {};
return (
@@ -65,6 +74,10 @@ export default class VideoModal extends ImmutablePureComponent {
alt={media.get('description')}
/>
+
+
+ {statusId && }
+
);
}
diff --git a/app/javascript/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 46eaebd9b9d..a2dccdfc043 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/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 '../ui/util/fullscreen';
@@ -495,25 +495,13 @@ class Video extends React.PureComponent {
}
handleOpenVideo = () => {
- const { src, preview, width, height, alt } = this.props;
+ this.video.pause();
- const media = fromJS({
- type: 'video',
- url: src,
- preview_url: preview,
- description: alt,
- width,
- height,
- });
-
- 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 = () => {