logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe
commit: fc4c74660b690038ae48264f9d5b0230df58acc4
parent: caf938562ef0d0fdb03bf57f15bbab8d76c5b4c0
Author: Sorin Davidoi <sorin.davidoi@gmail.com>
Date:   Sun,  9 Jul 2017 15:02:26 +0200

Swipeable views (#4105)

* feat: Replace react-swipeable with react-swipeable-views

* fix: iOS 9

Diffstat:

Mapp/javascript/mastodon/components/column_header.js2+-
Mapp/javascript/mastodon/features/ui/components/column_loading.js10++++++++--
Mapp/javascript/mastodon/features/ui/components/columns_area.js48+++++++++++++++++++++++++++++-------------------
Mapp/javascript/mastodon/features/ui/components/media_modal.js18++++++++++++------
Mapp/javascript/mastodon/features/ui/components/onboarding_modal.js40+++++++++++++++-------------------------
Mapp/javascript/mastodon/features/ui/components/tabs_bar.js26+++++++-------------------
Mapp/javascript/styles/components.scss40++++++++++++++++++++++++++++++++++++++--
Mpackage.json2+-
Myarn.lock46+++++++++++++++++++++++++++++++++++++++-------
9 files changed, 150 insertions(+), 82 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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; diff --git a/package.json b/package.json @@ -88,7 +88,7 @@ "react-router-dom": "^4.1.1", "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", - "react-swipeable": "^4.0.1", + "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", "redis": "^2.7.1", diff --git a/yarn.lock b/yarn.lock @@ -1259,7 +1259,7 @@ babel-register@^6.24.1: mkdirp "^0.5.1" source-map-support "^0.4.2" -babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.5.0, babel-runtime@^6.9.2: +babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.5.0, babel-runtime@^6.9.2: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" dependencies: @@ -2318,7 +2318,7 @@ doctrine@^2.0.0: esutils "^2.0.2" isarray "^1.0.0" -"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.0.0: +"dom-helpers@^2.4.0 || ^3.0.0", dom-helpers@^3.0.0, dom-helpers@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" @@ -3937,7 +3937,7 @@ jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" -keycode@^2.1.8: +keycode@^2.1.7, keycode@^2.1.8: version "2.1.9" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" @@ -5711,6 +5711,15 @@ react-element-to-jsx-string@^5.0.0: stringify-object "2.4.0" traverse "^0.6.6" +react-event-listener@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.4.5.tgz#e3e895a0970cf14ee8f890113af68197abf3d0b1" + dependencies: + babel-runtime "^6.20.0" + fbjs "^0.8.4" + prop-types "^15.5.4" + warning "^3.0.0" + react-html-attributes@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/react-html-attributes/-/react-html-attributes-1.3.0.tgz#c97896e9cac47ad9c4e6618b835029a826f5d28c" @@ -5875,11 +5884,34 @@ react-style-proptype@^3.0.0: dependencies: prop-types "^15.5.4" -react-swipeable@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-4.0.1.tgz#2cb3a04a52ccebf5361881b30e233dc13ee4b115" +react-swipeable-views-core@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.11.1.tgz#61d046799f90725bbf91a0eb3abcab805c774cac" dependencies: - prop-types "^15.5.8" + babel-runtime "^6.23.0" + warning "^3.0.0" + +react-swipeable-views-utils@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/react-swipeable-views-utils/-/react-swipeable-views-utils-0.12.0.tgz#4ff11f20a8da0561f623876d9fd691116e1a6a03" + dependencies: + babel-runtime "^6.23.0" + fbjs "^0.8.4" + keycode "^2.1.7" + prop-types "^15.5.4" + react-event-listener "^0.4.5" + react-swipeable-views-core "^0.11.1" + +react-swipeable-views@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/react-swipeable-views/-/react-swipeable-views-0.12.3.tgz#b0d3f417bcbcd06afda2f8437c15e8360a568744" + dependencies: + babel-runtime "^6.23.0" + dom-helpers "^3.2.1" + prop-types "^15.5.4" + react-swipeable-views-core "^0.11.1" + react-swipeable-views-utils "^0.12.0" + warning "^3.0.0" react-test-renderer@^15.6.1: version "15.6.1"