Change media modals look in web UI (#15217)
- Change overlay background to match color of viewed image - Add interactive reply/boost/favourite buttons to footer of modal - Change ugly "View context" link to button among the action bar
This commit is contained in:
		@@ -4,13 +4,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import Video from 'mastodon/features/video';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import IconButton from 'mastodon/components/icon_button';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import ImageLoader from './image_loader';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
			
		||||
@@ -20,15 +21,121 @@ 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 {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    media: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    status: ImmutablePropTypes.map,
 | 
			
		||||
    statusId: PropTypes.string,
 | 
			
		||||
    index: PropTypes.number.isRequired,
 | 
			
		||||
    onClose: PropTypes.func.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
    onChangeBackgroundColor: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
@@ -67,6 +174,7 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  handleChangeIndex = (e) => {
 | 
			
		||||
    const index = Number(e.currentTarget.getAttribute('data-index'));
 | 
			
		||||
 | 
			
		||||
    this.setState({
 | 
			
		||||
      index: index % this.props.media.size,
 | 
			
		||||
      zoomButtonHidden: true,
 | 
			
		||||
@@ -100,6 +208,22 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
        this.props.onClose();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._sendBackgroundColor();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidUpdate (prevProps, prevState) {
 | 
			
		||||
    if (prevState.index !== this.state.index) {
 | 
			
		||||
      this._sendBackgroundColor();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _sendBackgroundColor () {
 | 
			
		||||
    const { media, onChangeBackgroundColor } = this.props;
 | 
			
		||||
    const index = this.getIndex();
 | 
			
		||||
    const backgroundColor = decodeRGB(decode83(media.getIn([index, 'blurhash']).slice(2, 6)));
 | 
			
		||||
 | 
			
		||||
    onChangeBackgroundColor(backgroundColor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
@@ -112,6 +236,8 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
        this.context.router.history.goBack();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.props.onChangeBackgroundColor(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getIndex () {
 | 
			
		||||
@@ -127,30 +253,19 @@ class MediaModal 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')}`);
 | 
			
		||||
      this.context.router.history.push(`/statuses/${this.props.statusId}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { media, status, intl, onClose } = this.props;
 | 
			
		||||
    const { media, statusId, intl, onClose } = this.props;
 | 
			
		||||
    const { navigationHidden } = this.state;
 | 
			
		||||
 | 
			
		||||
    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 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 width  = image.getIn(['meta', 'original', 'width']) || null;
 | 
			
		||||
      const height = image.getIn(['meta', 'original', 'height']) || null;
 | 
			
		||||
@@ -218,13 +333,19 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
      '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 (
 | 
			
		||||
      <div className='modal-root__modal media-modal'>
 | 
			
		||||
        <div
 | 
			
		||||
          className='media-modal__closer'
 | 
			
		||||
          role='presentation'
 | 
			
		||||
          onClick={onClose}
 | 
			
		||||
        >
 | 
			
		||||
        <div className='media-modal__closer' role='presentation' onClick={onClose} >
 | 
			
		||||
          <ReactSwipeableViews
 | 
			
		||||
            style={swipeableViewsStyle}
 | 
			
		||||
            containerStyle={containerStyle}
 | 
			
		||||
@@ -243,15 +364,10 @@ class MediaModal extends ImmutablePureComponent {
 | 
			
		||||
          {leftNav}
 | 
			
		||||
          {rightNav}
 | 
			
		||||
 | 
			
		||||
          {status && (
 | 
			
		||||
            <div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
 | 
			
		||||
              <a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <ul className='media-modal__pagination'>
 | 
			
		||||
            {pagination}
 | 
			
		||||
          </ul>
 | 
			
		||||
          <div className='media-modal__overlay'>
 | 
			
		||||
            {pagination && <ul className='media-modal__pagination'>{pagination}</ul>}
 | 
			
		||||
            {statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user