diff options
Diffstat (limited to 'app/assets/javascripts/components/features/ui')
18 files changed, 0 insertions, 1182 deletions
diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx deleted file mode 100644 index 3bd82ceee..000000000 --- a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import IconButton from '../../../components/icon_button'; -import Button from '../../../components/button'; -import StatusContent from '../../../components/status_content'; -import Avatar from '../../../components/avatar'; -import RelativeTimestamp from '../../../components/relative_timestamp'; -import DisplayName from '../../../components/display_name'; - -const messages = defineMessages({ - reblog: { id: 'status.reblog', defaultMessage: 'Boost' } -}); - -class BoostModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleReblog = this.handleReblog.bind(this); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleReblog() { - this.props.onReblog(this.props.status); - this.props.onClose(); - } - - handleAccountClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.props.onClose(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - } - - render () { - const { status, intl, onClose } = this.props; - - return ( - <div className='modal-root__modal boost-modal'> - <div className='boost-modal__container'> - <div className='status light'> - <div className='boost-modal__status-header'> - <div className='boost-modal__status-time'> - <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a> - </div> - - <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'> - <div className='status__avatar'> - <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /> - </div> - - <DisplayName account={status.get('account')} /> - </a> - </div> - - <StatusContent status={status} /> - </div> - </div> - - <div className='boost-modal__action-bar'> - <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div> - <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} /> - </div> - </div> - ); - } - -} - -BoostModal.contextTypes = { - router: PropTypes.object -}; - -BoostModal.propTypes = { - status: ImmutablePropTypes.map.isRequired, - onReblog: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(BoostModal); diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx deleted file mode 100644 index aa09d0fd2..000000000 --- a/app/assets/javascripts/components/features/ui/components/column.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ColumnHeader from './column_header'; -import PropTypes from 'prop-types'; - -const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b; - -const scrollTop = (node) => { - const startTime = Date.now(); - const offset = node.scrollTop; - const targetY = -offset; - const duration = 1000; - let interrupt = false; - - const step = () => { - const elapsed = Date.now() - startTime; - const percentage = elapsed / duration; - - if (percentage > 1 || interrupt) { - return; - } - - node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration); - requestAnimationFrame(step); - }; - - step(); - - return () => { - interrupt = true; - }; -}; - -class Column extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleHeaderClick = this.handleHeaderClick.bind(this); - this.handleWheel = this.handleWheel.bind(this); - } - - handleHeaderClick () { - const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable'); - if (!scrollable) { - return; - } - this._interruptScrollAnimation = scrollTop(scrollable); - } - - handleWheel () { - if (typeof this._interruptScrollAnimation !== 'undefined') { - this._interruptScrollAnimation(); - } - } - - render () { - const { heading, icon, children, active, hideHeadingOnMobile } = this.props; - - let columnHeaderId = null - let header = ''; - - if (heading) { - columnHeaderId = heading.replace(/ /g, '-') - header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId}/>; - } - return ( - <div role='region' aria-labelledby={columnHeaderId} className='column' onWheel={this.handleWheel}> - {header} - {children} - </div> - ); - } - -} - -Column.propTypes = { - heading: PropTypes.string, - icon: PropTypes.string, - children: PropTypes.node, - active: PropTypes.bool, - hideHeadingOnMobile: PropTypes.bool -}; - -export default Column; diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx deleted file mode 100644 index 7ccd72e0b..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_header.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types' - -class ColumnHeader extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick () { - this.props.onClick(); - } - - render () { - const { type, active, hideOnMobile, columnHeaderId } = this.props; - - let icon = ''; - - if (this.props.icon) { - icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />; - } - - return ( - <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}> - {icon} - {type} - </div> - ); - } - -} - -ColumnHeader.propTypes = { - icon: PropTypes.string, - type: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func, - hideOnMobile: PropTypes.bool, - columnHeaderId: PropTypes.string -}; - -export default ColumnHeader; diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx deleted file mode 100644 index 820e4246a..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_link.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; -import { Link } from 'react-router'; - -const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => { - if (href) { - return ( - <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}> - <i className={`fa fa-fw fa-${icon} column-link__icon`} /> - {text} - </a> - ); - } else { - return ( - <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}> - <i className={`fa fa-fw fa-${icon} column-link__icon`} /> - {text} - </Link> - ); - } -}; - -ColumnLink.propTypes = { - icon: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - to: PropTypes.string, - href: PropTypes.string, - method: PropTypes.string, - hideOnMobile: PropTypes.bool -}; - -export default ColumnLink; diff --git a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx b/app/assets/javascripts/components/features/ui/components/column_subheading.jsx deleted file mode 100644 index 061c8be6c..000000000 --- a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import PropTypes from 'prop-types'; - -const ColumnSubheading = ({ text }) => { - return ( - <div className='column-subheading'> - {text} - </div> - ); - }; - -ColumnSubheading.propTypes = { - text: PropTypes.string.isRequired, -}; - -export default ColumnSubheading; diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx deleted file mode 100644 index 360a759ae..000000000 --- a/app/assets/javascripts/components/features/ui/components/columns_area.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import PropTypes from 'prop-types'; - -class ColumnsArea extends React.PureComponent { - - render () { - return ( - <div className='columns-area'> - {this.props.children} - </div> - ); - } - -} - -ColumnsArea.propTypes = { - children: PropTypes.node -}; - -export default ColumnsArea; diff --git a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx b/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx deleted file mode 100644 index 914c12f82..000000000 --- a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Button from '../../../components/button'; - -class ConfirmationModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - this.handleCancel = this.handleCancel.bind(this); - } - - handleClick () { - this.props.onClose(); - this.props.onConfirm(); - } - - handleCancel (e) { - e.preventDefault(); - this.props.onClose(); - } - - render () { - const { intl, message, confirm, onConfirm, onClose } = this.props; - - return ( - <div className='modal-root__modal confirmation-modal'> - <div className='confirmation-modal__container'> - {message} - </div> - - <div className='confirmation-modal__action-bar'> - <div><a href='#' onClick={this.handleCancel}><FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /></a></div> - <Button text={confirm} onClick={this.handleClick} /> - </div> - </div> - ); - } - -} - -ConfirmationModal.propTypes = { - message: PropTypes.node.isRequired, - confirm: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ConfirmationModal); diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx deleted file mode 100644 index 02a577500..000000000 --- a/app/assets/javascripts/components/features/ui/components/media_modal.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import LoadingIndicator from '../../../components/loading_indicator'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ExtendedVideoPlayer from '../../../components/extended_video_player'; -import ImageLoader from 'react-imageloader'; -import { defineMessages, injectIntl } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' } -}); - -class MediaModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - index: null - }; - this.handleNextClick = this.handleNextClick.bind(this); - this.handlePrevClick = this.handlePrevClick.bind(this); - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleNextClick () { - this.setState({ index: (this.getIndex() + 1) % this.props.media.size}); - } - - handlePrevClick () { - this.setState({ index: (this.getIndex() - 1) % this.props.media.size}); - } - - handleKeyUp (e) { - switch(e.key) { - case 'ArrowLeft': - this.handlePrevClick(); - break; - case 'ArrowRight': - this.handleNextClick(); - break; - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - getIndex () { - return this.state.index !== null ? this.state.index : this.props.index; - } - - render () { - const { media, intl, onClose } = this.props; - - const index = this.getIndex(); - const attachment = media.get(index); - const url = attachment.get('url'); - - let leftNav, rightNav, content; - - leftNav = rightNav = content = ''; - - if (media.size > 1) { - leftNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; - rightNav = <div role='button' tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; - } - - if (attachment.get('type') === 'image') { - content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />; - } else if (attachment.get('type') === 'gifv') { - content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />; - } - - return ( - <div className='modal-root__modal media-modal'> - {leftNav} - - <div className='media-modal__content'> - <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> - {content} - </div> - - {rightNav} - </div> - ); - } - -} - -MediaModal.propTypes = { - media: ImmutablePropTypes.list.isRequired, - index: PropTypes.number.isRequired, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(MediaModal); diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx deleted file mode 100644 index 23057715c..000000000 --- a/app/assets/javascripts/components/features/ui/components/modal_root.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import PropTypes from 'prop-types'; -import MediaModal from './media_modal'; -import OnboardingModal from './onboarding_modal'; -import VideoModal from './video_modal'; -import BoostModal from './boost_modal'; -import ConfirmationModal from './confirmation_modal'; -import { TransitionMotion, spring } from 'react-motion'; - -const MODAL_COMPONENTS = { - 'MEDIA': MediaModal, - 'ONBOARDING': OnboardingModal, - 'VIDEO': VideoModal, - 'BOOST': BoostModal, - 'CONFIRM': ConfirmationModal -}; - -class ModalRoot extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleKeyUp (e) { - if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) - && !!this.props.type) { - this.props.onClose(); - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - willEnter () { - return { opacity: 0, scale: 0.98 }; - } - - willLeave () { - return { opacity: spring(0), scale: spring(0.98) }; - } - - render () { - const { type, props, onClose } = this.props; - const items = []; - - if (!!type) { - items.push({ - key: type, - data: { type, props }, - style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) } - }); - } - - return ( - <TransitionMotion - styles={items} - willEnter={this.willEnter} - willLeave={this.willLeave}> - {interpolatedStyles => - <div className='modal-root'> - {interpolatedStyles.map(({ key, data: { type, props }, style }) => { - const SpecificComponent = MODAL_COMPONENTS[type]; - - return ( - <div key={key}> - <div role='presentation' className='modal-root__overlay' style={{ opacity: style.opacity }} onClick={onClose} /> - <div className='modal-root__container' style={{ opacity: style.opacity, transform: `translateZ(0px) scale(${style.scale})` }}> - <SpecificComponent {...props} onClose={onClose} /> - </div> - </div> - ); - })} - </div> - } - </TransitionMotion> - ); - } - -} - -ModalRoot.propTypes = { - type: PropTypes.string, - props: PropTypes.object, - onClose: PropTypes.func.isRequired -}; - -export default ModalRoot; diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx deleted file mode 100644 index 4c2c55f93..000000000 --- a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx +++ /dev/null @@ -1,263 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Permalink from '../../../components/permalink'; -import { TransitionMotion, spring } from 'react-motion'; -import ComposeForm from '../../compose/components/compose_form'; -import Search from '../../compose/components/search'; -import NavigationBar from '../../compose/components/navigation_bar'; -import ColumnHeader from './column_header'; -import Immutable from 'immutable'; - -const messages = defineMessages({ - home_title: { id: 'column.home', defaultMessage: 'Home' }, - notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' }, - local_title: { id: 'column.community', defaultMessage: 'Local timeline' }, - federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' } -}); - -const PageOne = ({ acct, domain }) => ( - <div className='onboarding-modal__page onboarding-modal__page-one'> - <div style={{ flex: '0 0 auto' }}> - <div className='onboarding-modal__page-one__elephant-friend' /> - </div> - - <div> - <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1> - <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> - <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> - </div> - </div> -); - -PageOne.propTypes = { - acct: PropTypes.string.isRequired, - domain: PropTypes.string.isRequired -}; - -const PageTwo = ({ me }) => ( - <div className='onboarding-modal__page onboarding-modal__page-two'> - <div className='figure non-interactive'> - <div className='pseudo-drawer'> - <NavigationBar account={me} /> - </div> - <ComposeForm - text='Awoo! #introductions' - suggestions={Immutable.List()} - mentionedDomains={[]} - spoiler={false} - onChange={() => {}} - onSubmit={() => {}} - onPaste={() => {}} - onPickEmoji={() => {}} - onChangeSpoilerText={() => {}} - onClearSuggestions={() => {}} - onFetchSuggestions={() => {}} - onSuggestionSelected={() => {}} - /> - </div> - - <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> - </div> -); - -PageTwo.propTypes = { - me: ImmutablePropTypes.map.isRequired, -}; - -const PageThree = ({ me, domain }) => ( - <div className='onboarding-modal__page onboarding-modal__page-three'> - <div className='figure non-interactive'> - <Search - value='' - onChange={() => {}} - onSubmit={() => {}} - onClear={() => {}} - onShow={() => {}} - /> - - <div className='pseudo-drawer'> - <NavigationBar account={me} /> - </div> - </div> - - <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> - <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> - </div> -); - -PageThree.propTypes = { - me: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired -}; - -const PageFour = ({ domain, intl }) => ( - <div className='onboarding-modal__page onboarding-modal__page-four'> - <div className='onboarding-modal__page-four__columns'> - <div className='row'> - <div> - <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p> - </div> - - <div> - <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div> - <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p> - </div> - </div> - - <div className='row'> - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div> - </div> - - <div> - <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div> - </div> - </div> - - <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> - </div> - </div> -); - -PageFour.propTypes = { - domain: PropTypes.string.isRequired, - intl: PropTypes.object.isRequired -}; - -const PageSix = ({ admin, domain }) => { - let adminSection = ''; - - if (admin) { - adminSection = ( - <p> - <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> }} /> - <br /> - <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> }}/> - </p> - ); - } - - return ( - <div className='onboarding-modal__page onboarding-modal__page-six'> - <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1> - {adminSection} - <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> - <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> - <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p> - </div> - ); -}; - -PageSix.propTypes = { - admin: ImmutablePropTypes.map, - domain: PropTypes.string.isRequired -}; - -const mapStateToProps = state => ({ - me: state.getIn(['accounts', state.getIn(['meta', 'me'])]), - admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]), - domain: state.getIn(['meta', 'domain']) -}); - -class OnboardingModal extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - currentIndex: 0 - }; - this.handleSkip = this.handleSkip.bind(this); - this.handleDot = this.handleDot.bind(this); - this.handleNext = this.handleNext.bind(this); - } - - handleSkip (e) { - e.preventDefault(); - this.props.onClose(); - } - - handleDot (i, e) { - e.preventDefault(); - this.setState({ currentIndex: i }); - } - - handleNext (maxNum, e) { - e.preventDefault(); - - if (this.state.currentIndex < maxNum - 1) { - this.setState({ currentIndex: this.state.currentIndex + 1 }); - } else { - this.props.onClose(); - } - } - - render () { - const { me, admin, domain, intl } = this.props; - - const pages = [ - <PageOne acct={me.get('acct')} domain={domain} />, - <PageTwo me={me} />, - <PageThree me={me} domain={domain} />, - <PageFour domain={domain} intl={intl} />, - <PageSix admin={admin} domain={domain} /> - ]; - - const { currentIndex } = this.state; - const hasMore = currentIndex < pages.length - 1; - - let nextOrDoneBtn; - - if(hasMore) { - nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>; - } else { - nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>; - } - - const styles = pages.map((page, i) => ({ - key: `page-${i}`, - style: { opacity: spring(i === currentIndex ? 1 : 0) } - })); - - return ( - <div className='modal-root__modal onboarding-modal'> - <TransitionMotion styles={styles}> - {interpolatedStyles => - <div className='onboarding-modal__pager'> - {pages.map((page, i) => - <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div> - )} - </div> - } - </TransitionMotion> - - <div className='onboarding-modal__paginator'> - <div> - <a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a> - </div> - - <div className='onboarding-modal__dots'> - {pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)} - </div> - - <div> - {nextOrDoneBtn} - </div> - </div> - </div> - ); - } - -} - -OnboardingModal.propTypes = { - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired, - domain: PropTypes.string.isRequired, - admin: ImmutablePropTypes.map -} - -export default connect(mapStateToProps)(injectIntl(OnboardingModal)); diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx deleted file mode 100644 index 316b4bf7d..000000000 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Link } from 'react-router'; -import { FormattedMessage } from 'react-intl'; - -class TabsBar extends React.Component { - - render () { - return ( - <div className='tabs-bar'> - <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link> - <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link> - <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link> - - <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link> - <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link> - - <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link> - </div> - ); - } - -} - -export default TabsBar; diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx deleted file mode 100644 index 3a933398b..000000000 --- a/app/assets/javascripts/components/features/ui/components/upload_area.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import PropTypes from 'prop-types'; -import { Motion, spring } from 'react-motion'; -import { FormattedMessage } from 'react-intl'; - -class UploadArea extends React.PureComponent { - - constructor (props, context) { - super(props, context); - - this.handleKeyUp = this.handleKeyUp.bind(this); - } - - handleKeyUp (e) { - e.preventDefault(); - e.stopPropagation(); - - const keyCode = e.keyCode - if (this.props.active) { - switch(keyCode) { - case 27: - this.props.onClose(); - break; - } - } - } - - componentDidMount () { - window.addEventListener('keyup', this.handleKeyUp, false); - } - - componentWillUnmount () { - window.removeEventListener('keyup', this.handleKeyUp); - } - - render () { - const { active } = this.props; - - return ( - <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}> - {({ backgroundOpacity, backgroundScale }) => - <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}> - <div className='upload-area__drop'> - <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} /> - <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div> - </div> - </div> - } - </Motion> - ); - } - -} - -UploadArea.propTypes = { - active: PropTypes.bool, - onClose: PropTypes.func -}; - -export default UploadArea; diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx deleted file mode 100644 index d98b42882..000000000 --- a/app/assets/javascripts/components/features/ui/components/video_modal.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import LoadingIndicator from '../../../components/loading_indicator'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ExtendedVideoPlayer from '../../../components/extended_video_player'; -import { defineMessages, injectIntl } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' } -}); - -class VideoModal extends React.PureComponent { - - render () { - const { media, intl, time, onClose } = this.props; - - const url = media.get('url'); - - return ( - <div className='modal-root__modal media-modal'> - <div> - <div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div> - <ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} /> - </div> - </div> - ); - } - -} - -VideoModal.propTypes = { - media: ImmutablePropTypes.map.isRequired, - time: PropTypes.number, - onClose: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(VideoModal); diff --git a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx b/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx deleted file mode 100644 index 6c4e73e38..000000000 --- a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import LoadingBar from 'react-redux-loading-bar'; - -const mapStateToProps = (state) => ({ - loading: state.get('loadingBar') -}); - -export default connect(mapStateToProps)(LoadingBar.WrappedComponent); diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx deleted file mode 100644 index 26d77818c..000000000 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import { closeModal } from '../../../actions/modal'; -import ModalRoot from '../components/modal_root'; - -const mapStateToProps = state => ({ - type: state.get('modal').modalType, - props: state.get('modal').modalProps -}); - -const mapDispatchToProps = dispatch => ({ - onClose () { - dispatch(closeModal()); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ModalRoot); diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx deleted file mode 100644 index 529ebf6c8..000000000 --- a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { NotificationStack } from 'react-notification'; -import { - dismissAlert, - clearAlerts -} from '../../../actions/alerts'; -import { getAlerts } from '../../../selectors'; - -const mapStateToProps = (state, props) => ({ - notifications: getAlerts(state) -}); - -const mapDispatchToProps = (dispatch) => { - return { - onDismiss: alert => { - dispatch(dismissAlert(alert)); - } - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack); diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx deleted file mode 100644 index 1599000b5..000000000 --- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { connect } from 'react-redux'; -import StatusList from '../../../components/status_list'; -import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines'; -import Immutable from 'immutable'; -import { createSelector } from 'reselect'; -import { debounce } from 'react-decoration'; - -const makeGetStatusIds = () => createSelector([ - (state, { type }) => state.getIn(['settings', type], Immutable.Map()), - (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()), - (state) => state.get('statuses'), - (state) => state.getIn(['meta', 'me']) -], (columnSettings, statusIds, statuses, me) => statusIds.filter(id => { - const statusForId = statuses.get(id); - let showStatus = true; - - if (columnSettings.getIn(['shows', 'reblog']) === false) { - showStatus = showStatus && statusForId.get('reblog') === null; - } - - if (columnSettings.getIn(['shows', 'reply']) === false) { - showStatus = showStatus && (statusForId.get('in_reply_to_id') === null || statusForId.get('in_reply_to_account_id') === me); - } - - if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) { - try { - if (showStatus) { - const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i'); - showStatus = !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'unescaped_content']) : statusForId.get('unescaped_content')); - } - } catch(e) { - // Bad regex, don't affect filters - } - } - - return showStatus; -})); - -const makeMapStateToProps = () => { - const getStatusIds = makeGetStatusIds(); - - const mapStateToProps = (state, props) => ({ - scrollKey: props.scrollKey, - shouldUpdateScroll: props.shouldUpdateScroll, - statusIds: getStatusIds(state, props), - isLoading: state.getIn(['timelines', props.type, 'isLoading'], true), - isUnread: state.getIn(['timelines', props.type, 'unread']) > 0, - hasMore: !!state.getIn(['timelines', props.type, 'next']) - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { type, id }) => ({ - - @debounce(300, true) - onScrollToBottom () { - dispatch(scrollTopTimeline(type, false)); - dispatch(expandTimeline(type, id)); - }, - - @debounce(100) - onScrollToTop () { - dispatch(scrollTopTimeline(type, true)); - }, - - @debounce(100) - onScroll () { - dispatch(scrollTopTimeline(type, false)); - } - -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx deleted file mode 100644 index b402639ce..000000000 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ /dev/null @@ -1,166 +0,0 @@ -import ColumnsArea from './components/columns_area'; -import NotificationsContainer from './containers/notifications_container'; -import PropTypes from 'prop-types'; -import LoadingBarContainer from './containers/loading_bar_container'; -import HomeTimeline from '../home_timeline'; -import Compose from '../compose'; -import TabsBar from './components/tabs_bar'; -import ModalContainer from './containers/modal_container'; -import Notifications from '../notifications'; -import { connect } from 'react-redux'; -import { isMobile } from '../../is_mobile'; -import { debounce } from 'react-decoration'; -import { uploadCompose } from '../../actions/compose'; -import { refreshTimeline } from '../../actions/timelines'; -import { refreshNotifications } from '../../actions/notifications'; -import UploadArea from './components/upload_area'; - -class UI extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - width: window.innerWidth, - draggingOver: false - }; - this.handleResize = this.handleResize.bind(this); - this.handleDragEnter = this.handleDragEnter.bind(this); - this.handleDragOver = this.handleDragOver.bind(this); - this.handleDrop = this.handleDrop.bind(this); - this.handleDragLeave = this.handleDragLeave.bind(this); - this.handleDragEnd = this.handleDragLeave.bind(this) - this.closeUploadModal = this.closeUploadModal.bind(this) - this.setRef = this.setRef.bind(this); - } - - @debounce(500) - handleResize () { - this.setState({ width: window.innerWidth }); - } - - handleDragEnter (e) { - e.preventDefault(); - - if (!this.dragTargets) { - this.dragTargets = []; - } - - if (this.dragTargets.indexOf(e.target) === -1) { - this.dragTargets.push(e.target); - } - - if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { - this.setState({ draggingOver: true }); - } - } - - handleDragOver (e) { - e.preventDefault(); - e.stopPropagation(); - - try { - e.dataTransfer.dropEffect = 'copy'; - } catch (err) { - - } - - return false; - } - - handleDrop (e) { - e.preventDefault(); - - this.setState({ draggingOver: false }); - - if (e.dataTransfer && e.dataTransfer.files.length === 1) { - this.props.dispatch(uploadCompose(e.dataTransfer.files)); - } - } - - handleDragLeave (e) { - e.preventDefault(); - e.stopPropagation(); - - this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el)); - - if (this.dragTargets.length > 0) { - return; - } - - this.setState({ draggingOver: false }); - } - - closeUploadModal() { - this.setState({ draggingOver: false }); - } - - componentWillMount () { - window.addEventListener('resize', this.handleResize, { passive: true }); - document.addEventListener('dragenter', this.handleDragEnter, false); - document.addEventListener('dragover', this.handleDragOver, false); - document.addEventListener('drop', this.handleDrop, false); - document.addEventListener('dragleave', this.handleDragLeave, false); - document.addEventListener('dragend', this.handleDragEnd, false); - - this.props.dispatch(refreshTimeline('home')); - this.props.dispatch(refreshNotifications()); - } - - componentWillUnmount () { - window.removeEventListener('resize', this.handleResize); - document.removeEventListener('dragenter', this.handleDragEnter); - document.removeEventListener('dragover', this.handleDragOver); - document.removeEventListener('drop', this.handleDrop); - document.removeEventListener('dragleave', this.handleDragLeave); - document.removeEventListener('dragend', this.handleDragEnd); - } - - setRef (c) { - this.node = c; - } - - render () { - const { width, draggingOver } = this.state; - const { children } = this.props; - - let mountedColumns; - - if (isMobile(width)) { - mountedColumns = ( - <ColumnsArea> - {children} - </ColumnsArea> - ); - } else { - mountedColumns = ( - <ColumnsArea> - <Compose withHeader={true} /> - <HomeTimeline shouldUpdateScroll={() => false} /> - <Notifications shouldUpdateScroll={() => false} /> - <div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div> - </ColumnsArea> - ); - } - - return ( - <div className='ui' ref={this.setRef}> - <TabsBar /> - - {mountedColumns} - - <NotificationsContainer /> - <LoadingBarContainer className="loading-bar" /> - <ModalContainer /> - <UploadArea active={draggingOver} onClose={this.closeUploadModal} /> - </div> - ); - } - -} - -UI.propTypes = { - dispatch: PropTypes.func.isRequired, - children: PropTypes.node -}; - -export default connect()(UI); |