Make profile header scroll along with contents. AccountTimeline, Followers and Following are no longer

nested inside a common parent (<Account>), instead they all embed <HeaderContainer />
This commit is contained in:
Eugen Rochko 2017-01-30 21:40:55 +01:00
parent a2a85e8549
commit f21e7d6ac0
14 changed files with 230 additions and 182 deletions

View file

@ -1,109 +0,0 @@
import { connect } from 'react-redux';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import {
fetchAccount,
followAccount,
unfollowAccount,
blockAccount,
unblockAccount,
fetchAccountTimeline,
expandAccountTimeline
} from '../../actions/accounts';
import { mentionCompose } from '../../actions/compose';
import Header from './components/header';
import {
getAccountTimeline,
makeGetAccount
} from '../../selectors';
import LoadingIndicator from '../../components/loading_indicator';
import ActionBar from './components/action_bar';
import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button';
import { isMobile } from '../../is_mobile'
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, props) => ({
account: getAccount(state, Number(props.params.accountId)),
me: state.getIn(['meta', 'me'])
});
return mapStateToProps;
};
const Account = React.createClass({
contextTypes: {
router: React.PropTypes.object
},
propTypes: {
params: React.PropTypes.object.isRequired,
dispatch: React.PropTypes.func.isRequired,
account: ImmutablePropTypes.map,
me: React.PropTypes.number.isRequired,
children: React.PropTypes.node
},
mixins: [PureRenderMixin],
componentWillMount () {
this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
},
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
}
},
handleFollow () {
if (this.props.account.getIn(['relationship', 'following'])) {
this.props.dispatch(unfollowAccount(this.props.account.get('id')));
} else {
this.props.dispatch(followAccount(this.props.account.get('id')));
}
},
handleBlock () {
if (this.props.account.getIn(['relationship', 'blocking'])) {
this.props.dispatch(unblockAccount(this.props.account.get('id')));
} else {
this.props.dispatch(blockAccount(this.props.account.get('id')));
}
},
handleMention () {
this.props.dispatch(mentionCompose(this.props.account));
if (isMobile(window.innerWidth)) {
this.context.router.push('/statuses/new');
}
},
render () {
const { account, me } = this.props;
if (account === null) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}
return (
<Column>
<ColumnBackButton />
<Header account={account} me={me} onFollow={this.handleFollow} />
<ActionBar account={account} me={me} onBlock={this.handleBlock} onMention={this.handleMention} />
{this.props.children}
</Column>
);
}
});
export default connect(makeMapStateToProps)(Account);

View file

@ -0,0 +1,59 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
import InnerHeader from '../../account/components/header';
import ActionBar from '../../account/components/action_bar';
const Header = React.createClass({
contextTypes: {
router: React.PropTypes.object
},
propTypes: {
account: ImmutablePropTypes.map.isRequired,
me: React.PropTypes.number.isRequired,
onFollow: React.PropTypes.func.isRequired,
onBlock: React.PropTypes.func.isRequired,
onMention: React.PropTypes.func.isRequired
},
mixins: [PureRenderMixin],
handleFollow () {
this.props.onFollow(this.props.account);
},
handleBlock () {
this.props.onBlock(this.props.account);
},
handleMention () {
this.props.onMention(this.props.account, this.context.router);
},
render () {
const { account, me } = this.props;
if (!account) {
return null;
}
return (
<div>
<InnerHeader
account={account}
me={me}
onFollow={this.handleFollow}
/>
<ActionBar
account={account}
me={me}
onBlock={this.handleBlock}
onMention={this.handleMention}
/>
</div>
);
}
});
export default Header;

View file

@ -0,0 +1,45 @@
import { connect } from 'react-redux';
import { makeGetAccount } from '../../../selectors';
import Header from '../components/header';
import {
followAccount,
unfollowAccount,
blockAccount,
unblockAccount
} from '../../../actions/accounts';
import { mentionCompose } from '../../../actions/compose';
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, { accountId }) => ({
account: getAccount(state, Number(accountId)),
me: state.getIn(['meta', 'me'])
});
return mapStateToProps;
};
const mapDispatchToProps = dispatch => ({
onFollow (account) {
if (account.getIn(['relationship', 'following'])) {
dispatch(unfollowAccount(account.get('id')));
} else {
dispatch(followAccount(account.get('id')));
}
},
onBlock (account) {
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(blockAccount(account.get('id')));
}
},
onMention (account, router) {
dispatch(mentionCompose(account, router));
}
});
export default connect(makeMapStateToProps, mapDispatchToProps)(Header);

