Add polls and media attachments to edit comparison modal in web UI (#17727)
This commit is contained in:
		
							
								
								
									
										116
									
								
								app/javascript/mastodon/components/media_attachments.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								app/javascript/mastodon/components/media_attachments.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import { MediaGallery, Video, Audio } from 'mastodon/features/ui/util/async-components';
 | 
			
		||||
import Bundle from 'mastodon/features/ui/components/bundle';
 | 
			
		||||
import noop from 'lodash/noop';
 | 
			
		||||
 | 
			
		||||
export default class MediaAttachments extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    status: ImmutablePropTypes.map.isRequired,
 | 
			
		||||
    height: PropTypes.number,
 | 
			
		||||
    width: PropTypes.number,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static defaultProps = {
 | 
			
		||||
    height: 110,
 | 
			
		||||
    width: 239,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  updateOnProps = [
 | 
			
		||||
    'status',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  renderLoadingMediaGallery = () => {
 | 
			
		||||
    const { height, width } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='media-gallery' style={{ height, width }} />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderLoadingVideoPlayer = () => {
 | 
			
		||||
    const { height, width } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='video-player' style={{ height, width }} />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderLoadingAudioPlayer = () => {
 | 
			
		||||
    const { height, width } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='audio-player' style={{ height, width }} />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { status, width, height } = this.props;
 | 
			
		||||
    const mediaAttachments = status.get('media_attachments');
 | 
			
		||||
 | 
			
		||||
    if (mediaAttachments.size === 0) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mediaAttachments.getIn([0, 'type']) === 'audio') {
 | 
			
		||||
      const audio = mediaAttachments.get(0);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
 | 
			
		||||
          {Component => (
 | 
			
		||||
            <Component
 | 
			
		||||
              src={audio.get('url')}
 | 
			
		||||
              alt={audio.get('description')}
 | 
			
		||||
              width={width}
 | 
			
		||||
              height={height}
 | 
			
		||||
              poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
 | 
			
		||||
              backgroundColor={audio.getIn(['meta', 'colors', 'background'])}
 | 
			
		||||
              foregroundColor={audio.getIn(['meta', 'colors', 'foreground'])}
 | 
			
		||||
              accentColor={audio.getIn(['meta', 'colors', 'accent'])}
 | 
			
		||||
              duration={audio.getIn(['meta', 'original', 'duration'], 0)}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </Bundle>
 | 
			
		||||
      );
 | 
			
		||||
    } else if (mediaAttachments.getIn([0, 'type']) === 'video') {
 | 
			
		||||
      const video = mediaAttachments.get(0);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
 | 
			
		||||
          {Component => (
 | 
			
		||||
            <Component
 | 
			
		||||
              preview={video.get('preview_url')}
 | 
			
		||||
              frameRate={video.getIn(['meta', 'original', 'frame_rate'])}
 | 
			
		||||
              blurhash={video.get('blurhash')}
 | 
			
		||||
              src={video.get('url')}
 | 
			
		||||
              alt={video.get('description')}
 | 
			
		||||
              width={width}
 | 
			
		||||
              height={height}
 | 
			
		||||
              inline
 | 
			
		||||
              sensitive={status.get('sensitive')}
 | 
			
		||||
              onOpenVideo={noop}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </Bundle>
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return (
 | 
			
		||||
        <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
 | 
			
		||||
          {Component => (
 | 
			
		||||
            <Component
 | 
			
		||||
              media={mediaAttachments}
 | 
			
		||||
              sensitive={status.get('sensitive')}
 | 
			
		||||
              defaultWidth={width}
 | 
			
		||||
              height={height}
 | 
			
		||||
              onOpenMedia={noop}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </Bundle>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,12 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import noop from 'lodash/noop';
 | 
			
		||||
import StatusContent from 'mastodon/components/status_content';
 | 
			
		||||
import { MediaGallery, Video } from 'mastodon/features/ui/util/async-components';
 | 
			
		||||
import Bundle from 'mastodon/features/ui/components/bundle';
 | 
			
		||||
import Avatar from 'mastodon/components/avatar';
 | 
			
		||||
import DisplayName from 'mastodon/components/display_name';
 | 
			
		||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
 | 
			
		||||
import Option from './option';
 | 
			
		||||
import MediaAttachments from 'mastodon/components/media_attachments';
 | 
			
		||||
 | 
			
		||||
export default class StatusCheckBox extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
@@ -27,51 +25,10 @@ export default class StatusCheckBox extends React.PureComponent {
 | 
			
		||||
  render () {
 | 
			
		||||
    const { status, checked } = this.props;
 | 
			
		||||
 | 
			
		||||
    let media = null;
 | 
			
		||||
 | 
			
		||||
    if (status.get('reblog')) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (status.get('media_attachments').size > 0) {
 | 
			
		||||
      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
 | 
			
		||||
 | 
			
		||||
      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
 | 
			
		||||
        const video = status.getIn(['media_attachments', 0]);
 | 
			
		||||
 | 
			
		||||
        media = (
 | 
			
		||||
          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
 | 
			
		||||
            {Component => (
 | 
			
		||||
              <Component
 | 
			
		||||
                preview={video.get('preview_url')}
 | 
			
		||||
                blurhash={video.get('blurhash')}
 | 
			
		||||
                src={video.get('url')}
 | 
			
		||||
                alt={video.get('description')}
 | 
			
		||||
                width={239}
 | 
			
		||||
                height={110}
 | 
			
		||||
                inline
 | 
			
		||||
                sensitive={status.get('sensitive')}
 | 
			
		||||
                onOpenVideo={noop}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </Bundle>
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        media = (
 | 
			
		||||
          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
 | 
			
		||||
            {Component => (
 | 
			
		||||
              <Component
 | 
			
		||||
                media={status.get('media_attachments')}
 | 
			
		||||
                sensitive={status.get('sensitive')}
 | 
			
		||||
                height={110}
 | 
			
		||||
                onOpenMedia={noop}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </Bundle>
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const labelComponent = (
 | 
			
		||||
      <div className='status-check-box__status poll__option__text'>
 | 
			
		||||
        <div className='detailed-status__display-name'>
 | 
			
		||||
@@ -79,12 +36,13 @@ export default class StatusCheckBox extends React.PureComponent {
 | 
			
		||||
            <Avatar account={status.get('account')} size={46} />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
 | 
			
		||||
          <div>
 | 
			
		||||
            <DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <StatusContent status={status} />
 | 
			
		||||
 | 
			
		||||
        {media}
 | 
			
		||||
        <MediaAttachments status={status} />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import escapeTextContentForBrowser from 'escape-html';
 | 
			
		||||
import InlineAccount from 'mastodon/components/inline_account';
 | 
			
		||||
import IconButton from 'mastodon/components/icon_button';
 | 
			
		||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
 | 
			
		||||
import MediaAttachments from 'mastodon/components/media_attachments';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { statusId }) => ({
 | 
			
		||||
  versions: state.getIn(['history', statusId, 'items']),
 | 
			
		||||
@@ -70,6 +71,25 @@ class CompareHistoryModal extends React.PureComponent {
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
            <div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
 | 
			
		||||
 | 
			
		||||
            {!!currentVersion.get('poll') && (
 | 
			
		||||
              <div className='poll'>
 | 
			
		||||
                <ul>
 | 
			
		||||
                  {currentVersion.getIn(['poll', 'options']).map(option => (
 | 
			
		||||
                    <li key={option.get('title')}>
 | 
			
		||||
                      <span className='poll__input disabled' />
 | 
			
		||||
 | 
			
		||||
                      <span
 | 
			
		||||
                        className='poll__option__text translate'
 | 
			
		||||
                        dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
 | 
			
		||||
                      />
 | 
			
		||||
                    </li>
 | 
			
		||||
                  ))}
 | 
			
		||||
                </ul>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
            <MediaAttachments status={currentVersion} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1127,7 +1127,7 @@
 | 
			
		||||
  .media-gallery,
 | 
			
		||||
  .audio-player,
 | 
			
		||||
  .video-player {
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
    max-width: 250px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -5609,6 +5609,12 @@ a.status-card.compact:hover {
 | 
			
		||||
      margin: 20px 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .media-gallery,
 | 
			
		||||
  .audio-player,
 | 
			
		||||
  .video-player {
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loading-bar {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,12 @@ class StatusEdit < ApplicationRecord
 | 
			
		||||
 | 
			
		||||
  class PreservedMediaAttachment < ActiveModelSerializers::Model
 | 
			
		||||
    attributes :media_attachment, :description
 | 
			
		||||
    delegate :id, :type, :url, :preview_url, :remote_url, :preview_remote_url, :text_url, :meta, :blurhash, to: :media_attachment
 | 
			
		||||
 | 
			
		||||
    delegate :id, :type, :url, :preview_url, :remote_url,
 | 
			
		||||
             :preview_remote_url, :text_url, :meta, :blurhash,
 | 
			
		||||
             :not_processed?, :needs_redownload?, :local?,
 | 
			
		||||
             :file, :thumbnail, :thumbnail_remote_url,
 | 
			
		||||
             :shortcode, to: :media_attachment
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  rate_limit by: :account, family: :statuses
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user