about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSorin Davidoi <sorin.davidoi@gmail.com>2017-07-21 01:38:24 +0200
committerEugen Rochko <eugen@zeonfederated.com>2017-07-21 01:38:24 +0200
commitc1bc5e14ebbecd8ffc6b4188fbf608e11f64422e (patch)
tree3c06dfd35b4e8f8d41a21383db7152c1cdbeb11d
parent4b911fea03a204a1a1cc0e5f63ede1679e0494c3 (diff)
feat(compose): More space on mobile devices (#4282)
* feat(compose): More space on mobile devices

* feat(compose): Hide navigation when typing on mobile devices

* fix(compose): Make animation faster

* fix(navigation_bar): Remove hardcoded title

* fix(compose): Prevent accidental bluring

* fix(compose): Increase max-height to 600px
-rw-r--r--app/javascript/mastodon/actions/compose.js8
-rw-r--r--app/javascript/mastodon/features/compose/components/navigation_bar.js5
-rw-r--r--app/javascript/mastodon/features/compose/index.js13
-rw-r--r--app/javascript/mastodon/features/ui/index.js15
-rw-r--r--app/javascript/mastodon/reducers/compose.js8
-rw-r--r--app/javascript/styles/components.scss68
6 files changed, 114 insertions, 3 deletions
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 9f05a53e9..ebb75f36e 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 fa4f560f3..c76210c85 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 {
 
           <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
         </div>
+
+        <IconButton title='' icon='close' onClick={this.props.onClose} />
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 6aa5de96c..e3cf2d33b 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 {
         <SearchContainer />
 
         <div className='drawer__pager'>
-          <div className='drawer__inner'>
-            <NavigationContainer />
+          <div className='drawer__inner' onFocus={this.onFocus}>
+            <NavigationContainer onClose={this.onBlur} />
             <ComposeFormContainer />
           </div>
 
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 3baf09b93..e667b390b 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 781e6e11b..534022000 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 8c930ad11..b376f3a30 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;
+      }
+    }
+  }
+}