[Glitch] Add ability to choose media thumbnail in web UI
Port 06fc6a9cd4c700c450dbe8e8c579a5d5bc4ba732 to glitch-soc Signed-off-by: Thibaut Girka <thib@sitedethib.com>
This commit is contained in:
		
							parent
							
								
									8b17d47e12
								
							
						
					
					
						commit
						6c7ac1b48f
					
				@ -30,6 +30,11 @@ export const COMPOSE_UPLOAD_FAIL     = 'COMPOSE_UPLOAD_FAIL';
 | 
			
		||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
 | 
			
		||||
export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO';
 | 
			
		||||
 | 
			
		||||
export const THUMBNAIL_UPLOAD_REQUEST  = 'THUMBNAIL_UPLOAD_REQUEST';
 | 
			
		||||
export const THUMBNAIL_UPLOAD_SUCCESS  = 'THUMBNAIL_UPLOAD_SUCCESS';
 | 
			
		||||
export const THUMBNAIL_UPLOAD_FAIL     = 'THUMBNAIL_UPLOAD_FAIL';
 | 
			
		||||
export const THUMBNAIL_UPLOAD_PROGRESS = 'THUMBNAIL_UPLOAD_PROGRESS';
 | 
			
		||||
 | 
			
		||||
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
 | 
			
		||||
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
 | 
			
		||||
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
 | 
			
		||||
