gab-social/app/javascript/gabsocial/features/introduction.js
2020-04-07 21:06:59 -04:00

235 lines
6.5 KiB
JavaScript

import ReactSwipeableViews from 'react-swipeable-views';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import { closeOnboarding } from '../actions/onboarding';
// : todo :
class FrameWelcome extends React.Component {
static propTypes = {
domain: PropTypes.string.isRequired,
onNext: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps) {
return nextProps.domain !== this.props.domain;
}
render() {
const { domain, onNext } = this.props;
return (
<div className='introduction__frame'>
<div className='introduction__text introduction__text--centered'>
<h3>
<FormattedMessage id='introduction.welcome.headline' defaultMessage='First steps' />
</h3>
<p>
<FormattedMessage
id='introduction.welcome.text'
defaultMessage="Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name."
values={{
domain: <code>{domain}</code>
}}
/>
</p>
</div>
<div className='introduction__action'>
<button className='button' onClick={onNext}>
<FormattedMessage id='introduction.welcome.action' defaultMessage="Let's go!" />
</button>
</div>
</div>
);
}
}
class FrameFederation extends React.Component {
static propTypes = {
onNext: PropTypes.func.isRequired,
}
shouldComponentUpdate() {
return false;
}
render() {
return (
<div className='introduction__frame'>
<div className='introduction__text introduction__text--columnized'>
<div>
<h3>
<FormattedMessage id='introduction.federation.home.headline' defaultMessage='Home' />
</h3>
<p>
<FormattedMessage
id='introduction.federation.home.text'
defaultMessage='Posts from people you follow will appear in your home feed. You can follow anyone on any server!'
/>
</p>
</div>
</div>
<div className='introduction__action'>
<button className='button' onClick={onNext}>
<FormattedMessage id='introduction.federation.action' defaultMessage='Next' />
</button>
</div>
</div>
)
}
};
class FrameInteractions extends React.Component {
static propTypes = {
onNext: PropTypes.func.isRequired,
};
shouldComponentUpdate() {
return false;
}
render() {
return (
<div className='introduction__frame'>
<div className='introduction__text introduction__text--columnized'>
<div>
<h3>
<FormattedMessage id='introduction.interactions.reply.headline' defaultMessage='Reply' />
</h3>
<p>
<FormattedMessage
id='introduction.interactions.reply.text'
defaultMessage="You can reply to other people's and your own gabs, which will chain them together in a conversation."
/>
</p>
</div>
<div>
<h3>
<FormattedMessage id='introduction.interactions.repost.headline' defaultMessage='Repost' />
</h3>
<p>
<FormattedMessage id='introduction.interactions.repost.text' defaultMessage="You can share other people's gabs with your followers by reposting them." />
</p>
</div>
<div>
<h3>
<FormattedMessage id='introduction.interactions.favorite.headline' defaultMessage='Favorite' />
</h3>
<p>
<FormattedMessage id='introduction.interactions.favorite.text' defaultMessage='You can save a gab for later, and let the author know that you liked it, by favoriting it.' />
</p>
</div>
</div>
<div className='introduction__action'>
<button className='button' onClick={onNext}>
<FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' />
</button>
</div>
</div>
);
}
}
export default
@connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
class Introduction extends PureComponent {
static propTypes = {
domain: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
};
state = {
currentIndex: 0,
};
componentWillMount () {
this.pages = [
<FrameWelcome domain={this.props.domain} onNext={this.handleNext} />,
<FrameFederation onNext={this.handleNext} />,
<FrameInteractions onNext={this.handleFinish} />,
];
}
componentDidMount() {
window.addEventListener('keyup', this.handleKeyUp);
}
componentWillUnmount() {
window.addEventListener('keyup', this.handleKeyUp);
}
handleDot = (e) => {
const i = Number(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.setState({ currentIndex: i });
}
handlePrev = () => {
this.setState(({ currentIndex }) => ({
currentIndex: Math.max(0, currentIndex - 1),
}));
}
handleNext = () => {
const { pages } = this;
this.setState(({ currentIndex }) => ({
currentIndex: Math.min(currentIndex + 1, pages.length - 1),
}));
}
handleSwipe = (index) => {
this.setState({ currentIndex: index });
}
handleFinish = () => {
this.props.dispatch(closeOnboarding());
}
handleKeyUp = ({ key }) => {
switch (key) {
case 'ArrowLeft':
this.handlePrev();
break;
case 'ArrowRight':
this.handleNext();
break;
}
}
render () {
const { currentIndex } = this.state;
const { pages } = this;
return (
<div className='introduction'>
<ReactSwipeableViews index={currentIndex} onChangeIndex={this.handleSwipe} className='introduction__pager'>
{pages.map((page, i) => (
<div key={i} className={classNames('introduction__frame-wrapper', { 'active': i === currentIndex })}>{page}</div>
))}
</ReactSwipeableViews>
<div className='introduction__dots'>
{pages.map((_, i) => (
<div
key={`dot-${i}`}
role='button'
tabIndex='0'
data-index={i}
onClick={this.handleDot}
className={classNames('introduction__dot', { active: i === currentIndex })}
/>
))}
</div>
</div>
);
}
}