[Glitch] Add media timeline

Port 7403e5d306 to glitch-soc
This commit is contained in:
Thibaut Girka 2018-05-27 20:55:11 +02:00 committed by ThibG
parent 74ee5bdf37
commit 1fb4bf87f7
6 changed files with 69 additions and 33 deletions

View file

@ -44,10 +44,9 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done)))); dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
}; };
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification); export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
export const connectCommunityStream = () => connectTimelineStream('community', 'public:local'); export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = () => connectTimelineStream('public', 'public'); export const connectHashtagStream = tag => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
export const connectListStream = (id) => connectTimelineStream(`list:${id}`, `list&list=${id}`);

View file

@ -69,15 +69,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
}; };
}; };
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done); export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandPublicTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }, done); export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
export const expandCommunityTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done);
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export function expandTimelineRequest(timeline, isLoadingMore) { export function expandTimelineRequest(timeline, isLoadingMore) {
return { return {

View file

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from 'flavours/glitch/actions/streaming'; import { connectCommunityStream } from 'flavours/glitch/actions/streaming';
@ -14,20 +15,25 @@ const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' }, title: { id: 'column.community', defaultMessage: 'Local timeline' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { onlyMedia }) => ({
hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0, hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
}); });
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl @injectIntl
export default class CommunityTimeline extends React.PureComponent { export default class CommunityTimeline extends React.PureComponent {
static defaultProps = {
onlyMedia: false,
};
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
onlyMedia: PropTypes.bool,
}; };
handlePin = () => { handlePin = () => {
@ -50,10 +56,10 @@ export default class CommunityTimeline extends React.PureComponent {
} }
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, onlyMedia } = this.props;
dispatch(expandCommunityTimeline()); dispatch(expandCommunityTimeline({ onlyMedia }));
this.disconnect = dispatch(connectCommunityStream()); this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
} }
componentWillUnmount () { componentWillUnmount () {
@ -68,13 +74,22 @@ export default class CommunityTimeline extends React.PureComponent {
} }
handleLoadMore = maxId => { handleLoadMore = maxId => {
this.props.dispatch(expandCommunityTimeline({ maxId })); const { dispatch, onlyMedia } = this.props;
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
} }
render () { render () {
const { intl, hasUnread, columnId, multiColumn } = this.props; const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const headline = (
<div className='community-timeline__section-headline'>
<NavLink exact to='/timelines/public/local' replace><FormattedMessage id='timeline.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to='/timelines/public/local/media' replace><FormattedMessage id='timeline.media' defaultMessage='Media' /></NavLink>
</div>
);
return ( return (
<Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}> <Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
@ -91,9 +106,11 @@ export default class CommunityTimeline extends React.PureComponent {
</ColumnHeader> </ColumnHeader>
<StatusListContainer <StatusListContainer
prepend={headline}
trackScroll={!pinned} trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`} scrollKey={`community_timeline-${columnId}`}
timelineId='community' shouldUpdateScroll={this.shouldUpdateScroll}
timelineId={`community${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
/> />

View file

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { expandPublicTimeline } from 'flavours/glitch/actions/timelines'; import { expandPublicTimeline } from 'flavours/glitch/actions/timelines';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from 'flavours/glitch/actions/streaming'; import { connectPublicStream } from 'flavours/glitch/actions/streaming';
@ -14,20 +15,25 @@ const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' }, title: { id: 'column.public', defaultMessage: 'Federated timeline' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { onlyMedia }) => ({
hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0, hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
}); });
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl @injectIntl
export default class PublicTimeline extends React.PureComponent { export default class PublicTimeline extends React.PureComponent {
static defaultProps = {
onlyMedia: false,
};
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool,
}; };
handlePin = () => { handlePin = () => {
@ -50,10 +56,10 @@ export default class PublicTimeline extends React.PureComponent {
} }
componentDidMount () { componentDidMount () {
const { dispatch } = this.props; const { dispatch, onlyMedia } = this.props;
dispatch(expandPublicTimeline()); dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream()); this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
} }
componentWillUnmount () { componentWillUnmount () {
@ -68,13 +74,22 @@ export default class PublicTimeline extends React.PureComponent {
} }
handleLoadMore = maxId => { handleLoadMore = maxId => {
this.props.dispatch(expandPublicTimeline({ maxId })); const { dispatch, onlyMedia } = this.props;
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
} }
render () { render () {
const { intl, columnId, hasUnread, multiColumn } = this.props; const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const headline = (
<div className='public-timeline__section-headline'>
<NavLink exact to='/timelines/public' replace><FormattedMessage id='timeline.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to='/timelines/public/media' replace><FormattedMessage id='timeline.media' defaultMessage='Media' /></NavLink>
</div>
);
return ( return (
<Column ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}> <Column ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}>
<ColumnHeader <ColumnHeader
@ -91,7 +106,8 @@ export default class PublicTimeline extends React.PureComponent {
</ColumnHeader> </ColumnHeader>
<StatusListContainer <StatusListContainer
timelineId='public' prepend={headline}
timelineId={`public${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
trackScroll={!pinned} trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`} scrollKey={`public_timeline-${columnId}`}

View file

@ -467,7 +467,9 @@ export default class UI extends React.Component {
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} /> <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} /> <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/media' extract component={PublicTimeline} content={children} componentParams={{ onlyMedia: true }} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} /> <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/public/local/media' component={CommunityTimeline} content={children} componentParams={{ onlyMedia: true }} />
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} /> <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} /> <WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />

View file

@ -446,6 +446,8 @@
} }
.notification__filter-bar, .notification__filter-bar,
.community-timeline__section-headline,
.public-timeline__section-headline,
.account__section-headline { .account__section-headline {
background: darken($ui-base-color, 4%); background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%); border-bottom: 1px solid lighten($ui-base-color, 8%);