[Glitch] Add keyboard shortcut to hide/show media

Port a472190729 and 988342a56c to glitch-soc
This commit is contained in:
Thibaut Girka 2019-05-26 18:58:14 +02:00
parent 20d01a954e
commit b4d4138cf9
7 changed files with 120 additions and 26 deletions

View file

@ -257,7 +257,6 @@ export default class MediaGallery extends React.PureComponent {
static propTypes = { static propTypes = {
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
revealed: PropTypes.bool,
standalone: PropTypes.bool, standalone: PropTypes.bool,
letterbox: PropTypes.bool, letterbox: PropTypes.bool,
fullwidth: PropTypes.bool, fullwidth: PropTypes.bool,
@ -268,6 +267,8 @@ export default class MediaGallery extends React.PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
defaultWidth: PropTypes.number, defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func, cacheWidth: PropTypes.func,
visible: PropTypes.bool,
onToggleVisibility: PropTypes.func,
}; };
static defaultProps = { static defaultProps = {
@ -275,13 +276,15 @@ export default class MediaGallery extends React.PureComponent {
}; };
state = { state = {
visible: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed, visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
width: this.props.defaultWidth, width: this.props.defaultWidth,
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (!is(nextProps.media, this.props.media) || nextProps.revealed === true) { if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
this.setState({ visible: nextProps.revealed === undefined ? (displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all') : nextProps.revealed }); this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
} else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ visible: nextProps.visible });
} }
} }
@ -294,8 +297,12 @@ export default class MediaGallery extends React.PureComponent {
} }
handleOpen = () => { handleOpen = () => {
if (this.props.onToggleVisibility) {
this.props.onToggleVisibility();
} else {
this.setState({ visible: !this.state.visible }); this.setState({ visible: !this.state.visible });
} }
}
handleClick = (index) => { handleClick = (index) => {
this.props.onOpenMedia(this.props.media, index); this.props.onOpenMedia(this.props.media, index);

View file

@ -16,6 +16,7 @@ import NotificationOverlayContainer from 'flavours/glitch/features/notifications
import classNames from 'classnames'; import classNames from 'classnames';
import { autoUnfoldCW } from 'flavours/glitch/util/content_warning'; import { autoUnfoldCW } from 'flavours/glitch/util/content_warning';
import PollContainer from 'flavours/glitch/containers/poll_container'; import PollContainer from 'flavours/glitch/containers/poll_container';
import { displayMedia } from 'flavours/glitch/util/initial_state';
// We use the component (and not the container) since we do not want // We use the component (and not the container) since we do not want
// to use the progress bar to show download progress // to use the progress bar to show download progress
@ -38,6 +39,22 @@ export const textForScreenReader = (intl, status, rebloggedByText = false, expan
return values.join(', '); return values.join(', ');
}; };
export const defaultMediaVisibility = (status, settings) => {
if (!status) {
return undefined;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}
if (settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text')) {
return true;
}
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
}
@injectIntl @injectIntl
export default class Status extends ImmutablePureComponent { export default class Status extends ImmutablePureComponent {
@ -82,6 +99,9 @@ export default class Status extends ImmutablePureComponent {
isCollapsed: false, isCollapsed: false,
autoCollapsed: false, autoCollapsed: false,
isExpanded: undefined, isExpanded: undefined,
showMedia: undefined,
statusId: undefined,
revealBehindCW: undefined,
} }
// Avoid checking props that are functions (and whose equality will always // Avoid checking props that are functions (and whose equality will always
@ -103,6 +123,7 @@ export default class Status extends ImmutablePureComponent {
updateOnStates = [ updateOnStates = [
'isExpanded', 'isExpanded',
'isCollapsed', 'isCollapsed',
'showMedia',
] ]
// If our settings have changed to disable collapsed statuses, then we // If our settings have changed to disable collapsed statuses, then we
@ -160,6 +181,20 @@ export default class Status extends ImmutablePureComponent {
} }
} }
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
update.showMedia = defaultMediaVisibility(nextProps.status, nextProps.settings);
update.statusId = nextProps.status.get('id');
updated = true;
}
if (nextProps.settings.getIn(['media', 'reveal_behind_cw']) !== prevState.revealBehindCW) {
update.revealBehindCW = nextProps.settings.getIn(['media', 'reveal_behind_cw']);
if (update.revealBehindCW) {
update.showMedia = defaultMediaVisibility(nextProps.status, nextProps.settings);
}
updated = true;
}
return updated ? update : null; return updated ? update : null;
} }
@ -305,6 +340,10 @@ export default class Status extends ImmutablePureComponent {
} }
} }
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
handleAccountClick = (e) => { handleAccountClick = (e) => {
if (this.context.router && e.button === 0) { if (this.context.router && e.button === 0) {
const id = e.currentTarget.getAttribute('data-id'); const id = e.currentTarget.getAttribute('data-id');
@ -374,6 +413,9 @@ export default class Status extends ImmutablePureComponent {
this.setCollapsed(!this.state.isCollapsed); this.setCollapsed(!this.state.isCollapsed);
} }
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
handleRef = c => { handleRef = c => {
this.node = c; this.node = c;
@ -490,7 +532,8 @@ export default class Status extends ImmutablePureComponent {
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
width={this.props.cachedMediaWidth} width={this.props.cachedMediaWidth}
cacheWidth={this.props.cacheMediaWidth} cacheWidth={this.props.cacheMediaWidth}
revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>)} />)}
</Bundle> </Bundle>
); );
@ -508,7 +551,8 @@ export default class Status extends ImmutablePureComponent {
onOpenMedia={this.props.onOpenMedia} onOpenMedia={this.props.onOpenMedia}
cacheWidth={this.props.cacheMediaWidth} cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth} defaultWidth={this.props.cachedMediaWidth}
revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/> />
)} )}
</Bundle> </Bundle>
@ -566,6 +610,7 @@ export default class Status extends ImmutablePureComponent {
toggleSpoiler: this.handleExpandedToggle, toggleSpoiler: this.handleExpandedToggle,
bookmark: this.handleHotkeyBookmark, bookmark: this.handleHotkeyBookmark,
toggleCollapse: this.handleHotkeyCollapse, toggleCollapse: this.handleHotkeyCollapse,
toggleSensitive: this.handleHotkeyToggleSensitive,
}; };
const computedClass = classNames('status', `status-${status.get('visibility')}`, { const computedClass = classNames('status', `status-${status.get('visibility')}`, {

View file

@ -71,6 +71,10 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>x</kbd></td> <td><kbd>x</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td> <td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>
</tr> </tr>
<tr>
<td><kbd>h</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
</tr>
{collapseEnabled && ( {collapseEnabled && (
<tr> <tr>
<td><kbd>shift</kbd>+<kbd>x</kbd></td> <td><kbd>shift</kbd>+<kbd>x</kbd></td>

View file

@ -33,6 +33,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
onHeightChange: PropTypes.func, onHeightChange: PropTypes.func,
domain: PropTypes.string.isRequired, domain: PropTypes.string.isRequired,
compact: PropTypes.bool, compact: PropTypes.bool,
showMedia: PropTypes.bool,
onToggleMediaVisibility: PropTypes.func,
}; };
state = { state = {
@ -144,7 +146,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
preventPlayback={!expanded} preventPlayback={!expanded}
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
autoplay autoplay
revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/> />
); );
mediaIcon = 'video-camera'; mediaIcon = 'video-camera';
@ -158,7 +161,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
fullwidth={settings.getIn(['media', 'fullwidth'])} fullwidth={settings.getIn(['media', 'fullwidth'])}
hidden={!expanded} hidden={!expanded}
onOpenMedia={this.props.onOpenMedia} onOpenMedia={this.props.onOpenMedia}
revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/> />
); );
mediaIcon = 'picture-o'; mediaIcon = 'picture-o';

View file

@ -41,7 +41,7 @@ import { HotKeys } from 'react-hotkeys';
import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state'; import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
import { autoUnfoldCW } from 'flavours/glitch/util/content_warning'; import { autoUnfoldCW } from 'flavours/glitch/util/content_warning';
import { textForScreenReader } from 'flavours/glitch/components/status'; import { textForScreenReader, defaultMediaVisibility } from 'flavours/glitch/components/status';
const messages = defineMessages({ const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -134,6 +134,9 @@ export default class Status extends ImmutablePureComponent {
isExpanded: undefined, isExpanded: undefined,
threadExpanded: undefined, threadExpanded: undefined,
statusId: undefined, statusId: undefined,
loadedStatusId: undefined,
showMedia: undefined,
revealBehindCW: undefined,
}; };
componentDidMount () { componentDidMount () {
@ -152,17 +155,31 @@ export default class Status extends ImmutablePureComponent {
} }
static getDerivedStateFromProps(props, state) { static getDerivedStateFromProps(props, state) {
if (state.statusId === props.params.statusId || !props.params.statusId) { let update = {};
return null; let updated = false;
if (props.params.statusId && state.statusId !== props.params.statusId) {
props.dispatch(fetchStatus(props.params.statusId));
update.threadExpanded = undefined;
update.statusId = props.params.statusId;
updated = true;
} }
props.dispatch(fetchStatus(props.params.statusId)); const revealBehindCW = props.settings.getIn(['media', 'reveal_behind_cw']);
if (revealBehindCW !== state.revealBehindCW) {
update.revealBehindCW = revealBehindCW;
if (revealBehindCW) update.showMedia = defaultMediaVisibility(props.status, props.settings);
updated = true;
}
return { if (props.status && state.loadedStatusId !== props.status.get('id')) {
threadExpanded: undefined, update.showMedia = defaultMediaVisibility(props.status, props.settings);
isExpanded: autoUnfoldCW(props.settings, props.status), update.loadedStatusId = props.status.get('id');
statusId: props.params.statusId, update.isExpanded = autoUnfoldCW(props.settings, props.status);
}; updated = true;
}
return updated ? update : null;
} }
handleExpandedToggle = () => { handleExpandedToggle = () => {
@ -171,6 +188,10 @@ export default class Status extends ImmutablePureComponent {
} }
}; };
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
handleModalFavourite = (status) => { handleModalFavourite = (status) => {
this.props.dispatch(favourite(status)); this.props.dispatch(favourite(status));
} }
@ -304,6 +325,10 @@ export default class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('EMBED', { url: status.get('url') })); this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
} }
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
handleHotkeyMoveUp = () => { handleHotkeyMoveUp = () => {
this.handleMoveUp(this.props.status.get('id')); this.handleMoveUp(this.props.status.get('id'));
} }
@ -477,6 +502,7 @@ export default class Status extends ImmutablePureComponent {
mention: this.handleHotkeyMention, mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile, openProfile: this.handleHotkeyOpenProfile,
toggleSpoiler: this.handleExpandedToggle, toggleSpoiler: this.handleExpandedToggle,
toggleSensitive: this.handleHotkeyToggleSensitive,
}; };
return ( return (
@ -505,6 +531,8 @@ export default class Status extends ImmutablePureComponent {
expanded={isExpanded} expanded={isExpanded}
onToggleHidden={this.handleExpandedToggle} onToggleHidden={this.handleExpandedToggle}
domain={domain} domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
/> />
<ActionBar <ActionBar

View file

@ -101,6 +101,7 @@ const keyMap = {
toggleSpoiler: 'x', toggleSpoiler: 'x',
bookmark: 'd', bookmark: 'd',
toggleCollapse: 'shift+x', toggleCollapse: 'shift+x',
toggleSensitive: 'h',
}; };
@connect(mapStateToProps) @connect(mapStateToProps)

View file

@ -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 } from 'immutable'; import { fromJS, is } from 'immutable';
import { throttle } from 'lodash'; import { throttle } 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';
@ -94,7 +94,6 @@ export default class Video extends React.PureComponent {
width: PropTypes.number, width: PropTypes.number,
height: PropTypes.number, height: PropTypes.number,
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
revealed: PropTypes.bool,
startTime: PropTypes.number, startTime: PropTypes.number,
onOpenVideo: PropTypes.func, onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func, onCloseVideo: PropTypes.func,
@ -102,9 +101,11 @@ export default class Video extends React.PureComponent {
fullwidth: PropTypes.bool, fullwidth: PropTypes.bool,
detailed: PropTypes.bool, detailed: PropTypes.bool,
inline: PropTypes.bool, inline: PropTypes.bool,
preventPlayback: PropTypes.bool,
intl: PropTypes.object.isRequired,
cacheWidth: PropTypes.func, cacheWidth: PropTypes.func,
intl: PropTypes.object.isRequired,
visible: PropTypes.bool,
onToggleVisibility: PropTypes.func,
preventPlayback: PropTypes.bool,
blurhash: PropTypes.string, blurhash: PropTypes.string,
link: PropTypes.node, link: PropTypes.node,
}; };
@ -119,12 +120,12 @@ export default class Video extends React.PureComponent {
fullscreen: false, fullscreen: false,
hovered: false, hovered: false,
muted: false, muted: false,
revealed: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed, revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.revealed === true) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ revealed: true }); this.setState({ revealed: nextProps.visible });
} }
} }
@ -349,8 +350,12 @@ export default class Video extends React.PureComponent {
this.video.pause(); this.video.pause();
} }
if (this.props.onToggleVisibility) {
this.props.onToggleVisibility();
} else {
this.setState({ revealed: !this.state.revealed }); this.setState({ revealed: !this.state.revealed });
} }
}
handleLoadedData = () => { handleLoadedData = () => {
if (this.props.startTime) { if (this.props.startTime) {