Merge pull request #1531 from ClearlyClaire/glitch-soc/features/upstream-media-modal
Port upstream's new media modallolsob-rspec
commit
ac44ffaa45
|
@ -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)));
|
||||||
|
};
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import 'wicg-inert';
|
import 'wicg-inert';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
import { multiply } from 'color-blend';
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -11,6 +12,11 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
backgroundColor: PropTypes.shape({
|
||||||
|
r: PropTypes.number,
|
||||||
|
g: PropTypes.number,
|
||||||
|
b: PropTypes.number,
|
||||||
|
}),
|
||||||
noEsc: PropTypes.bool,
|
noEsc: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,9 +74,7 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
this.activeElement.focus({ preventScroll: true });
|
this.activeElement.focus({ preventScroll: true });
|
||||||
this.activeElement = null;
|
this.activeElement = null;
|
||||||
}).catch((error) => {
|
}).catch(console.error);
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.handleModalClose();
|
this.handleModalClose();
|
||||||
}
|
}
|
||||||
|
@ -120,10 +124,16 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let backgroundColor = null;
|
||||||
|
|
||||||
|
if (this.props.backgroundColor) {
|
||||||
|
backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root' ref={this.setRef}>
|
<div className='modal-root' ref={this.setRef}>
|
||||||
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} />
|
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.7)` : null }} />
|
||||||
<div role='dialog' className='modal-root__container'>{children}</div>
|
<div role='dialog' className='modal-root__container'>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -378,22 +378,26 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = (media, options) => {
|
handleOpenVideo = (options) => {
|
||||||
this.props.onOpenVideo(media, options);
|
const { status } = this.props;
|
||||||
|
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpenMedia = (media, index) => {
|
||||||
|
this.props.onOpenMedia(this.props.status.get('id'), media, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyOpenMedia = e => {
|
handleHotkeyOpenMedia = e => {
|
||||||
const { status, onOpenMedia, onOpenVideo } = this.props;
|
const { status, onOpenMedia, onOpenVideo } = this.props;
|
||||||
|
const statusId = status.get('id');
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
// TODO: toggle play/paused?
|
onOpenVideo(statusId, status.getIn(['media_attachments', 0]), { startTime: 0 });
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
|
||||||
onOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
|
|
||||||
} else {
|
} else {
|
||||||
onOpenMedia(status.get('media_attachments'), 0);
|
onOpenMedia(statusId, status.get('media_attachments'), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,7 +661,7 @@ class Status extends ImmutablePureComponent {
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
||||||
hidden={isCollapsed || !isExpanded}
|
hidden={isCollapsed || !isExpanded}
|
||||||
onOpenMedia={this.props.onOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
defaultWidth={this.props.cachedMediaWidth}
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
visible={this.state.showMedia}
|
visible={this.state.showMedia}
|
||||||
|
@ -675,7 +679,7 @@ class Status extends ImmutablePureComponent {
|
||||||
} else if (status.get('card') && settings.get('inline_preview_cards')) {
|
} else if (status.get('card') && settings.get('inline_preview_cards')) {
|
||||||
media = (
|
media = (
|
||||||
<Card
|
<Card
|
||||||
onOpenMedia={this.props.onOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
card={status.get('card')}
|
card={status.get('card')}
|
||||||
compact
|
compact
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { PureComponent, Fragment } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { List as ImmutableList, fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import { getLocale } from 'mastodon/locales';
|
import { getLocale } from 'mastodon/locales';
|
||||||
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
|
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
|
||||||
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
||||||
|
@ -30,6 +30,8 @@ export default class MediaContainer extends PureComponent {
|
||||||
media: null,
|
media: null,
|
||||||
index: null,
|
index: null,
|
||||||
time: null,
|
time: null,
|
||||||
|
backgroundColor: null,
|
||||||
|
options: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
handleOpenMedia = (media, index) => {
|
||||||
|
@ -39,20 +41,32 @@ export default class MediaContainer extends PureComponent {
|
||||||
this.setState({ media, index });
|
this.setState({ media, index });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = (video, time) => {
|
handleOpenVideo = (options) => {
|
||||||
const media = ImmutableList([video]);
|
const { components } = this.props;
|
||||||
|
const { media } = JSON.parse(components[options.componetIndex].getAttribute('data-props'));
|
||||||
|
const mediaList = fromJS(media);
|
||||||
|
|
||||||
document.body.classList.add('with-modals--active');
|
document.body.classList.add('with-modals--active');
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
||||||
|
|
||||||
this.setState({ media, time });
|
this.setState({ media: mediaList, options });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseMedia = () => {
|
handleCloseMedia = () => {
|
||||||
document.body.classList.remove('with-modals--active');
|
document.body.classList.remove('with-modals--active');
|
||||||
document.documentElement.style.marginRight = 0;
|
document.documentElement.style.marginRight = 0;
|
||||||
|
|
||||||
this.setState({ media: null, index: null, time: null });
|
this.setState({
|
||||||
|
media: null,
|
||||||
|
index: null,
|
||||||
|
time: null,
|
||||||
|
backgroundColor: null,
|
||||||
|
options: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackgroundColor = color => {
|
||||||
|
this.setState({ backgroundColor: color });
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -73,6 +87,7 @@ export default class MediaContainer extends PureComponent {
|
||||||
...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
|
...(hashtag ? { hashtag: fromJS(hashtag) } : {}),
|
||||||
|
|
||||||
...(componentName === 'Video' ? {
|
...(componentName === 'Video' ? {
|
||||||
|
componetIndex: i,
|
||||||
onOpenVideo: this.handleOpenVideo,
|
onOpenVideo: this.handleOpenVideo,
|
||||||
} : {
|
} : {
|
||||||
onOpenMedia: this.handleOpenMedia,
|
onOpenMedia: this.handleOpenMedia,
|
||||||
|
@ -85,13 +100,16 @@ export default class MediaContainer extends PureComponent {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<ModalRoot onClose={this.handleCloseMedia}>
|
<ModalRoot backgroundColor={this.state.backgroundColor} onClose={this.handleCloseMedia}>
|
||||||
{this.state.media && (
|
{this.state.media && (
|
||||||
<MediaModal
|
<MediaModal
|
||||||
media={this.state.media}
|
media={this.state.media}
|
||||||
index={this.state.index || 0}
|
index={this.state.index || 0}
|
||||||
time={this.state.time}
|
currentTime={this.state.options?.startTime}
|
||||||
|
autoPlay={this.state.options?.autoPlay}
|
||||||
|
volume={this.state.options?.defaultVolume}
|
||||||
onClose={this.handleCloseMedia}
|
onClose={this.handleCloseMedia}
|
||||||
|
onChangeBackgroundColor={this.setBackgroundColor}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
|
|
|
@ -177,12 +177,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
dispatch(mentionCompose(account, router));
|
dispatch(mentionCompose(account, router));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenMedia (media, index) {
|
onOpenMedia (statusId, media, index) {
|
||||||
dispatch(openModal('MEDIA', { media, index }));
|
dispatch(openModal('MEDIA', { statusId, media, index }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenVideo (media, options) {
|
onOpenVideo (statusId, media, options) {
|
||||||
dispatch(openModal('VIDEO', { media, options }));
|
dispatch(openModal('VIDEO', { statusId, media, options }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlock (status) {
|
onBlock (status) {
|
||||||
|
|
|
@ -114,15 +114,18 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenMedia = attachment => {
|
handleOpenMedia = attachment => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
const statusId = attachment.getIn(['status', 'id']);
|
||||||
|
|
||||||
if (attachment.get('type') === 'video') {
|
if (attachment.get('type') === 'video') {
|
||||||
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
|
dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } }));
|
||||||
} else if (attachment.get('type') === 'audio') {
|
} else if (attachment.get('type') === 'audio') {
|
||||||
this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } }));
|
dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } }));
|
||||||
} else {
|
} else {
|
||||||
const media = attachment.getIn(['status', 'media_attachments']);
|
const media = attachment.getIn(['status', 'media_attachments']);
|
||||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||||
|
|
||||||
this.props.dispatch(openModal('MEDIA', { media, index, status: attachment.get('status') }));
|
dispatch(openModal('MEDIA', { media, index, statusId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ const messages = defineMessages({
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -52,11 +53,19 @@ class Footer extends ImmutablePureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
askReplyConfirmation: PropTypes.bool,
|
askReplyConfirmation: PropTypes.bool,
|
||||||
showReplyCount: PropTypes.bool,
|
showReplyCount: PropTypes.bool,
|
||||||
|
withOpenButton: PropTypes.bool,
|
||||||
|
onClose: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
_performReply = () => {
|
_performReply = () => {
|
||||||
const { dispatch, status } = this.props;
|
const { dispatch, status, onClose } = this.props;
|
||||||
dispatch(replyCompose(status, this.context.router.history));
|
const { router } = this.context;
|
||||||
|
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(replyCompose(status, router.history));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleReplyClick = () => {
|
handleReplyClick = () => {
|
||||||
|
@ -100,8 +109,20 @@ class Footer extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleOpenClick = e => {
|
||||||
|
const { router } = this.context;
|
||||||
|
|
||||||
|
if (e.button !== 0 || !router) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status } = this.props;
|
||||||
|
|
||||||
|
router.history.push(`/statuses/${status.get('id')}`);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, intl, showReplyCount } = this.props;
|
const { status, intl, showReplyCount, withOpenButton } = this.props;
|
||||||
|
|
||||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||||
|
@ -156,6 +177,7 @@ class Footer extends ImmutablePureComponent {
|
||||||
{replyButton}
|
{replyButton}
|
||||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||||
|
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,8 +68,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = (media, options) => {
|
handleOpenVideo = (options) => {
|
||||||
this.props.onOpenVideo(media, options);
|
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
_measureHeight (heightJustChanged) {
|
_measureHeight (heightJustChanged) {
|
||||||
|
|
|
@ -316,11 +316,11 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
handleOpenMedia = (media, index) => {
|
||||||
this.props.dispatch(openModal('MEDIA', { media, index }));
|
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = (media, options) => {
|
handleOpenVideo = (media, options) => {
|
||||||
this.props.dispatch(openModal('VIDEO', { media, options }));
|
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyOpenMedia = e => {
|
handleHotkeyOpenMedia = e => {
|
||||||
|
@ -329,9 +329,7 @@ class Status extends ImmutablePureComponent {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
// TODO: toggle play/paused?
|
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
|
||||||
this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
|
this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
|
||||||
} else {
|
} else {
|
||||||
this.handleOpenMedia(status.get('media_attachments'), 0);
|
this.handleOpenMedia(status.get('media_attachments'), 0);
|
||||||
|
|
|
@ -4,12 +4,10 @@ import PropTypes from 'prop-types';
|
||||||
import Audio from 'flavours/glitch/features/audio';
|
import Audio from 'flavours/glitch/features/audio';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
||||||
import classNames from 'classnames';
|
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { status }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
account: state.getIn(['accounts', status.get('account')]),
|
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -17,27 +15,21 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
statusId: PropTypes.string.isRequired,
|
||||||
|
accountStaticAvatar: PropTypes.string.isRequired,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
}),
|
}),
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
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 () {
|
render () {
|
||||||
const { media, status, account } = this.props;
|
const { media, accountStaticAvatar, statusId, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,7 +40,7 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
height={150}
|
height={150}
|
||||||
poster={media.get('preview_url') || account.get('avatar_static')}
|
poster={media.get('preview_url') || accountStaticAvatar}
|
||||||
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
||||||
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
||||||
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
||||||
|
@ -56,11 +48,9 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{status && (
|
<div className='media-modal__overlay'>
|
||||||
<div className={classNames('media-modal__meta')}>
|
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
|
||||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import ImageLoader from './image_loader';
|
import ImageLoader from './image_loader';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
import GIFV from 'flavours/glitch/components/gifv';
|
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({
|
const messages = defineMessages({
|
||||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
@ -26,10 +28,14 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
statusId: PropTypes.string,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||||
|
currentTime: PropTypes.number,
|
||||||
|
autoPlay: PropTypes.bool,
|
||||||
|
volume: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -64,6 +70,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleChangeIndex = (e) => {
|
handleChangeIndex = (e) => {
|
||||||
const index = Number(e.currentTarget.getAttribute('data-index'));
|
const index = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
index: index % this.props.media.size,
|
index: index % this.props.media.size,
|
||||||
zoomButtonHidden: true,
|
zoomButtonHidden: true,
|
||||||
|
@ -87,10 +94,12 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||||
|
this._sendBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('keydown', this.handleKeyDown);
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
this.props.onChangeBackgroundColor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex () {
|
getIndex () {
|
||||||
|
@ -106,30 +115,38 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
handleStatusClick = e => {
|
handleStatusClick = e => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
this.context.router.history.push(`/statuses/${this.props.statusId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sendBackgroundColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
if (prevState.index !== this.state.index) {
|
||||||
|
this._sendBackgroundColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendBackgroundColor () {
|
||||||
|
const { media, onChangeBackgroundColor } = this.props;
|
||||||
|
const index = this.getIndex();
|
||||||
|
const blurhash = media.getIn([index, 'blurhash']);
|
||||||
|
|
||||||
|
if (blurhash) {
|
||||||
|
const backgroundColor = getAverageFromBlurhash(blurhash);
|
||||||
|
onChangeBackgroundColor(backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, status, intl, onClose } = this.props;
|
const { media, statusId, intl, onClose } = this.props;
|
||||||
const { navigationHidden } = this.state;
|
const { navigationHidden } = this.state;
|
||||||
|
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
let pagination = [];
|
|
||||||
|
|
||||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
|
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
|
||||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
|
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
|
||||||
|
|
||||||
if (media.size > 1) {
|
|
||||||
pagination = media.map((item, i) => {
|
|
||||||
const classes = ['media-modal__button'];
|
|
||||||
if (i === index) {
|
|
||||||
classes.push('media-modal__button--active');
|
|
||||||
}
|
|
||||||
return (<li className='media-modal__page-dot' key={i}><button tabIndex='0' className={classes.join(' ')} onClick={this.handleChangeIndex} data-index={i}>{i + 1}</button></li>);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = media.map((image) => {
|
const content = media.map((image) => {
|
||||||
const width = image.getIn(['meta', 'original', 'width']) || null;
|
const width = image.getIn(['meta', 'original', 'width']) || null;
|
||||||
const height = image.getIn(['meta', 'original', 'height']) || null;
|
const height = image.getIn(['meta', 'original', 'height']) || null;
|
||||||
|
@ -148,7 +165,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (image.get('type') === 'video') {
|
} else if (image.get('type') === 'video') {
|
||||||
const { time } = this.props;
|
const { currentTime, autoPlay, volume } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Video
|
<Video
|
||||||
|
@ -157,7 +174,10 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
src={image.get('url')}
|
src={image.get('url')}
|
||||||
width={image.get('width')}
|
width={image.get('width')}
|
||||||
height={image.get('height')}
|
height={image.get('height')}
|
||||||
currentTime={time || 0}
|
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
||||||
|
currentTime={currentTime || 0}
|
||||||
|
autoPlay={autoPlay || false}
|
||||||
|
volume={volume || 1}
|
||||||
onCloseVideo={onClose}
|
onCloseVideo={onClose}
|
||||||
detailed
|
detailed
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
@ -197,13 +217,19 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
'media-modal__navigation--hidden': navigationHidden,
|
'media-modal__navigation--hidden': navigationHidden,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let pagination;
|
||||||
|
|
||||||
|
if (media.size > 1) {
|
||||||
|
pagination = media.map((item, i) => (
|
||||||
|
<button key={i} className={classNames('media-modal__page-dot', { active: i === index })} data-index={i} onClick={this.handleChangeIndex}>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal media-modal'>
|
<div className='modal-root__modal media-modal'>
|
||||||
<div
|
<div className='media-modal__closer' role='presentation' onClick={onClose} >
|
||||||
className='media-modal__closer'
|
|
||||||
role='presentation'
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<ReactSwipeableViews
|
<ReactSwipeableViews
|
||||||
style={swipeableViewsStyle}
|
style={swipeableViewsStyle}
|
||||||
containerStyle={containerStyle}
|
containerStyle={containerStyle}
|
||||||
|
@ -221,15 +247,10 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
{leftNav}
|
{leftNav}
|
||||||
{rightNav}
|
{rightNav}
|
||||||
|
|
||||||
{status && (
|
<div className='media-modal__overlay'>
|
||||||
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
|
{pagination && <ul className='media-modal__pagination'>{pagination}</ul>}
|
||||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<ul className='media-modal__pagination'>
|
|
||||||
{pagination}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -55,6 +55,10 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
backgroundColor: null,
|
||||||
|
};
|
||||||
|
|
||||||
getSnapshotBeforeUpdate () {
|
getSnapshotBeforeUpdate () {
|
||||||
return { visible: !!this.props.type };
|
return { visible: !!this.props.type };
|
||||||
}
|
}
|
||||||
|
@ -69,6 +73,10 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBackgroundColor = color => {
|
||||||
|
this.setState({ backgroundColor: color });
|
||||||
|
}
|
||||||
|
|
||||||
renderLoading = modalId => () => {
|
renderLoading = modalId => () => {
|
||||||
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
return ['MEDIA', 'VIDEO', 'BOOST', 'FAVOURITE', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
|
||||||
}
|
}
|
||||||
|
@ -81,13 +89,14 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { type, props, onClose } = this.props;
|
const { type, props, onClose } = this.props;
|
||||||
|
const { backgroundColor } = this.state;
|
||||||
const visible = !!type;
|
const visible = !!type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base onClose={onClose} noEsc={props ? props.noEsc : false}>
|
<Base backgroundColor={backgroundColor} onClose={onClose} noEsc={props ? props.noEsc : false}>
|
||||||
{visible && (
|
{visible && (
|
||||||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
||||||
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
|
{(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={onClose} />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
)}
|
)}
|
||||||
</Base>
|
</Base>
|
||||||
|
|
|
@ -3,9 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
||||||
import classNames from 'classnames';
|
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
|
||||||
|
|
||||||
export default class VideoModal extends ImmutablePureComponent {
|
export default class VideoModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -15,24 +14,28 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
statusId: PropTypes.string,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
startTime: PropTypes.number,
|
startTime: PropTypes.number,
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
defaultVolume: PropTypes.number,
|
defaultVolume: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleStatusClick = e => {
|
componentDidMount () {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
const { media, onChangeBackgroundColor, onClose } = this.props;
|
||||||
e.preventDefault();
|
|
||||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
|
||||||
|
|
||||||
|
if (backgroundColor) {
|
||||||
|
onChangeBackgroundColor(backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, status, onClose } = this.props;
|
const { media, statusId, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -52,11 +55,9 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{status && (
|
<div className='media-modal__overlay'>
|
||||||
<div className={classNames('media-modal__meta')}>
|
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
|
||||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { fromJS, is } from 'immutable';
|
import { is } from 'immutable';
|
||||||
import { throttle, debounce } from 'lodash';
|
import { throttle, debounce } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen';
|
||||||
|
@ -120,10 +120,10 @@ class Video extends React.PureComponent {
|
||||||
deployPictureInPicture: PropTypes.func,
|
deployPictureInPicture: PropTypes.func,
|
||||||
preventPlayback: PropTypes.bool,
|
preventPlayback: PropTypes.bool,
|
||||||
blurhash: PropTypes.string,
|
blurhash: PropTypes.string,
|
||||||
link: PropTypes.node,
|
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
volume: PropTypes.number,
|
volume: PropTypes.number,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
|
componetIndex: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -510,25 +510,14 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
const { src, preview, width, height, alt } = this.props;
|
this.video.pause();
|
||||||
|
|
||||||
const media = fromJS({
|
this.props.onOpenVideo({
|
||||||
type: 'video',
|
|
||||||
url: src,
|
|
||||||
preview_url: preview,
|
|
||||||
description: alt,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
startTime: this.video.currentTime,
|
startTime: this.video.currentTime,
|
||||||
autoPlay: !this.state.paused,
|
autoPlay: !this.state.paused,
|
||||||
defaultVolume: this.state.volume,
|
defaultVolume: this.state.volume,
|
||||||
};
|
componetIndex: this.props.componetIndex,
|
||||||
|
});
|
||||||
this.video.pause();
|
|
||||||
this.props.onOpenVideo(media, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseVideo = () => {
|
handleCloseVideo = () => {
|
||||||
|
@ -548,7 +537,7 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, link, editable, blurhash } = this.props;
|
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash } = this.props;
|
||||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||||
const playerStyle = {};
|
const playerStyle = {};
|
||||||
|
@ -666,8 +655,6 @@ class Video extends React.PureComponent {
|
||||||
<span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
|
<span className='video-player__time-total'>{formatTime(Math.floor(duration))}</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{link && <span className='video-player__link'>{link}</span>}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -334,11 +334,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.star-icon.active {
|
.icon-button.star-icon.active {
|
||||||
color: $gold-star;
|
color: $gold-star;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-icon.active {
|
.icon-button.bookmark-icon.active {
|
||||||
color: $red-bookmark;
|
color: $red-bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -187,16 +187,19 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.extended-video-player {
|
&__close,
|
||||||
width: 100%;
|
&__zoom-button {
|
||||||
height: 100%;
|
color: rgba($white, 0.7);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
video {
|
&:hover,
|
||||||
max-width: $media-modal-media-max-width;
|
&:focus,
|
||||||
max-height: $media-modal-media-max-height;
|
&:active {
|
||||||
|
color: $white;
|
||||||
|
background-color: rgba($white, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: rgba($white, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,10 +236,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__nav {
|
.media-modal__nav {
|
||||||
background: rgba($base-overlay-background, 0.5);
|
background: transparent;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: $primary-text-color;
|
color: rgba($primary-text-color, 0.7);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -247,6 +250,12 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: $primary-text-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__nav--left {
|
.media-modal__nav--left {
|
||||||
|
@ -257,58 +266,86 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__pagination {
|
.media-modal__overlay {
|
||||||
width: 100%;
|
max-width: 600px;
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 20px;
|
right: 0;
|
||||||
pointer-events: none;
|
bottom: 0;
|
||||||
}
|
margin: 0 auto;
|
||||||
|
|
||||||
.media-modal__meta {
|
.picture-in-picture__footer {
|
||||||
text-align: center;
|
border-radius: 0;
|
||||||
position: absolute;
|
background: transparent;
|
||||||
left: 0;
|
padding: 20px 0;
|
||||||
bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&--shifted {
|
.icon-button {
|
||||||
bottom: 62px;
|
color: $white;
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
&:hover,
|
||||||
pointer-events: auto;
|
&:focus,
|
||||||
text-decoration: none;
|
&:active {
|
||||||
font-weight: 500;
|
color: $white;
|
||||||
color: $ui-secondary-color;
|
background-color: rgba($white, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&:focus {
|
||||||
&:focus,
|
background-color: rgba($white, 0.3);
|
||||||
&:active {
|
}
|
||||||
text-decoration: underline;
|
|
||||||
|
&.active {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background: rgba($highlight-text-color, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: rgba($highlight-text-color, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.star-icon.active {
|
||||||
|
color: $gold-star;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background: rgba($gold-star, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: rgba($gold-star, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__page-dot {
|
.media-modal__pagination {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__button {
|
.media-modal__page-dot {
|
||||||
|
flex: 0 0 auto;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
height: 12px;
|
opacity: 0.4;
|
||||||
width: 12px;
|
height: 6px;
|
||||||
border-radius: 6px;
|
width: 6px;
|
||||||
margin: 10px;
|
border-radius: 50%;
|
||||||
|
margin: 0 4px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
font-size: 0;
|
font-size: 0;
|
||||||
}
|
transition: opacity .2s ease-in-out;
|
||||||
|
|
||||||
.media-modal__button--active {
|
&.active {
|
||||||
background-color: $ui-highlight-color;
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__close {
|
.media-modal__close {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba($base-overlay-background, 0.7);
|
background: rgba($base-overlay-background, 0.7);
|
||||||
|
transition: background 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-root__container {
|
.modal-root__container {
|
||||||
|
|
|
@ -1122,21 +1122,6 @@ a.status-card.compact:hover {
|
||||||
.audio-player {
|
.audio-player {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 415px) {
|
|
||||||
width: 210px;
|
|
||||||
bottom: 10px;
|
|
||||||
right: 10px;
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-player,
|
|
||||||
.audio-player {
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.picture-in-picture-placeholder {
|
.picture-in-picture-placeholder {
|
||||||
|
|
Loading…
Reference in New Issue