The code powering m.abunchtell.com https://m.abunchtell.com
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

276 lines
11 KiB

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  6. import Permalink from '../../../components/permalink';
  7. import TransitionMotion from 'react-motion/lib/TransitionMotion';
  8. import spring from 'react-motion/lib/spring';
  9. import ComposeForm from '../../compose/components/compose_form';
  10. import Search from '../../compose/components/search';
  11. import NavigationBar from '../../compose/components/navigation_bar';
  12. import ColumnHeader from './column_header';
  13. import Immutable from 'immutable';
  14. const noop = () => { };
  15. const messages = defineMessages({
  16. home_title: { id: 'column.home', defaultMessage: 'Home' },
  17. notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
  18. local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
  19. federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' },
  20. });
  21. const PageOne = ({ acct, domain }) => (
  22. <div className='onboarding-modal__page onboarding-modal__page-one'>
  23. <div style={{ flex: '0 0 auto' }}>
  24. <div className='onboarding-modal__page-one__elephant-friend' />
  25. </div>
  26. <div>
  27. <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
  28. <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
  29. <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p>
  30. </div>
  31. </div>
  32. );
  33. PageOne.propTypes = {
  34. acct: PropTypes.string.isRequired,
  35. domain: PropTypes.string.isRequired,
  36. };
  37. const PageTwo = ({ me }) => (
  38. <div className='onboarding-modal__page onboarding-modal__page-two'>
  39. <div className='figure non-interactive'>
  40. <div className='pseudo-drawer'>
  41. <NavigationBar account={me} />
  42. </div>
  43. <ComposeForm
  44. text='Awoo! #introductions'
  45. suggestions={Immutable.List()}
  46. mentionedDomains={[]}
  47. spoiler={false}
  48. onChange={noop}
  49. onSubmit={noop}
  50. onPaste={noop}
  51. onPickEmoji={noop}
  52. onChangeSpoilerText={noop}
  53. onClearSuggestions={noop}
  54. onFetchSuggestions={noop}
  55. onSuggestionSelected={noop}
  56. />
  57. </div>
  58. <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
  59. </div>
  60. );
  61. PageTwo.propTypes = {
  62. me: ImmutablePropTypes.map.isRequired,
  63. };
  64. const PageThree = ({ me, domain }) => (
  65. <div className='onboarding-modal__page onboarding-modal__page-three'>
  66. <div className='figure non-interactive'>
  67. <Search
  68. value=''
  69. onChange={noop}
  70. onSubmit={noop}
  71. onClear={noop}
  72. onShow={noop}
  73. />
  74. <div className='pseudo-drawer'>
  75. <NavigationBar account={me} />
  76. </div>
  77. </div>
  78. <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p>
  79. <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
  80. </div>
  81. );
  82. PageThree.propTypes = {
  83. me: ImmutablePropTypes.map.isRequired,
  84. domain: PropTypes.string.isRequired,
  85. };
  86. const PageFour = ({ domain, intl }) => (
  87. <div className='onboarding-modal__page onboarding-modal__page-four'>
  88. <div className='onboarding-modal__page-four__columns'>
  89. <div className='row'>
  90. <div>
  91. <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
  92. <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p>
  93. </div>
  94. <div>
  95. <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
  96. <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
  97. </div>
  98. </div>
  99. <div className='row'>
  100. <div>
  101. <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
  102. </div>
  103. <div>
  104. <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
  105. </div>
  106. </div>
  107. <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
  108. </div>
  109. </div>
  110. );
  111. PageFour.propTypes = {
  112. domain: PropTypes.string.isRequired,
  113. intl: PropTypes.object.isRequired,
  114. };
  115. const PageSix = ({ admin, domain }) => {
  116. let adminSection = '';
  117. if (admin) {
  118. adminSection = (
  119. <p>
  120. <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
  121. <br />
  122. <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{ domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/>
  123. </p>
  124. );
  125. }
  126. return (
  127. <div className='onboarding-modal__page onboarding-modal__page-six'>
  128. <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
  129. {adminSection}
  130. <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
  131. <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
  132. <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
  133. </div>
  134. );
  135. };
  136. PageSix.propTypes = {
  137. admin: ImmutablePropTypes.map,
  138. domain: PropTypes.string.isRequired,
  139. };
  140. const mapStateToProps = state => ({
  141. me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
  142. admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
  143. domain: state.getIn(['meta', 'domain']),
  144. });
  145. class OnboardingModal extends React.PureComponent {
  146. static propTypes = {
  147. onClose: PropTypes.func.isRequired,
  148. intl: PropTypes.object.isRequired,
  149. me: ImmutablePropTypes.map.isRequired,
  150. domain: PropTypes.string.isRequired,
  151. admin: ImmutablePropTypes.map,
  152. };
  153. state = {
  154. currentIndex: 0,
  155. };
  156. handleSkip = (e) => {
  157. e.preventDefault();
  158. this.props.onClose();
  159. }
  160. handleDot = (e) => {
  161. const i = Number(e.currentTarget.getAttribute('data-index'));
  162. e.preventDefault();
  163. this.setState({ currentIndex: i });
  164. }
  165. handleNext = () => {
  166. this.setState(({ currentIndex }) => ({
  167. currentIndex: currentIndex + 1,
  168. }));
  169. }
  170. handleClose = () => {
  171. this.props.onClose();
  172. }
  173. render () {
  174. const { me, admin, domain, intl } = this.props;
  175. const pages = [
  176. <PageOne acct={me.get('acct')} domain={domain} />,
  177. <PageTwo me={me} />,
  178. <PageThree me={me} domain={domain} />,
  179. <PageFour domain={domain} intl={intl} />,
  180. <PageSix admin={admin} domain={domain} />,
  181. ];
  182. const { currentIndex } = this.state;
  183. const hasMore = currentIndex < pages.length - 1;
  184. const nextOrDoneBtn = hasMore ? (
  185. <button
  186. onClick={this.handleNext}
  187. className='onboarding-modal__nav onboarding-modal__next'
  188. >
  189. <FormattedMessage id='onboarding.next' defaultMessage='Next' />
  190. </button>
  191. ) : (
  192. <button
  193. onClick={this.handleClose}
  194. className='onboarding-modal__nav onboarding-modal__done'
  195. >
  196. <FormattedMessage id='onboarding.done' defaultMessage='Done' />
  197. </button>
  198. );
  199. const styles = pages.map((page, i) => ({
  200. key: `page-${i}`,
  201. style: { opacity: spring(i === currentIndex ? 1 : 0) },
  202. }));
  203. return (
  204. <div className='modal-root__modal onboarding-modal'>
  205. <TransitionMotion styles={styles}>
  206. {interpolatedStyles =>
  207. <div className='onboarding-modal__pager'>
  208. {pages.map((page, i) =>
  209. <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div>
  210. )}
  211. </div>
  212. }
  213. </TransitionMotion>
  214. <div className='onboarding-modal__paginator'>
  215. <div>
  216. <button
  217. onClick={this.handleSkip}
  218. className='onboarding-modal__nav onboarding-modal__skip'
  219. >
  220. <FormattedMessage id='onboarding.skip' defaultMessage='Skip' />
  221. </button>
  222. </div>
  223. <div className='onboarding-modal__dots'>
  224. {pages.map((_, i) => <div key={i} role='button' tabIndex='0' data-index={i} onClick={this.handleDot} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
  225. </div>
  226. <div>
  227. {nextOrDoneBtn}
  228. </div>
  229. </div>
  230. </div>
  231. );
  232. }
  233. }
  234. export default connect(mapStateToProps)(injectIntl(OnboardingModal));