Add media editing modal (#11563)
Move media description input to a modal and unite that modal with the focal point modal. Add a hint about choosing focal points, as well as a preview of a 16:9 thumbnail. Enable the user to watch the video next to the media description input. Fix #8320 Fix #6713remotes/1727458204337373841/tmp_refs/heads/signup-info-prompt
parent
7ffec882ae
commit
23f7afa562
app/javascript
mastodon/features
compose
components
containers
ui/components
video
styles/mastodon
|
@ -4,16 +4,11 @@ import PropTypes from 'prop-types';
|
||||||
import Motion from '../../ui/util/optional_motion';
|
import Motion from '../../ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export default class Upload extends ImmutablePureComponent {
|
||||||
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @injectIntl
|
|
||||||
class Upload extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
@ -21,30 +16,10 @@ class Upload extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
onUndo: PropTypes.func.isRequired,
|
onUndo: PropTypes.func.isRequired,
|
||||||
onDescriptionChange: PropTypes.func.isRequired,
|
|
||||||
onOpenFocalPoint: PropTypes.func.isRequired,
|
onOpenFocalPoint: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
hovered: false,
|
|
||||||
focused: false,
|
|
||||||
dirtyDescription: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleKeyDown = (e) => {
|
|
||||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
|
||||||
this.handleSubmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = () => {
|
|
||||||
this.handleInputBlur();
|
|
||||||
this.props.onSubmit(this.context.router.history);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUndoClick = e => {
|
handleUndoClick = e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onUndo(this.props.media.get('id'));
|
this.props.onUndo(this.props.media.get('id'));
|
||||||
|
@ -55,69 +30,21 @@ class Upload extends ImmutablePureComponent {
|
||||||
this.props.onOpenFocalPoint(this.props.media.get('id'));
|
this.props.onOpenFocalPoint(this.props.media.get('id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = e => {
|
|
||||||
this.setState({ dirtyDescription: e.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
|
||||||
this.setState({ hovered: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
this.setState({ hovered: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInputFocus = () => {
|
|
||||||
this.setState({ focused: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.setState({ focused: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInputBlur = () => {
|
|
||||||
const { dirtyDescription } = this.state;
|
|
||||||
|
|
||||||
this.setState({ focused: false, dirtyDescription: null });
|
|
||||||
|
|
||||||
if (dirtyDescription !== null) {
|
|
||||||
this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, media } = this.props;
|
const { media } = this.props;
|
||||||
const active = this.state.hovered || this.state.focused;
|
|
||||||
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
|
|
||||||
const focusX = media.getIn(['meta', 'focus', 'x']);
|
const focusX = media.getIn(['meta', 'focus', 'x']);
|
||||||
const focusY = media.getIn(['meta', 'focus', 'y']);
|
const focusY = media.getIn(['meta', 'focus', 'y']);
|
||||||
const x = ((focusX / 2) + .5) * 100;
|
const x = ((focusX / 2) + .5) * 100;
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
|
<div className='compose-form__upload' tabIndex='0' role='button'>
|
||||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||||
{({ scale }) => (
|
{({ scale }) => (
|
||||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||||
<div className={classNames('compose-form__upload__actions', { active })}>
|
<div className={classNames('compose-form__upload__actions', { active: true })}>
|
||||||
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
|
||||||
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
|
<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames('compose-form__upload-description', { active })}>
|
|
||||||
<label>
|
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
placeholder={intl.formatMessage(messages.description)}
|
|
||||||
value={description}
|
|
||||||
maxLength={420}
|
|
||||||
onFocus={this.handleInputFocus}
|
|
||||||
onChange={this.handleInputChange}
|
|
||||||
onBlur={this.handleInputBlur}
|
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Upload from '../components/upload';
|
import Upload from '../components/upload';
|
||||||
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
|
import { undoUploadCompose } from '../../../actions/compose';
|
||||||
import { openModal } from '../../../actions/modal';
|
import { openModal } from '../../../actions/modal';
|
||||||
import { submitCompose } from '../../../actions/compose';
|
import { submitCompose } from '../../../actions/compose';
|
||||||
|
|
||||||
|
@ -14,10 +14,6 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(undoUploadCompose(id));
|
dispatch(undoUploadCompose(id));
|
||||||
},
|
},
|
||||||
|
|
||||||
onDescriptionChange: (id, description) => {
|
|
||||||
dispatch(changeUploadCompose(id, { description }));
|
|
||||||
},
|
|
||||||
|
|
||||||
onOpenFocalPoint: id => {
|
onOpenFocalPoint: id => {
|
||||||
dispatch(openModal('FOCAL_POINT', { id }));
|
dispatch(openModal('FOCAL_POINT', { id }));
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImageLoader from './image_loader';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { changeUploadCompose } from '../../../actions/compose';
|
import { changeUploadCompose } from '../../../actions/compose';
|
||||||
import { getPointerPosition } from '../../video';
|
import { getPointerPosition } from '../../video';
|
||||||
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
|
import IconButton from 'mastodon/components/icon_button';
|
||||||
|
import Button from 'mastodon/components/button';
|
||||||
|
import Video from 'mastodon/features/video';
|
||||||
|
|
||||||
|
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' },
|
||||||
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, { id }) => ({
|
const mapStateToProps = (state, { id }) => ({
|
||||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||||
|
@ -13,17 +23,20 @@ const mapStateToProps = (state, { id }) => ({
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { id }) => ({
|
const mapDispatchToProps = (dispatch, { id }) => ({
|
||||||
|
|
||||||
onSave: (x, y) => {
|
onSave: (description, x, y) => {
|
||||||
dispatch(changeUploadCompose(id, { focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
|
dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
class FocalPointModal extends ImmutablePureComponent {
|
class FocalPointModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -32,6 +45,8 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
focusX: 0,
|
focusX: 0,
|
||||||
focusY: 0,
|
focusY: 0,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
|
description: '',
|
||||||
|
dirty: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -66,7 +81,6 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
|
||||||
this.setState({ dragging: false });
|
this.setState({ dragging: false });
|
||||||
this.props.onSave(this.state.focusX, this.state.focusY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition = e => {
|
updatePosition = e => {
|
||||||
|
@ -74,46 +88,113 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
const focusX = (x - .5) * 2;
|
const focusX = (x - .5) * 2;
|
||||||
const focusY = (y - .5) * -2;
|
const focusY = (y - .5) * -2;
|
||||||
|
|
||||||
this.setState({ x, y, focusX, focusY });
|
this.setState({ x, y, focusX, focusY, dirty: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePositionFromMedia = media => {
|
updatePositionFromMedia = media => {
|
||||||
const focusX = media.getIn(['meta', 'focus', 'x']);
|
const focusX = media.getIn(['meta', 'focus', 'x']);
|
||||||
const focusY = media.getIn(['meta', 'focus', 'y']);
|
const focusY = media.getIn(['meta', 'focus', 'y']);
|
||||||
|
const description = media.get('description') || '';
|
||||||
|
|
||||||
if (focusX && focusY) {
|
if (focusX && focusY) {
|
||||||
const x = (focusX / 2) + .5;
|
const x = (focusX / 2) + .5;
|
||||||
const y = (focusY / -2) + .5;
|
const y = (focusY / -2) + .5;
|
||||||
|
|
||||||
this.setState({ x, y, focusX, focusY });
|
this.setState({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
focusX,
|
||||||
|
focusY,
|
||||||
|
description,
|
||||||
|
dirty: false,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ x: 0.5, y: 0.5, focusX: 0, focusY: 0 });
|
this.setState({
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5,
|
||||||
|
focusX: 0,
|
||||||
|
focusY: 0,
|
||||||
|
description,
|
||||||
|
dirty: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChange = e => {
|
||||||
|
this.setState({ description: e.target.value, dirty: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.onSave(this.state.description, this.state.focusX, this.state.focusY);
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
setRef = c => {
|
setRef = c => {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media } = this.props;
|
const { media, intl, onClose } = this.props;
|
||||||
const { x, y, dragging } = this.state;
|
const { x, y, dragging, description, dirty } = this.state;
|
||||||
|
|
||||||
const width = media.getIn(['meta', 'original', 'width']) || null;
|
const width = media.getIn(['meta', 'original', 'width']) || null;
|
||||||
const height = media.getIn(['meta', 'original', 'height']) || null;
|
const height = media.getIn(['meta', 'original', 'height']) || null;
|
||||||
|
const focals = ['image', 'gifv'].includes(media.get('type'));
|
||||||
|
|
||||||
|
const previewRatio = 16/9;
|
||||||
|
const previewWidth = 200;
|
||||||
|
const previewHeight = previewWidth / previewRatio;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal video-modal focal-point-modal'>
|
<div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
|
||||||
<div className={classNames('focal-point', { dragging })} ref={this.setRef}>
|
<div className='report-modal__target'>
|
||||||
<ImageLoader
|
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
|
||||||
previewSrc={media.get('preview_url')}
|
<FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
|
||||||
src={media.get('url')}
|
</div>
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
|
<div className='report-modal__container'>
|
||||||
<div className='focal-point__overlay' onMouseDown={this.handleMouseDown} />
|
<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>}
|
||||||
|
|
||||||
|
<label className='setting-text-label' htmlFor='upload-modal__description'><FormattedMessage id='upload_form.description' defaultMessage='Describe for the visually impaired' /></label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
id='upload-modal__description'
|
||||||
|
className='setting-text light'
|
||||||
|
value={description}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button disabled={!dirty} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='report-modal__statuses'>
|
||||||
|
{focals && (
|
||||||
|
<div className={classNames('focal-point', { dragging })} ref={this.setRef}>
|
||||||
|
{media.get('type') === 'image' && <img src={media.get('url')} width={width} height={height} alt='' />}
|
||||||
|
{media.get('type') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />}
|
||||||
|
|
||||||
|
<div className='focal-point__preview'>
|
||||||
|
<strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
|
||||||
|
<div style={{ width: previewWidth, height: previewHeight, backgroundImage: `url(${media.get('preview_url')})`, backgroundSize: 'cover', backgroundPosition: `${x * 100}% ${y * 100}%` }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
|
||||||
|
<div className='focal-point__overlay' onMouseDown={this.handleMouseDown} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{['audio', 'video'].includes(media.get('type')) && (
|
||||||
|
<Video
|
||||||
|
preview={media.get('preview_url')}
|
||||||
|
blurhash={media.get('blurhash')}
|
||||||
|
src={media.get('url')}
|
||||||
|
detailed
|
||||||
|
editable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -101,6 +101,7 @@ class Video extends React.PureComponent {
|
||||||
onCloseVideo: PropTypes.func,
|
onCloseVideo: PropTypes.func,
|
||||||
detailed: PropTypes.bool,
|
detailed: PropTypes.bool,
|
||||||
inline: PropTypes.bool,
|
inline: PropTypes.bool,
|
||||||
|
editable: PropTypes.bool,
|
||||||
cacheWidth: PropTypes.func,
|
cacheWidth: PropTypes.func,
|
||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
onToggleVisibility: PropTypes.func,
|
onToggleVisibility: PropTypes.func,
|
||||||
|
@ -375,7 +376,7 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link } = this.props;
|
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, link, editable } = 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 = (currentTime / duration) * 100;
|
const progress = (currentTime / duration) * 100;
|
||||||
|
|
||||||
|
@ -413,7 +414,7 @@ class Video extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role='menuitem'
|
role='menuitem'
|
||||||
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })}
|
className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable })}
|
||||||
style={playerStyle}
|
style={playerStyle}
|
||||||
ref={this.setPlayerRef}
|
ref={this.setPlayerRef}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
@ -423,7 +424,7 @@ class Video extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
|
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
|
||||||
|
|
||||||
{revealed && <video
|
{(revealed || editable) && <video
|
||||||
ref={this.setVideoRef}
|
ref={this.setVideoRef}
|
||||||
src={src}
|
src={src}
|
||||||
poster={preview}
|
poster={preview}
|
||||||
|
@ -445,7 +446,7 @@ class Video extends React.PureComponent {
|
||||||
onVolumeChange={this.handleVolumeChange}
|
onVolumeChange={this.handleVolumeChange}
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
||||||
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
|
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
|
||||||
<span className='spoiler-button__overlay__label'>{warning}</span>
|
<span className='spoiler-button__overlay__label'>{warning}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -489,7 +490,7 @@ class Video extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
{(!onCloseVideo && !editable) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||||
|
|
|
@ -4567,6 +4567,14 @@ a.status-card.compact:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-text-label {
|
||||||
|
display: block;
|
||||||
|
color: $inverted-text-color;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-toggle {
|
.setting-toggle {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
@ -4960,6 +4968,10 @@ a.status-card.compact:hover {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&.editable {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
@ -5688,27 +5700,20 @@ noscript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.focal-point-modal {
|
|
||||||
max-width: 80vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.focal-point {
|
.focal-point {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: move;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.dragging {
|
img,
|
||||||
cursor: move;
|
video {
|
||||||
}
|
display: block;
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 80vw;
|
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
width: auto;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin: auto;
|
margin: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
background: $base-shadow-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__reticle {
|
&__reticle {
|
||||||
|
@ -5728,6 +5733,27 @@ noscript {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__preview {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 0 14px rgba($base-shadow-color, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__header__content {
|
.account__header__content {
|
||||||
|
|
Loading…
Reference in New Issue