diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-06-04 01:39:38 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-04 01:39:38 +0200 |
commit | 8ee2eb5d2e7bd3c601c0277f12d8ad0c5f84cc43 (patch) | |
tree | 2c51c5c5cd47273cf1b66d553e1cc5c7f762a0f8 /app/javascript/mastodon/components | |
parent | 20b647020bf8de2af6d2ce44ed76566d137dd1f6 (diff) |
Allow mounting arbitrary columns (#3207)
* Allow mounting arbitrary columns * Refactor column headers, allow pinning/unpinning and moving columns around * Collapse animation * Re-introduce scroll to top * Save column settings properly, do not display pin options in single-column view, do not display collapse icon if there is nothing to collapse * Fix one instance of public timeline being closed closing the stream Fix back buttons inconsistently sending you back to / even if history exists * Getting started displays links to columns that are not mounted
Diffstat (limited to 'app/javascript/mastodon/components')
4 files changed, 186 insertions, 2 deletions
diff --git a/app/javascript/mastodon/components/column.js b/app/javascript/mastodon/components/column.js new file mode 100644 index 000000000..157a89c0e --- /dev/null +++ b/app/javascript/mastodon/components/column.js @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import scrollTop from '../scroll'; + +class Column extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + }; + + scrollTop () { + const scrollable = this.node.querySelector('.scrollable'); + + if (!scrollable) { + return; + } + + this._interruptScrollAnimation = scrollTop(scrollable); + } + + handleWheel = () => { + if (typeof this._interruptScrollAnimation !== 'function') { + return; + } + + this._interruptScrollAnimation(); + } + + setRef = c => { + this.node = c; + } + + render () { + const { children } = this.props; + + return ( + <div role='region' className='column' ref={this.setRef} onWheel={this.handleWheel}> + {children} + </div> + ); + } + +} + +export default Column; diff --git a/app/javascript/mastodon/components/column_back_button.js b/app/javascript/mastodon/components/column_back_button.js index 9d2de40f5..6c61ca6d4 100644 --- a/app/javascript/mastodon/components/column_back_button.js +++ b/app/javascript/mastodon/components/column_back_button.js @@ -9,7 +9,7 @@ class ColumnBackButton extends React.PureComponent { }; handleClick = () => { - if (window.history && window.history.length === 1) this.context.router.push("/"); + if (window.history && window.history.length === 1) this.context.router.push('/'); else this.context.router.goBack(); } diff --git a/app/javascript/mastodon/components/column_back_button_slim.js b/app/javascript/mastodon/components/column_back_button_slim.js index 6f6bbc0b8..2d3f1b57a 100644 --- a/app/javascript/mastodon/components/column_back_button_slim.js +++ b/app/javascript/mastodon/components/column_back_button_slim.js @@ -9,7 +9,8 @@ class ColumnBackButtonSlim extends React.PureComponent { }; handleClick = () => { - this.context.router.push('/'); + if (window.history && window.history.length === 1) this.context.router.push('/'); + else this.context.router.goBack(); } render () { diff --git a/app/javascript/mastodon/components/column_header.js b/app/javascript/mastodon/components/column_header.js new file mode 100644 index 000000000..e6349a399 --- /dev/null +++ b/app/javascript/mastodon/components/column_header.js @@ -0,0 +1,138 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { FormattedMessage } from 'react-intl'; + +class ColumnHeader extends React.PureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + title: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + active: PropTypes.bool, + multiColumn: PropTypes.bool, + children: PropTypes.node, + pinned: PropTypes.bool, + onPin: PropTypes.func, + onMove: PropTypes.func, + onClick: PropTypes.func, + }; + + state = { + collapsed: true, + animating: false, + }; + + handleToggleClick = (e) => { + e.stopPropagation(); + this.setState({ collapsed: !this.state.collapsed, animating: true }); + } + + handleTitleClick = () => { + this.props.onClick(); + } + + handleMoveLeft = () => { + this.props.onMove(-1); + } + + handleMoveRight = () => { + this.props.onMove(1); + } + + handleBackClick = () => { + if (window.history && window.history.length === 1) this.context.router.push('/'); + else this.context.router.goBack(); + } + + handleTransitionEnd = () => { + this.setState({ animating: false }); + } + + render () { + const { title, icon, active, children, pinned, onPin, multiColumn } = this.props; + const { collapsed, animating } = this.state; + + const buttonClassName = classNames('column-header', { + 'active': active, + }); + + const collapsibleClassName = classNames('column-header__collapsible', { + 'collapsed': collapsed, + 'animating': animating, + }); + + const collapsibleButtonClassName = classNames('column-header__button', { + 'active': !collapsed, + }); + + let extraContent, pinButton, moveButtons, backButton, collapseButton; + + if (children) { + extraContent = ( + <div key='extra-content' className='column-header__collapsible__extra'> + {children} + </div> + ); + } + + if (multiColumn && pinned) { + pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>; + + moveButtons = ( + <div key='move-buttons' className='column-header__setting-arrows'> + <button className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button> + <button className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button> + </div> + ); + } else if (multiColumn) { + pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={onPin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; + + backButton = ( + <button onClick={this.handleBackClick} className='column-header__back-button'> + <i className='fa fa-fw fa-chevron-left column-back-button__icon' /> + <FormattedMessage id='column_back_button.label' defaultMessage='Back' /> + </button> + ); + } + + const collapsedContent = [ + extraContent, + ]; + + if (multiColumn) { + collapsedContent.push(moveButtons); + collapsedContent.push(pinButton); + } + + if (children || multiColumn) { + collapseButton = <button className={collapsibleButtonClassName} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>; + } + + return ( + <div> + <div role='button heading' tabIndex='0' className={buttonClassName} onClick={this.handleTitleClick}> + <i className={`fa fa-fw fa-${icon} column-header__icon`} /> + {title} + + <div className='column-header__buttons'> + {backButton} + {collapseButton} + </div> + </div> + + <div className={collapsibleClassName} onTransitionEnd={this.handleTransitionEnd}> + <div> + {(!collapsed || animating) && collapsedContent} + </div> + </div> + </div> + ); + } + +} + +export default ColumnHeader; |