about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorSorin Davidoi <sorin.davidoi@gmail.com>2017-07-09 15:02:26 +0200
committerEugen Rochko <eugen@zeonfederated.com>2017-07-09 15:02:26 +0200
commitfc4c74660b690038ae48264f9d5b0230df58acc4 (patch)
tree51ed1a92c15a1700da32b6914e446f1d4a12e24e /app
parentcaf938562ef0d0fdb03bf57f15bbab8d76c5b4c0 (diff)
Swipeable views (#4105)
* feat: Replace react-swipeable with react-swipeable-views

* fix: iOS 9
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/components/column_header.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/column_loading.js10
-rw-r--r--app/javascript/mastodon/features/ui/components/columns_area.js48
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js18
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js40
-rw-r--r--app/javascript/mastodon/features/ui/components/tabs_bar.js26
-rw-r--r--app/javascript/styles/components.scss40
7 files changed, 110 insertions, 74 deletions
diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js
index ec9379320..5b2a4d84c 100644
--- a/app/javascript/mastodon/components/column_header.js
+++ b/app/javascript/mastodon/components/column_header.js
@@ -10,7 +10,7 @@ export default class ColumnHeader extends React.PureComponent {
   };
 
   static propTypes = {
-    title: PropTypes.string.isRequired,
+    title: PropTypes.node.isRequired,
     icon: PropTypes.string.isRequired,
     active: PropTypes.bool,
     multiColumn: PropTypes.bool,
diff --git a/app/javascript/mastodon/features/ui/components/column_loading.js b/app/javascript/mastodon/features/ui/components/column_loading.js
index 9bb9c14a1..7ecfaf77a 100644
--- a/app/javascript/mastodon/features/ui/components/column_loading.js
+++ b/app/javascript/mastodon/features/ui/components/column_loading.js
@@ -1,13 +1,19 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 
 import Column from '../../../components/column';
 import ColumnHeader from '../../../components/column_header';
 
-const ColumnLoading = () => (
+const ColumnLoading = ({ title = '', icon = ' ' }) => (
   <Column>
-    <ColumnHeader icon=' ' title='' multiColumn={false} />
+    <ColumnHeader icon={icon} title={title} multiColumn={false} />
     <div className='scrollable' />
   </Column>
 );
 
+ColumnLoading.propTypes = {
+  title: PropTypes.node,
+  icon: PropTypes.string,
+};
+
 export default ColumnLoading;
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index 5fa27599f..9ff913774 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
-import ReactSwipeable from 'react-swipeable';
-import { getPreviousLink, getNextLink } from './tabs_bar';
+import ReactSwipeableViews from 'react-swipeable-views';
+import { links, getIndex, getLink } from './tabs_bar';
 
 import BundleContainer from '../containers/bundle_container';
 import ColumnLoading from './column_loading';
@@ -32,21 +32,29 @@ export default class ColumnsArea extends ImmutablePureComponent {
     children: PropTypes.node,
   };
 
-  handleRightSwipe = () => {
-    const previousLink = getPreviousLink(this.context.router.history.location.pathname);
-
-    if (previousLink) {
-      this.context.router.history.push(previousLink);
-    }
+  handleSwipe = (index) => {
+    window.requestAnimationFrame(() => {
+      window.requestAnimationFrame(() => {
+        this.context.router.history.push(getLink(index));
+      });
+    });
   }
 
-  handleLeftSwipe = () => {
-    const previousLink = getNextLink(this.context.router.history.location.pathname);
+  renderView = (link, index) => {
+    const columnIndex = getIndex(this.context.router.history.location.pathname);
+    const title = link.props.children[1] && React.cloneElement(link.props.children[1]);
+    const icon = (link.props.children[0] || link.props.children).props.className.split(' ')[2].split('-')[1];
 
-    if (previousLink) {
-      this.context.router.history.push(previousLink);
-    }
-  };
+    const view = (index === columnIndex) ?
+      React.cloneElement(this.props.children) :
+      <ColumnLoading title={title} icon={icon} />;
+
+    return (
+      <div className='columns-area' key={index}>
+        {view}
+      </div>
+    );
+  }
 
   renderLoading = () => {
     return <ColumnLoading />;
@@ -59,12 +67,14 @@ export default class ColumnsArea extends ImmutablePureComponent {
   render () {
     const { columns, children, singleColumn } = this.props;
 
+    const columnIndex = getIndex(this.context.router.history.location.pathname);
+
     if (singleColumn) {
-      return (
-        <ReactSwipeable onSwipedLeft={this.handleLeftSwipe} onSwipedRight={this.handleRightSwipe} delta={30} className='columns-area'>
-          {children}
-        </ReactSwipeable>
-      );
+      return columnIndex !== -1 ? (
+        <ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} animateTransitions={false} style={{ height: '100%' }}>
+          {links.map(this.renderView)}
+        </ReactSwipeableViews>
+      ) : <div className='columns-area'>{children}></div>;
     }
 
     return (
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index a5b9dc19f..769e18820 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -1,5 +1,5 @@
 import React from 'react';
-import ReactSwipeable from 'react-swipeable';
+import ReactSwipeableViews from 'react-swipeable-views';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import ExtendedVideoPlayer from '../../../components/extended_video_player';
@@ -26,6 +26,10 @@ export default class MediaModal extends ImmutablePureComponent {
     index: null,
   };
 
+  handleSwipe = (index) => {
+    this.setState({ index: (index) % this.props.media.size });
+  }
+
   handleNextClick = () => {
     this.setState({ index: (this.getIndex() + 1) % this.props.media.size });
   }
@@ -74,10 +78,12 @@ export default class MediaModal extends ImmutablePureComponent {
     }
 
     if (attachment.get('type') === 'image') {
-      const width  = attachment.getIn(['meta', 'original', 'width']) || null;
-      const height = attachment.getIn(['meta', 'original', 'height']) || null;
+      content = media.map((image) => {
+        const width  = image.getIn(['meta', 'original', 'width']) || null;
+        const height = image.getIn(['meta', 'original', 'height']) || null;
 
-      content = <ImageLoader previewSrc={attachment.get('preview_url')} src={url} width={width} height={height} />;
+        return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} key={image.get('preview_url')} />;
+      }).toArray();
     } else if (attachment.get('type') === 'gifv') {
       content = <ExtendedVideoPlayer src={url} muted controls={false} />;
     }
@@ -88,9 +94,9 @@ export default class MediaModal extends ImmutablePureComponent {
 
         <div className='media-modal__content'>
           <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
-          <ReactSwipeable onSwipedRight={this.handlePrevClick} onSwipedLeft={this.handleNextClick}>
+          <ReactSwipeableViews onChangeIndex={this.handleSwipe} index={index}>
             {content}
-          </ReactSwipeable>
+          </ReactSwipeableViews>
         </div>
 
         {rightNav}
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index b056357a2..189bd8665 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -3,11 +3,9 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ReactSwipeable from 'react-swipeable';
+import ReactSwipeableViews from 'react-swipeable-views';
 import classNames from 'classnames';
 import Permalink from '../../../components/permalink';
-import TransitionMotion from 'react-motion/lib/TransitionMotion';
-import spring from 'react-motion/lib/spring';
 import ComposeForm from '../../compose/components/compose_form';
 import Search from '../../compose/components/search';
 import NavigationBar from '../../compose/components/navigation_bar';
@@ -227,6 +225,10 @@ export default class OnboardingModal extends React.PureComponent {
     }));
   }
 
+  handleSwipe = (index) => {
+    this.setState({ currentIndex: index });
+  }
+
   handleKeyUp = ({ key }) => {
     switch (key) {
     case 'ArrowLeft':
@@ -263,30 +265,18 @@ export default class OnboardingModal extends React.PureComponent {
       </button>
     );
 
-    const styles = pages.map((data, i) => ({
-      key: `page-${i}`,
-      data,
-      style: {
-        opacity: spring(i === currentIndex ? 1 : 0),
-      },
-    }));
-
     return (
       <div className='modal-root__modal onboarding-modal'>
-        <TransitionMotion styles={styles}>
-          {interpolatedStyles => (
-            <ReactSwipeable onSwipedRight={this.handlePrev} onSwipedLeft={this.handleNext} className='onboarding-modal__pager'>
-              {interpolatedStyles.map(({ key, data, style }, i) => {
-                const className = classNames('onboarding-modal__page__wrapper', {
-                  'onboarding-modal__page__wrapper--active': i === currentIndex,
-                });
-                return (
-                  <div key={key} style={style} className={className}>{data}</div>
-                );
-              })}
-            </ReactSwipeable>
-          )}
-        </TransitionMotion>
+        <ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='onboarding-modal__pager'>
+          {pages.map((page, i) => {
+            const className = classNames('onboarding-modal__page__wrapper', {
+              'onboarding-modal__page__wrapper--active': i === currentIndex,
+            });
+            return (
+              <div key={i} className={className}>{page}</div>
+            );
+          })}
+        </ReactSwipeableViews>
 
         <div className='onboarding-modal__paginator'>
           <div>
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.js b/app/javascript/mastodon/features/ui/components/tabs_bar.js
index c2e6c88b5..b4153ff45 100644
--- a/app/javascript/mastodon/features/ui/components/tabs_bar.js
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.js
@@ -2,7 +2,7 @@ import React from 'react';
 import NavLink from 'react-router-dom/NavLink';
 import { FormattedMessage } from 'react-intl';
 
-const links = [
+export const links = [
   <NavLink className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
   <NavLink className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
   <NavLink className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
@@ -13,25 +13,13 @@ const links = [
   <NavLink className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></NavLink>,
 ];
 
-export function getPreviousLink (path) {
-  const index = links.findIndex(link => link.props.to === path);
-
-  if (index > 0) {
-    return links[index - 1].props.to;
-  }
-
-  return null;
-};
-
-export function getNextLink (path) {
-  const index = links.findIndex(link => link.props.to === path);
-
-  if (index !== -1 && index < links.length - 1) {
-    return links[index + 1].props.to;
-  }
+export function getIndex (path) {
+  return links.findIndex(link => link.props.to === path);
+}
 
-  return null;
-};
+export function getLink (index) {
+  return links[index].props.to;
+}
 
 export default class TabsBar extends React.Component {
 
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index 66d2715da..397126ec1 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -1266,6 +1266,23 @@
   .columns-area {
     padding: 10px;
   }
+
+  .react-swipeable-view-container .columns-area {
+    height: calc(100% - 20px) !important;
+  }
+}
+
+.react-swipeable-view-container {
+  &,
+  .columns-area,
+  .drawer,
+  .column {
+    height: 100%;
+  }
+}
+
+.react-swipeable-view-container > * {
+  height: 100%;
 }
 
 .column {
@@ -2910,7 +2927,7 @@ button.icon-button.active i.fa-retweet {
   video {
     max-width: 80vw;
     max-height: 80vh;
-    width: auto;
+    width: 100%;
     height: auto;
   }
 
@@ -2938,7 +2955,26 @@ button.icon-button.active i.fa-retweet {
   flex-direction: column;
 }
 
-.onboarding-modal__pager,
+.onboarding-modal__pager {
+  height: 80vh;
+  width: 80vw;
+  max-width: 520px;
+  max-height: 420px;
+
+  .react-swipeable-view-container > div {
+    width: 100%;
+    height: 100%;
+    box-sizing: border-box;
+    padding: 25px;
+    display: none;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    display: flex;
+    user-select: text;
+  }
+}
+
 .error-modal__body {
   height: 80vh;
   width: 80vw;