diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 9f05a53e96..ebb75f36e9 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -27,6 +27,7 @@ export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; +export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; @@ -278,3 +279,10 @@ export function insertEmojiCompose(position, emoji) { emoji, }; }; + +export function changeComposing(value) { + return { + type: COMPOSE_COMPOSING_CHANGE, + value, + }; +} diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js index fa4f560f39..c76210c85e 100644 --- a/app/javascript/mastodon/features/compose/components/navigation_bar.js +++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js @@ -1,6 +1,8 @@ import React from 'react'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from '../../../components/avatar'; +import IconButton from '../../../components/icon_button'; import Permalink from '../../../components/permalink'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -9,6 +11,7 @@ export default class NavigationBar extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, + onClose: PropTypes.func.isRequired, }; render () { @@ -25,6 +28,8 @@ export default class NavigationBar extends ImmutablePureComponent { + + ); } diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 6aa5de96c0..e3cf2d33b6 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -11,6 +11,7 @@ import SearchContainer from './containers/search_container'; import Motion from 'react-motion/lib/Motion'; import spring from 'react-motion/lib/spring'; import SearchResultsContainer from './containers/search_results_container'; +import { changeComposing } from '../../actions/compose'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -47,6 +48,14 @@ export default class Compose extends React.PureComponent { this.props.dispatch(unmountCompose()); } + onFocus = () => { + this.props.dispatch(changeComposing(true)); + } + + onBlur = () => { + this.props.dispatch(changeComposing(false)); + } + render () { const { multiColumn, showSearch, intl } = this.props; @@ -82,8 +91,8 @@ export default class Compose extends React.PureComponent {
-
- +
+
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 3baf09b932..e667b390b0 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -43,6 +43,7 @@ import '../../components/status'; const mapStateToProps = state => ({ systemFontUi: state.getIn(['meta', 'system_font_ui']), + isComposing: state.getIn(['compose', 'is_composing']), }); @connect(mapStateToProps) @@ -52,6 +53,7 @@ export default class UI extends React.PureComponent { dispatch: PropTypes.func.isRequired, children: PropTypes.node, systemFontUi: PropTypes.bool, + isComposing: PropTypes.bool, }; state = { @@ -133,6 +135,19 @@ export default class UI extends React.PureComponent { this.props.dispatch(refreshNotifications()); } + shouldComponentUpdate (nextProps) { + if (nextProps.isComposing !== this.props.isComposing) { + // Avoid expensive update just to toggle a class + this.node.classList.toggle('is-composing', nextProps.isComposing); + + return false; + } + + // Why isn't this working?!? + // return super.shouldComponentUpdate(nextProps, nextState); + return true; + } + componentWillUnmount () { window.removeEventListener('resize', this.handleResize); document.removeEventListener('dragenter', this.handleDragEnter); diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 781e6e11b9..5340220003 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -20,6 +20,7 @@ import { COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_VISIBILITY_CHANGE, + COMPOSE_COMPOSING_CHANGE, COMPOSE_EMOJI_INSERT, } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; @@ -37,6 +38,7 @@ const initialState = ImmutableMap({ focusDate: null, preselectDate: null, in_reply_to: null, + is_composing: false, is_submitting: false, is_uploading: false, progress: 0, @@ -146,7 +148,9 @@ export default function compose(state = initialState, action) { case COMPOSE_MOUNT: return state.set('mounted', true); case COMPOSE_UNMOUNT: - return state.set('mounted', false); + return state + .set('mounted', false) + .set('is_composing', false); case COMPOSE_SENSITIVITY_CHANGE: return state .set('sensitive', !state.get('sensitive')) @@ -169,6 +173,8 @@ export default function compose(state = initialState, action) { return state .set('text', action.text) .set('idempotencyKey', uuid()); + case COMPOSE_COMPOSING_CHANGE: + return state.set('is_composing', action.value); case COMPOSE_REPLY: return state.withMutations(map => { map.set('in_reply_to', action.status.get('id')); diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 8c930ad111..b376f3a306 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1177,6 +1177,11 @@ .permalink { text-decoration: none; } + + .icon-button { + pointer-events: none; + opacity: 0; + } } .navigation-bar__profile { @@ -3723,3 +3728,66 @@ noscript { margin: 20px 0; } } + +@media screen and (max-width: 1024px) and (max-height: 600px) { + $duration: 400ms; + $delay: 100ms; + + .tabs-bar, + .search { + will-change: margin-top; + transition: margin-top $duration $delay; + } + + .navigation-bar { + will-change: padding-bottom; + transition: padding-bottom $duration $delay; + } + + .navigation-bar { + & > a:first-child { + will-change: margin-top, margin-left, width; + transition: margin-top $duration $delay, margin-left $duration ($duration + $delay); + } + + & > .navigation-bar__profile-edit { + will-change: margin-top; + transition: margin-top $duration $delay; + } + + & > .icon-button { + will-change: opacity; + transition: opacity $duration $delay; + } + } + + .is-composing { + .tabs-bar, + .search { + margin-top: -50px; + } + + .navigation-bar { + padding-bottom: 0; + + & > a:first-child { + margin-top: -50px; + margin-left: -40px; + } + + .navigation-bar__profile { + padding-top: 2px; + } + + .navigation-bar__profile-edit { + position: absolute; + margin-top: -50px; + } + + .icon-button { + pointer-events: auto; + opacity: 1; + } + } + } +}