@ -289,6 +294,49 @@ export function uploadCompose(files) {
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const uploadThumbnail = (id, file) => (dispatch, getState) => {
 | 
			
		||||
  dispatch(uploadThumbnailRequest());
 | 
			
		||||
 | 
			
		||||
  const total = file.size;
 | 
			
		||||
  const data = new FormData();
 | 
			
		||||
 | 
			
		||||
  data.append('thumbnail', file);
 | 
			
		||||
 | 
			
		||||
  api(getState).put(`/api/v1/media/${id}`, data, {
 | 
			
		||||
    onUploadProgress: ({ loaded }) => {
 | 
			
		||||
      dispatch(uploadThumbnailProgress(loaded, total));
 | 
			
		||||
    },
 | 
			
		||||
  }).then(({ data }) => {
 | 
			
		||||
    dispatch(uploadThumbnailSuccess(data));
 | 
			
		||||
  }).catch(error => {
 | 
			
		||||
    dispatch(uploadThumbnailFail(id, error));
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const uploadThumbnailRequest = () => ({
 | 
			
		||||
  type: THUMBNAIL_UPLOAD_REQUEST,
 | 
			
		||||
  skipLoading: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const uploadThumbnailProgress = (loaded, total) => ({
 | 
			
		||||
  type: THUMBNAIL_UPLOAD_PROGRESS,
 | 
			
		||||
  loaded,
 | 
			
		||||
  total,
 | 
			
		||||
  skipLoading: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const uploadThumbnailSuccess = media => ({
 | 
			
		||||
  type: THUMBNAIL_UPLOAD_SUCCESS,
 | 
			
		||||
  media,
 | 
			
		||||
  skipLoading: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const uploadThumbnailFail = error => ({
 | 
			
		||||
  type: THUMBNAIL_UPLOAD_FAIL,
 | 
			
		||||
  error,
 | 
			
		||||
  skipLoading: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function changeUploadCompose(id, params) {
 | 
			
		||||
  return (dispatch, getState) => {
 | 
			
		||||
    dispatch(changeUploadComposeRequest());
 | 
			
		||||
@ -307,6 +355,7 @@ export function changeUploadComposeRequest() {
 | 
			
		||||
    skipLoading: true,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function changeUploadComposeSuccess(media) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import { changeUploadCompose } from 'flavours/glitch/actions/compose';
 | 
			
		||||
import { changeUploadCompose, uploadThumbnail } from 'flavours/glitch/actions/compose';
 | 
			
		||||
import { getPointerPosition } from 'flavours/glitch/features/video';
 | 
			
		||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import IconButton from 'flavours/glitch/components/icon_button';
 | 
			
		||||
@ -23,11 +23,13 @@ const messages = defineMessages({
 | 
			
		||||
  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
			
		||||
  apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' },
 | 
			
		||||
  placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' },
 | 
			
		||||
  chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { id }) => ({
 | 
			
		||||
  media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
 | 
			
		||||
  account: state.getIn(['accounts', me]),
 | 
			
		||||
  isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch, { id }) => ({
 | 
			
		||||
@ -36,6 +38,10 @@ const mapDispatchToProps = (dispatch, { id }) => ({
 | 
			
		||||
    dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onSelectThumbnail: files => {
 | 
			
		||||
    dispatch(uploadThumbnail(id, files[0]));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
 | 
			
		||||
@ -81,6 +87,9 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    media: ImmutablePropTypes.map.isRequired,
 | 
			
		||||
    account: ImmutablePropTypes.map.isRequired,
 | 
			
		||||
    isUploadingThumbnail: PropTypes.bool,
 | 
			
		||||
    onSave: PropTypes.func.isRequired,
 | 
			
		||||
    onSelectThumbnail: PropTypes.func.isRequired,
 | 
			
		||||
    onClose: PropTypes.func.isRequired,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
@ -235,13 +244,29 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
			
		||||
    }).catch(() => this.setState({ detecting: false }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleThumbnailChange = e => {
 | 
			
		||||
    if (e.target.files.length > 0) {
 | 
			
		||||
      this.setState({ dirty: true });
 | 
			
		||||
      this.props.onSelectThumbnail(e.target.files);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setFileInputRef = c => {
 | 
			
		||||
    this.fileInput = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleFileInputClick = () => {
 | 
			
		||||
    this.fileInput.click();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { media, intl, account, onClose } = this.props;
 | 
			
		||||
    const { media, intl, account, onClose, isUploadingThumbnail } = this.props;
 | 
			
		||||
    const { x, y, dragging, description, dirty, detecting, progress } = this.state;
 | 
			
		||||
 | 
			
		||||
    const width  = media.getIn(['meta', 'original', 'width']) || null;
 | 
			
		||||
    const height = media.getIn(['meta', 'original', 'height']) || null;
 | 
			
		||||
    const focals = ['image', 'gifv'].includes(media.get('type'));
 | 
			
		||||
    const thumbnailable = ['audio', 'video'].includes(media.get('type'));
 | 
			
		||||
 | 
			
		||||
    const previewRatio  = 16/9;
 | 
			
		||||
    const previewWidth  = 200;
 | 
			
		||||
@ -268,6 +293,30 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
			
		||||
          <div className='report-modal__comment'>
 | 
			
		||||
            {focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>}
 | 
			
		||||
 | 
			
		||||
            {thumbnailable && (
 | 
			
		||||
              <React.Fragment>
 | 
			
		||||
                <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
 | 
			
		||||
 | 
			
		||||
                <Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
 | 
			
		||||
 | 
			
		||||
                <label>
 | 
			
		||||
                  <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>
 | 
			
		||||
 | 
			
		||||
                  <input
 | 
			
		||||
                    id='upload-modal__thumbnail'
 | 
			
		||||
                    ref={this.setFileInputRef}
 | 
			
		||||
                    type='file'
 | 
			
		||||
                    accept='image/png,image/jpeg'
 | 
			
		||||
                    onChange={this.handleThumbnailChange}
 | 
			
		||||
                    style={{ display: 'none' }}
 | 
			
		||||
                    disabled={isUploadingThumbnail}
 | 
			
		||||
                  />
 | 
			
		||||
                </label>
 | 
			
		||||
 | 
			
		||||
                <hr className='setting-divider' />
 | 
			
		||||
              </React.Fragment>
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
            <label className='setting-text-label' htmlFor='upload-modal__description'>
 | 
			
		||||
              {descriptionLabel}
 | 
			
		||||
            </label>
 | 
			
		||||
@ -293,7 +342,7 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
			
		||||
              <CharacterCounter max={1500} text={detecting ? '' : description} />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
 | 
			
		||||
            <Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className='focal-point-modal__content'>
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,10 @@ import {
 | 
			
		||||
  COMPOSE_UPLOAD_FAIL,
 | 
			
		||||
  COMPOSE_UPLOAD_UNDO,
 | 
			
		||||
  COMPOSE_UPLOAD_PROGRESS,
 | 
			
		||||
  THUMBNAIL_UPLOAD_REQUEST,
 | 
			
		||||
  THUMBNAIL_UPLOAD_SUCCESS,
 | 
			
		||||
  THUMBNAIL_UPLOAD_FAIL,
 | 
			
		||||
  THUMBNAIL_UPLOAD_PROGRESS,
 | 
			
		||||
  COMPOSE_SUGGESTIONS_CLEAR,
 | 
			
		||||
  COMPOSE_SUGGESTIONS_READY,
 | 
			
		||||
  COMPOSE_SUGGESTION_SELECT,
 | 
			
		||||
@ -77,6 +81,8 @@ const initialState = ImmutableMap({
 | 
			
		||||
  is_uploading: false,
 | 
			
		||||
  is_changing_upload: false,
 | 
			
		||||
  progress: 0,
 | 
			
		||||
  isUploadingThumbnail: false,
 | 
			
		||||
  thumbnailProgress: 0,
 | 
			
		||||
  media_attachments: ImmutableList(),
 | 
			
		||||
  pending_media_attachments: 0,
 | 
			
		||||
  poll: null,
 | 
			
		||||
@ -433,6 +439,22 @@ export default function compose(state = initialState, action) {
 | 
			
		||||
    return removeMedia(state, action.media_id);
 | 
			
		||||
  case COMPOSE_UPLOAD_PROGRESS:
 | 
			
		||||
    return state.set('progress', Math.round((action.loaded / action.total) * 100));
 | 
			
		||||
  case THUMBNAIL_UPLOAD_REQUEST:
 | 
			
		||||
    return state.set('isUploadingThumbnail', true);
 | 
			
		||||
  case THUMBNAIL_UPLOAD_PROGRESS:
 | 
			
		||||
    return state.set('thumbnailProgress', Math.round((action.loaded / action.total) * 100));
 | 
			
		||||
  case THUMBNAIL_UPLOAD_FAIL:
 | 
			
		||||
    return state.set('isUploadingThumbnail', false);
 | 
			
		||||
  case THUMBNAIL_UPLOAD_SUCCESS:
 | 
			
		||||
    return state
 | 
			
		||||
      .set('isUploadingThumbnail', false)
 | 
			
		||||
      .update('media_attachments', list => list.map(item => {
 | 
			
		||||
        if (item.get('id') === action.media.id) {
 | 
			
		||||
          return fromJS(action.media);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return item;
 | 
			
		||||
      }));
 | 
			
		||||
  case COMPOSE_MENTION:
 | 
			
		||||
    return state.withMutations(map => {
 | 
			
		||||
      map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
 | 
			
		||||
 | 
			
		||||
@ -555,6 +555,15 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.setting-divider {
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  margin-bottom: 29px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.report-modal__comment {
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
  border-right: 1px solid $ui-secondary-color;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user