View file

@ -7,6 +7,9 @@ import {
} from '../../actions/accounts';
import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import HeaderContainer from './containers/header_container';
import ColumnBackButton from '../../components/column_back_button';
const mapStateToProps = (state, props) => ({
statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items']),
@ -44,10 +47,26 @@ const AccountTimeline = React.createClass({
const { statusIds, isLoading, me } = this.props;
if (!statusIds) {
return <LoadingIndicator />;
return (
<Column>
<LoadingIndicator />
</Column>
);
}
return <StatusList statusIds={statusIds} isLoading={isLoading} me={me} onScrollToBottom={this.handleScrollToBottom} />
return (
<Column>
<ColumnBackButton />
<StatusList
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
statusIds={statusIds}
isLoading={isLoading}
me={me}
onScrollToBottom={this.handleScrollToBottom}
/>
</Column>
);
}
});

View file

@ -8,6 +8,10 @@ import {
} from '../../actions/accounts';
import { ScrollContainer } from 'react-router-scroll';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container';
import LoadMore from '../../components/load_more';
import ColumnBackButton from '../../components/column_back_button';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items'])
@ -41,21 +45,35 @@ const Followers = React.createClass({
}
},
handleLoadMore (e) {
e.preventDefault();
this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
},
render () {
const { accountIds } = this.props;
if (!accountIds) {
return <LoadingIndicator />;
return (
<Column>
<LoadingIndicator />
</Column>
);
}
return (
<ScrollContainer scrollKey='followers'>
<div className='scrollable' onScroll={this.handleScroll}>
<div>
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='followers'>
<div className='scrollable' onScroll={this.handleScroll}>
<div>
<HeaderContainer accountId={this.props.params.accountId} />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
<LoadMore onClick={this.handleLoadMore} />
</div>
</div>
</div>
</ScrollContainer>
</ScrollContainer>
</Column>
);
}

View file

@ -8,6 +8,10 @@ import {
} from '../../actions/accounts';
import { ScrollContainer } from 'react-router-scroll';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import HeaderContainer from '../account_timeline/containers/header_container';
import LoadMore from '../../components/load_more';
import ColumnBackButton from '../../components/column_back_button';
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items'])
@ -41,21 +45,35 @@ const Following = React.createClass({
}
},
handleLoadMore (e) {
e.preventDefault();
this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
},
render () {
const { accountIds } = this.props;
if (!accountIds) {
return <LoadingIndicator />;
return (
<Column>
<LoadingIndicator />
</Column>
);
}
return (
<ScrollContainer scrollKey='following'>
<div className='scrollable' onScroll={this.handleScroll}>
<div>
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
<Column>
<ColumnBackButton />
<ScrollContainer scrollKey='following'>
<div className='scrollable' onScroll={this.handleScroll}>
<div>
<HeaderContainer accountId={this.props.params.accountId} />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
<LoadMore onClick={this.handleLoadMore} />
</div>
</div>
</div>
</ScrollContainer>
</ScrollContainer>
</Column>
);
}

View file

@ -14,6 +14,10 @@ const messages = defineMessages({
const ActionBar = React.createClass({
contextTypes: {
router: React.PropTypes.object
},
propTypes: {
status: ImmutablePropTypes.map.isRequired,
onReply: React.PropTypes.func.isRequired,
@ -43,7 +47,7 @@ const ActionBar = React.createClass({
},
handleMentionClick () {
this.props.onMention(this.props.status.get('account'));
this.props.onMention(this.props.status.get('account'), this.context.router);
},
render () {

View file

@ -80,12 +80,8 @@ const Status = React.createClass({
this.props.dispatch(deleteStatus(status.get('id')));
},
handleMentionClick (account) {
this.props.dispatch(mentionCompose(account));
if (isMobile(window.innerWidth)) {
this.context.router.push('/statuses/new');
}
handleMentionClick (account, router) {
this.props.dispatch(mentionCompose(account, router));
},
handleOpenMedia (url) {