* feat: Replace react-swipeable with react-swipeable-views * fix: iOS 9master
@@ -10,7 +10,7 @@ export default class ColumnHeader extends React.PureComponent { | |||||
}; | }; | ||||
static propTypes = { | static propTypes = { | ||||
title: PropTypes.string.isRequired, | |||||
title: PropTypes.node.isRequired, | |||||
icon: PropTypes.string.isRequired, | icon: PropTypes.string.isRequired, | ||||
active: PropTypes.bool, | active: PropTypes.bool, | ||||
multiColumn: PropTypes.bool, | multiColumn: PropTypes.bool, | ||||
@@ -1,13 +1,19 @@ | |||||
import React from 'react'; | import React from 'react'; | ||||
import PropTypes from 'prop-types'; | |||||
import Column from '../../../components/column'; | import Column from '../../../components/column'; | ||||
import ColumnHeader from '../../../components/column_header'; | import ColumnHeader from '../../../components/column_header'; | ||||
const ColumnLoading = () => ( | |||||
const ColumnLoading = ({ title = '', icon = ' ' }) => ( | |||||
<Column> | <Column> | ||||
<ColumnHeader icon=' ' title='' multiColumn={false} /> | |||||
<ColumnHeader icon={icon} title={title} multiColumn={false} /> | |||||
<div className='scrollable' /> | <div className='scrollable' /> | ||||
</Column> | </Column> | ||||
); | ); | ||||
ColumnLoading.propTypes = { | |||||
title: PropTypes.node, | |||||
icon: PropTypes.string, | |||||
}; | |||||
export default ColumnLoading; | export default ColumnLoading; |
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; | |||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
import ImmutablePureComponent from 'react-immutable-pure-component'; | 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 BundleContainer from '../containers/bundle_container'; | ||||
import ColumnLoading from './column_loading'; | import ColumnLoading from './column_loading'; | ||||
@@ -32,21 +32,29 @@ export default class ColumnsArea extends ImmutablePureComponent { | |||||
children: PropTypes.node, | 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 = () => { | renderLoading = () => { | ||||
return <ColumnLoading />; | return <ColumnLoading />; | ||||
@@ -59,12 +67,14 @@ export default class ColumnsArea extends ImmutablePureComponent { | |||||
render () { | render () { | ||||
const { columns, children, singleColumn } = this.props; | const { columns, children, singleColumn } = this.props; | ||||
const columnIndex = getIndex(this.context.router.history.location.pathname); | |||||
if (singleColumn) { | 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 ( | return ( | ||||
@@ -1,5 +1,5 @@ | |||||
import React from 'react'; | import React from 'react'; | ||||
import ReactSwipeable from 'react-swipeable'; | |||||
import ReactSwipeableViews from 'react-swipeable-views'; | |||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
import ExtendedVideoPlayer from '../../../components/extended_video_player'; | import ExtendedVideoPlayer from '../../../components/extended_video_player'; | ||||
@@ -26,6 +26,10 @@ export default class MediaModal extends ImmutablePureComponent { | |||||
index: null, | index: null, | ||||
}; | }; | ||||
handleSwipe = (index) => { | |||||
this.setState({ index: (index) % this.props.media.size }); | |||||
} | |||||
handleNextClick = () => { | handleNextClick = () => { | ||||
this.setState({ index: (this.getIndex() + 1) % this.props.media.size }); | 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') { | 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') { | } else if (attachment.get('type') === 'gifv') { | ||||
content = <ExtendedVideoPlayer src={url} muted controls={false} />; | content = <ExtendedVideoPlayer src={url} muted controls={false} />; | ||||
} | } | ||||
@@ -88,9 +94,9 @@ export default class MediaModal extends ImmutablePureComponent { | |||||
<div className='media-modal__content'> | <div className='media-modal__content'> | ||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> | <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} | {content} | ||||
</ReactSwipeable> | |||||
</ReactSwipeableViews> | |||||
</div> | </div> | ||||
{rightNav} | {rightNav} | ||||
@@ -3,11 +3,9 @@ import { connect } from 'react-redux'; | |||||
import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||
import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
import ReactSwipeable from 'react-swipeable'; | |||||
import ReactSwipeableViews from 'react-swipeable-views'; | |||||
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import Permalink from '../../../components/permalink'; | 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 ComposeForm from '../../compose/components/compose_form'; | ||||
import Search from '../../compose/components/search'; | import Search from '../../compose/components/search'; | ||||
import NavigationBar from '../../compose/components/navigation_bar'; | 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 }) => { | handleKeyUp = ({ key }) => { | ||||
switch (key) { | switch (key) { | ||||
case 'ArrowLeft': | case 'ArrowLeft': | ||||
@@ -263,30 +265,18 @@ export default class OnboardingModal extends React.PureComponent { | |||||
</button> | </button> | ||||
); | ); | ||||
const styles = pages.map((data, i) => ({ | |||||
key: `page-${i}`, | |||||
data, | |||||
style: { | |||||
opacity: spring(i === currentIndex ? 1 : 0), | |||||
}, | |||||
})); | |||||
return ( | return ( | ||||
<div className='modal-root__modal onboarding-modal'> | <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 className='onboarding-modal__paginator'> | ||||
<div> | <div> | ||||
@@ -2,7 +2,7 @@ import React from 'react'; | |||||
import NavLink from 'react-router-dom/NavLink'; | import NavLink from 'react-router-dom/NavLink'; | ||||
import { FormattedMessage } from 'react-intl'; | 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='/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='/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>, | <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>, | <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 { | export default class TabsBar extends React.Component { | ||||
@@ -1266,6 +1266,23 @@ | |||||
.columns-area { | .columns-area { | ||||
padding: 10px; | 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 { | .column { | ||||
@@ -2910,7 +2927,7 @@ button.icon-button.active i.fa-retweet { | |||||
video { | video { | ||||
max-width: 80vw; | max-width: 80vw; | ||||
max-height: 80vh; | max-height: 80vh; | ||||
width: auto; | |||||
width: 100%; | |||||
height: auto; | height: auto; | ||||
} | } | ||||
@@ -2938,7 +2955,26 @@ button.icon-button.active i.fa-retweet { | |||||
flex-direction: column; | 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 { | .error-modal__body { | ||||
height: 80vh; | height: 80vh; | ||||
width: 80vw; | width: 80vw; | ||||
@@ -88,7 +88,7 @@ | |||||
"react-router-dom": "^4.1.1", | "react-router-dom": "^4.1.1", | ||||
"react-router-scroll": "ytase/react-router-scroll#build", | "react-router-scroll": "ytase/react-router-scroll#build", | ||||
"react-simple-dropdown": "^3.0.0", | "react-simple-dropdown": "^3.0.0", | ||||
"react-swipeable": "^4.0.1", | |||||
"react-swipeable-views": "^0.12.3", | |||||
"react-textarea-autosize": "^5.0.7", | "react-textarea-autosize": "^5.0.7", | ||||
"react-toggle": "^4.0.1", | "react-toggle": "^4.0.1", | ||||
"redis": "^2.7.1", | "redis": "^2.7.1", | ||||
@@ -1259,7 +1259,7 @@ babel-register@^6.24.1: | |||||
mkdirp "^0.5.1" | mkdirp "^0.5.1" | ||||
source-map-support "^0.4.2" | 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" | version "6.23.0" | ||||
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" | ||||
dependencies: | dependencies: | ||||
@@ -2318,7 +2318,7 @@ doctrine@^2.0.0: | |||||
esutils "^2.0.2" | esutils "^2.0.2" | ||||
isarray "^1.0.0" | 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" | version "3.2.1" | ||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" | 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" | version "1.4.1" | ||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1" | 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" | version "2.1.9" | ||||
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa" | 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" | stringify-object "2.4.0" | ||||
traverse "^0.6.6" | 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: | react-html-attributes@^1.3.0: | ||||
version "1.3.0" | version "1.3.0" | ||||
resolved "https://registry.yarnpkg.com/react-html-attributes/-/react-html-attributes-1.3.0.tgz#c97896e9cac47ad9c4e6618b835029a826f5d28c" | 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: | dependencies: | ||||
prop-types "^15.5.4" | 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: | 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: | react-test-renderer@^15.6.1: | ||||
version "15.6.1" | version "15.6.1" | ||||