diff options
author | Reverite <github@reverite.sh> | 2019-06-13 23:05:19 -0700 |
---|---|---|
committer | Reverite <github@reverite.sh> | 2019-06-13 23:05:19 -0700 |
commit | 7ce2a4e95331cc9ef9b782a5c4d8046d8a835a05 (patch) | |
tree | bc4e5e39ee96ae74cbf9c09570b2e545da6587e0 /app/javascript/flavours/glitch/features/ui/components | |
parent | 3614718bc91f90a6dc19dd80ecf3bc191283c24e (diff) | |
parent | c0e5f32d13dfd696728dc1fa2ad9a93a27aa405f (diff) |
Merge branch 'glitch' into production
Diffstat (limited to 'app/javascript/flavours/glitch/features/ui/components')
9 files changed, 252 insertions, 45 deletions
diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index ce7ec2479..600e4422f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -7,6 +7,7 @@ import StatusContent from 'flavours/glitch/components/status_content'; import Avatar from 'flavours/glitch/components/avatar'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import DisplayName from 'flavours/glitch/components/display_name'; +import AttachmentList from 'flavours/glitch/components/attachment_list'; import ImmutablePureComponent from 'react-immutable-pure-component'; const messages = defineMessages({ @@ -25,6 +26,7 @@ export default class BoostModal extends ImmutablePureComponent { status: ImmutablePropTypes.map.isRequired, onReblog: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, + missingMediaDescription: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -52,7 +54,7 @@ export default class BoostModal extends ImmutablePureComponent { } render () { - const { status, intl } = this.props; + const { status, missingMediaDescription, intl } = this.props; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; return ( @@ -74,11 +76,24 @@ export default class BoostModal extends ImmutablePureComponent { </div> <StatusContent status={status} /> + + {status.get('media_attachments').size > 0 && ( + <AttachmentList + compact + media={status.get('media_attachments')} + /> + )} </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> + <div> + { missingMediaDescription ? + <FormattedMessage id='boost_modal.missing_description' defaultMessage='This toot contains some media without description' /> + : + <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(buttonText)} onClick={this.handleReblog} ref={this.setRef} /> </div> </div> diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 0fe580b9b..3a188ca87 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ReactSwipeableViews from 'react-swipeable-views'; -import { links, getIndex, getLink } from './tabs_bar'; +import TabsBar, { links, getIndex, getLink } from './tabs_bar'; import { Link } from 'react-router-dom'; import BundleContainer from '../containers/bundle_container'; @@ -13,6 +13,8 @@ import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; import BundleColumnError from './bundle_column_error'; import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components'; +import ComposePanel from './compose_panel'; +import NavigationPanel from './navigation_panel'; import detectPassiveEvents from 'detect-passive-events'; import { scrollRight } from 'flavours/glitch/util/scroll'; @@ -49,6 +51,8 @@ export default class ColumnsArea extends ImmutablePureComponent { swipeToChangeColumns: PropTypes.bool, singleColumn: PropTypes.bool, children: PropTypes.node, + navbarUnder: PropTypes.bool, + openSettings: PropTypes.func, }; state = { @@ -139,7 +143,7 @@ export default class ColumnsArea extends ImmutablePureComponent { <ColumnLoading title={title} icon={icon} />; return ( - <div className='columns-area' key={index}> + <div className='columns-area columns-area--mobile' key={index}> {view} </div> ); @@ -154,7 +158,7 @@ export default class ColumnsArea extends ImmutablePureComponent { } render () { - const { columns, children, singleColumn, swipeToChangeColumns, intl } = this.props; + const { columns, children, singleColumn, swipeToChangeColumns, intl, navbarUnder, openSettings } = this.props; const { shouldAnimate } = this.state; const columnIndex = getIndex(this.context.router.history.location.pathname); @@ -163,17 +167,37 @@ export default class ColumnsArea extends ImmutablePureComponent { if (singleColumn) { const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><i className='fa fa-pencil' /></Link>; - return columnIndex !== -1 ? [ + const content = columnIndex !== -1 ? ( <ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={!swipeToChangeColumns}> {links.map(this.renderView)} - </ReactSwipeableViews>, - - floatingActionButton, - ] : [ - <div className='columns-area'>{children}</div>, - - floatingActionButton, - ]; + </ReactSwipeableViews> + ) : ( + <div key='content' className='columns-area columns-area--mobile'>{children}</div> + ); + + return ( + <div className='columns-area__panels'> + <div className='columns-area__panels__pane columns-area__panels__pane--compositional'> + <div className='columns-area__panels__pane__inner'> + <ComposePanel /> + </div> + </div> + + <div className='columns-area__panels__main'> + {!navbarUnder && <TabsBar key='tabs' />} + {content} + {navbarUnder && <TabsBar key='tabs' />} + </div> + + <div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'> + <div className='columns-area__panels__pane__inner'> + <NavigationPanel onOpenSettings={openSettings} /> + </div> + </div> + + {floatingActionButton} + </div> + ); } return ( diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js new file mode 100644 index 000000000..f5eefee0d --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; +import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; +import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; +import LinkFooter from './link_footer'; + +const ComposePanel = () => ( + <div className='compose-panel'> + <SearchContainer openInRoute /> + <NavigationContainer /> + <ComposeFormContainer /> + <LinkFooter withHotkeys /> + </div> +); + +export default ComposePanel; diff --git a/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js b/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js new file mode 100644 index 000000000..189f403bd --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; +import { connect } from 'react-redux'; +import { NavLink, withRouter } from 'react-router-dom'; +import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; +import { me } from 'flavours/glitch/util/initial_state'; +import { List as ImmutableList } from 'immutable'; +import { FormattedMessage } from 'react-intl'; + +const mapStateToProps = state => ({ + locked: state.getIn(['accounts', me, 'locked']), + count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, +}); + +export default @withRouter +@connect(mapStateToProps) +class FollowRequestsNavLink extends React.Component { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + locked: PropTypes.bool, + count: PropTypes.number.isRequired, + }; + + componentDidMount () { + const { dispatch, locked } = this.props; + + if (locked) { + dispatch(fetchFollowRequests()); + } + } + + render () { + const { locked, count } = this.props; + + if (!locked || count === 0) { + return null; + } + + return <NavLink className='column-link column-link--transparent' to='/follow_requests'><IconWithBadge className='column-link__icon' id='user-plus' count={count} /><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></NavLink>; + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js new file mode 100644 index 000000000..3e724fffb --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; +import { invitesEnabled, version, repository, source_url } from 'flavours/glitch/util/initial_state'; +import { signOutLink } from 'flavours/glitch/util/backend_links'; + +const LinkFooter = () => ( + <div className='getting-started__footer'> + <ul> + {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>} + <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li> + <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li> + <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li> + <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li> + <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li> + <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li> + <li><a href={signOutLink} data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li> + </ul> + + <p> + <FormattedMessage + id='getting_started.open_source_notice' + defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.' + values={{ + github: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a> (v{version})</span>, + Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }} + /> + </p> + </div> +); + +LinkFooter.propTypes = { +}; + +export default LinkFooter; diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js new file mode 100644 index 000000000..b2e6925b7 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { NavLink, withRouter } from 'react-router-dom'; +import Icon from 'flavours/glitch/components/icon'; + +const getOrderedLists = createSelector([state => state.get('lists')], lists => { + if (!lists) { + return lists; + } + + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4); +}); + +const mapStateToProps = state => ({ + lists: getOrderedLists(state), +}); + +export default @withRouter +@connect(mapStateToProps) +class ListPanel extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + lists: ImmutablePropTypes.list, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchLists()); + } + + render () { + const { lists } = this.props; + + if (!lists || lists.isEmpty()) { + return null; + } + + return ( + <div> + <hr /> + + {lists.map(list => ( + <NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/timelines/list/${list.get('id')}`}><Icon className='column-link__icon' icon='list-ul' fixedWidth />{list.get('title')}</NavLink> + ))} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js new file mode 100644 index 000000000..4688c7766 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { NavLink, withRouter } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; +import Icon from 'flavours/glitch/components/icon'; +import { profile_directory } from 'flavours/glitch/util/initial_state'; +import NotificationsCounterIcon from './notifications_counter_icon'; +import FollowRequestsNavLink from './follow_requests_nav_link'; +import ListPanel from './list_panel'; + +const NavigationPanel = ({ onOpenSettings }) => ( + <div className='navigation-panel'> + <NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' icon='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> + <FollowRequestsNavLink /> + <NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' icon='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> + <NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' icon='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' icon='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' icon='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' icon='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink> + + <ListPanel /> + + <hr /> + + <a className='column-link column-link--transparent' href='/settings/preferences' target='_blank'><Icon className='column-link__icon' icon='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a> + <a className='column-link column-link--transparent' href='#' onClick={onOpenSettings}><Icon className='column-link__icon' icon='cogs' fixedWidth /><FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' /></a> + <a className='column-link column-link--transparent' href='/relationships' target='_blank'><Icon className='column-link__icon' icon='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a> + {!!profile_directory && <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>} + </div> +); + +export default withRouter(NavigationPanel); diff --git a/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js new file mode 100644 index 000000000..6b52ef9b4 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/notifications_counter_icon.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; + +const mapStateToProps = state => ({ + count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0, + id: 'bell', +}); + +export default connect(mapStateToProps)(IconWithBadge); diff --git a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js index b44a21a42..dbd08aa2b 100644 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js @@ -4,40 +4,16 @@ import { NavLink, withRouter } from 'react-router-dom'; import { FormattedMessage, injectIntl } from 'react-intl'; import { debounce } from 'lodash'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -import { connect } from 'react-redux'; - -const mapStateToProps = state => ({ - unreadNotifications: state.getIn(['notifications', 'unread']), - showBadge: state.getIn(['local_settings', 'notifications', 'tab_badge']), -}); - -@connect(mapStateToProps) -class NotificationsIcon extends React.PureComponent { - static propTypes = { - unreadNotifications: PropTypes.number, - showBadge: PropTypes.bool, - }; - - render() { - const { unreadNotifications, showBadge } = this.props; - return ( - <span className='icon-badge-wrapper'> - <i className='fa fa-fw fa-bell' /> - { showBadge && unreadNotifications > 0 && <div className='icon-badge' />} - </span> - ); - } -} +import NotificationsCounterIcon from './notifications_counter_icon'; export const links = [ - <NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, - <NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, - - <NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>, - <NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>, - <NavLink className='tabs-bar__link primary' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><i className='fa fa-fw fa-search' /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>, + <NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, + <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, - <NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>, + <NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>, + <NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>, + <NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><i className='fa fa-fw fa-search' /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>, + <NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>, ]; export function getIndex (path) { |