mirror of
https://github.com/mastodon/mastodon.git
synced 2025-02-11 09:25:08 +00:00
Add streaming updates for profiles in web UI
This commit is contained in:
parent
a9643cb7e7
commit
51ddab84cc
|
@ -192,3 +192,32 @@ export const connectDirectStream = () =>
|
||||||
*/
|
*/
|
||||||
export const connectListStream = listId =>
|
export const connectListStream = listId =>
|
||||||
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
|
connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} accountId
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {boolean} options.withReplies
|
||||||
|
* @param {string} options.tagged
|
||||||
|
* @param {boolean} options.onlyMedia
|
||||||
|
* @returns {function(): void}
|
||||||
|
*/
|
||||||
|
export const connectProfileStream = (accountId, { withReplies, tagged, onlyMedia }) =>
|
||||||
|
connectTimelineStream(`account:${accountId}${onlyMedia ? ':media' : ''}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, 'profile', { account_id: accountId }, {
|
||||||
|
accept (status) {
|
||||||
|
let passThrough = true;
|
||||||
|
|
||||||
|
if (!withReplies) {
|
||||||
|
passThrough = passThrough && (status.in_reply_to_id === null || status.in_reply_to_account_id === status.account.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagged) {
|
||||||
|
passThrough = passThrough && status.tags.some(tag => tag.name === tagged);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyMedia) {
|
||||||
|
passThrough = passThrough && status.media_attachments.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passThrough;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
import { connectProfileStream } from 'mastodon/actions/streaming';
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||||
import { LoadMore } from 'mastodon/components/load_more';
|
import { LoadMore } from 'mastodon/components/load_more';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
@ -90,7 +91,15 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
const { accountId, isAccount, dispatch } = this.props;
|
const { accountId, isAccount, dispatch } = this.props;
|
||||||
|
|
||||||
if (!isAccount) dispatch(fetchAccount(accountId));
|
if (!isAccount) dispatch(fetchAccount(accountId));
|
||||||
|
|
||||||
dispatch(expandAccountMediaTimeline(accountId));
|
dispatch(expandAccountMediaTimeline(accountId));
|
||||||
|
|
||||||
|
if (this.disconnect) {
|
||||||
|
this.disconnect();
|
||||||
|
this.disconnect = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disconnect = dispatch(connectProfileStream(accountId, { onlyMedia: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -103,6 +112,13 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.disconnect) {
|
||||||
|
this.disconnect();
|
||||||
|
this.disconnect = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
const { params: { acct }, accountId, dispatch } = this.props;
|
const { params: { acct }, accountId, dispatch } = this.props;
|
||||||
|
|
||||||
|
|
|
@ -7,21 +7,20 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||||
|
import { fetchFeaturedTags } from 'mastodon/actions/featured_tags';
|
||||||
|
import { connectProfileStream } from 'mastodon/actions/streaming';
|
||||||
|
import { expandAccountFeaturedTimeline, expandAccountTimeline } from 'mastodon/actions/timelines';
|
||||||
|
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||||
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
|
import StatusList from 'mastodon/components/status_list';
|
||||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import { me } from 'mastodon/initial_state';
|
import Column from 'mastodon/features/ui/components/column';
|
||||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||||
import { getAccountHidden } from 'mastodon/selectors';
|
import { getAccountHidden } from 'mastodon/selectors';
|
||||||
import { useAppSelector } from 'mastodon/store';
|
import { useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
|
||||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
|
||||||
import { ColumnBackButton } from '../../components/column_back_button';
|
|
||||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
|
||||||
import StatusList from '../../components/status_list';
|
|
||||||
import Column from '../ui/components/column';
|
|
||||||
|
|
||||||
import { LimitedAccountHint } from './components/limited_account_hint';
|
import { LimitedAccountHint } from './components/limited_account_hint';
|
||||||
import HeaderContainer from './containers/header_container';
|
import HeaderContainer from './containers/header_container';
|
||||||
|
|
||||||
|
@ -114,9 +113,12 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
dispatch(fetchFeaturedTags(accountId));
|
dispatch(fetchFeaturedTags(accountId));
|
||||||
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
||||||
|
|
||||||
if (accountId === me) {
|
if (this.disconnect) {
|
||||||
dispatch(connectTimeline(`account:${me}`));
|
this.disconnect();
|
||||||
|
this.disconnect = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.disconnect = dispatch(connectProfileStream(accountId, { withReplies, tagged }));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -142,17 +144,12 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
dispatch(expandAccountTimeline(accountId, { withReplies, tagged }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.accountId === me && accountId !== me) {
|
|
||||||
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
const { dispatch, accountId } = this.props;
|
if (this.disconnect) {
|
||||||
|
this.disconnect();
|
||||||
if (accountId === me) {
|
this.disconnect = null;
|
||||||
dispatch(disconnectTimeline({ timeline: `account:${me}` }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,14 +93,16 @@ export const makeGetReport = () => createSelector([
|
||||||
|
|
||||||
export const getAccountGallery = createSelector([
|
export const getAccountGallery = createSelector([
|
||||||
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
(state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()),
|
||||||
state => state.get('statuses'),
|
state => state.get('statuses'),
|
||||||
(state, id) => state.getIn(['accounts', id]),
|
], (statusIds, statuses) => {
|
||||||
], (statusIds, statuses, account) => {
|
|
||||||
let medias = ImmutableList();
|
let medias = ImmutableList();
|
||||||
|
|
||||||
statusIds.forEach(statusId => {
|
statusIds.forEach(statusId => {
|
||||||
const status = statuses.get(statusId).set('account', account);
|
const status = statuses.get(statusId);
|
||||||
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
|
|
||||||
|
if (status) {
|
||||||
|
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return medias;
|
return medias;
|
||||||
|
|
|
@ -18,6 +18,7 @@ class FanOutOnWriteService < BaseService
|
||||||
warm_payload_cache!
|
warm_payload_cache!
|
||||||
|
|
||||||
fan_out_to_local_recipients!
|
fan_out_to_local_recipients!
|
||||||
|
fan_out_to_profile_streams! if distributable?
|
||||||
fan_out_to_public_recipients! if broadcastable?
|
fan_out_to_public_recipients! if broadcastable?
|
||||||
fan_out_to_public_streams! if broadcastable?
|
fan_out_to_public_streams! if broadcastable?
|
||||||
end
|
end
|
||||||
|
@ -145,6 +146,10 @@ class FanOutOnWriteService < BaseService
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fan_out_to_profile_streams!
|
||||||
|
redis.publish("timeline:profile:#{@status.account_id}:public", anonymous_payload)
|
||||||
|
end
|
||||||
|
|
||||||
def deliver_to_conversation!
|
def deliver_to_conversation!
|
||||||
AccountConversation.add_status(@account, @status) unless update?
|
AccountConversation.add_status(@account, @status) unless update?
|
||||||
end
|
end
|
||||||
|
@ -168,6 +173,10 @@ class FanOutOnWriteService < BaseService
|
||||||
@options[:update]
|
@options[:update]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def distributable?
|
||||||
|
@status.distributable?
|
||||||
|
end
|
||||||
|
|
||||||
def broadcastable?
|
def broadcastable?
|
||||||
@status.public_visibility? && !@status.reblog? && !@account.silenced?
|
@status.public_visibility? && !@status.reblog? && !@account.silenced?
|
||||||
end
|
end
|
||||||
|
|
|
@ -87,6 +87,7 @@ const PUBLIC_CHANNELS = [
|
||||||
'public:remote:media',
|
'public:remote:media',
|
||||||
'hashtag',
|
'hashtag',
|
||||||
'hashtag:local',
|
'hashtag:local',
|
||||||
|
'profile',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Used for priming the counters/gauges for the various metrics that are
|
// Used for priming the counters/gauges for the various metrics that are
|
||||||
|
@ -420,6 +421,8 @@ const startServer = async () => {
|
||||||
return 'direct';
|
return 'direct';
|
||||||
case '/api/v1/streaming/list':
|
case '/api/v1/streaming/list':
|
||||||
return 'list';
|
return 'list';
|
||||||
|
case '/api/v1/streaming/profile':
|
||||||
|
return 'profile';
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -972,6 +975,7 @@ const startServer = async () => {
|
||||||
* @property {string} [tag]
|
* @property {string} [tag]
|
||||||
* @property {string} [list]
|
* @property {string} [list]
|
||||||
* @property {string} [only_media]
|
* @property {string} [only_media]
|
||||||
|
* @property {string} [account_id]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1096,6 +1100,17 @@ const startServer = async () => {
|
||||||
reject(new AuthenticationError('Not authorized to stream this list'));
|
reject(new AuthenticationError('Not authorized to stream this list'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'profile':
|
||||||
|
if (!params.account_id) {
|
||||||
|
reject(new RequestError('Missing account id parameter'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
channelIds: [`timeline:profile:${params.account_id}:public`],
|
||||||
|
options: { needsFiltering: true },
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
reject(new RequestError('Unknown stream type'));
|
reject(new RequestError('Unknown stream type'));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user