forked from glitch-soc/mastodon
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change local and federated timelines to be in a single firehose column (
- Loading branch information
1 parent
0139b1c
commit cea9db5
Showing
6 changed files
with
234 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
import PropTypes from 'prop-types'; | ||
import { useRef, useCallback, useEffect } from 'react'; | ||
|
||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; | ||
|
||
import { Helmet } from 'react-helmet'; | ||
import { NavLink } from 'react-router-dom'; | ||
|
||
import { addColumn } from 'mastodon/actions/columns'; | ||
import { changeSetting } from 'mastodon/actions/settings'; | ||
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; | ||
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; | ||
import DismissableBanner from 'mastodon/components/dismissable_banner'; | ||
import initialState, { domain } from 'mastodon/initial_state'; | ||
import { useAppDispatch, useAppSelector } from 'mastodon/store'; | ||
|
||
import Column from '../../components/column'; | ||
import ColumnHeader from '../../components/column_header'; | ||
import SettingToggle from '../notifications/components/setting_toggle'; | ||
import StatusListContainer from '../ui/containers/status_list_container'; | ||
|
||
const messages = defineMessages({ | ||
title: { id: 'column.firehose', defaultMessage: 'Live feeds' }, | ||
}); | ||
|
||
// TODO: use a proper React context later on | ||
const useIdentity = () => ({ | ||
signedIn: !!initialState.meta.me, | ||
accountId: initialState.meta.me, | ||
disabledAccountId: initialState.meta.disabled_account_id, | ||
accessToken: initialState.meta.access_token, | ||
permissions: initialState.role ? initialState.role.permissions : 0, | ||
}); | ||
|
||
const ColumnSettings = () => { | ||
const dispatch = useAppDispatch(); | ||
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose'])); | ||
const onChange = useCallback( | ||
(key, checked) => dispatch(changeSetting(['firehose', ...key], checked)), | ||
[dispatch], | ||
); | ||
|
||
return ( | ||
<div> | ||
<div className='column-settings__row'> | ||
<SettingToggle | ||
settings={settings} | ||
settingPath={['onlyMedia']} | ||
onChange={onChange} | ||
label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
const Firehose = ({ feedType, multiColumn }) => { | ||
const dispatch = useAppDispatch(); | ||
const intl = useIntl(); | ||
const { signedIn } = useIdentity(); | ||
const columnRef = useRef(null); | ||
|
||
const onlyMedia = useAppSelector((state) => state.getIn(['settings', 'firehose', 'onlyMedia'], false)); | ||
const hasUnread = useAppSelector((state) => state.getIn(['timelines', `${feedType}${onlyMedia ? ':media' : ''}`, 'unread'], 0) > 0); | ||
|
||
const handlePin = useCallback( | ||
() => { | ||
switch(feedType) { | ||
case 'community': | ||
dispatch(addColumn('COMMUNITY', { other: { onlyMedia } })); | ||
break; | ||
case 'public': | ||
dispatch(addColumn('PUBLIC', { other: { onlyMedia } })); | ||
break; | ||
case 'public:remote': | ||
dispatch(addColumn('REMOTE', { other: { onlyMedia, onlyRemote: true } })); | ||
break; | ||
} | ||
}, | ||
[dispatch, onlyMedia, feedType], | ||
); | ||
|
||
const handleLoadMore = useCallback( | ||
(maxId) => { | ||
switch(feedType) { | ||
case 'community': | ||
dispatch(expandCommunityTimeline({ onlyMedia })); | ||
break; | ||
case 'public': | ||
dispatch(expandPublicTimeline({ maxId, onlyMedia })); | ||
break; | ||
case 'public:remote': | ||
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote: true })); | ||
break; | ||
} | ||
}, | ||
[dispatch, onlyMedia, feedType], | ||
); | ||
|
||
const handleHeaderClick = useCallback(() => columnRef.current?.scrollTop(), []); | ||
|
||
useEffect(() => { | ||
let disconnect; | ||
|
||
switch(feedType) { | ||
case 'community': | ||
dispatch(expandCommunityTimeline({ onlyMedia })); | ||
if (signedIn) { | ||
disconnect = dispatch(connectCommunityStream({ onlyMedia })); | ||
} | ||
break; | ||
case 'public': | ||
dispatch(expandPublicTimeline({ onlyMedia })); | ||
if (signedIn) { | ||
disconnect = dispatch(connectPublicStream({ onlyMedia })); | ||
} | ||
break; | ||
case 'public:remote': | ||
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote: true })); | ||
if (signedIn) { | ||
disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote: true })); | ||
} | ||
break; | ||
} | ||
|
||
return () => disconnect?.(); | ||
}, [dispatch, signedIn, feedType, onlyMedia]); | ||
|
||
const prependBanner = feedType === 'community' ? ( | ||
<DismissableBanner id='community_timeline'> | ||
<FormattedMessage | ||
id='dismissable_banner.community_timeline' | ||
defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' | ||
values={{ domain }} | ||
/> | ||
</DismissableBanner> | ||
) : ( | ||
<DismissableBanner id='public_timeline'> | ||
<FormattedMessage | ||
id='dismissable_banner.public_timeline' | ||
defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' | ||
/> | ||
</DismissableBanner> | ||
); | ||
|
||
const emptyMessage = feedType === 'community' ? ( | ||
<FormattedMessage | ||
id='empty_column.community' | ||
defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' | ||
/> | ||
) : ( | ||
<FormattedMessage | ||
id='empty_column.public' | ||
defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' | ||
/> | ||
); | ||
|
||
return ( | ||
<Column bindToDocument={!multiColumn} ref={columnRef} label={intl.formatMessage(messages.title)}> | ||
<ColumnHeader | ||
icon='globe' | ||
active={hasUnread} | ||
title={intl.formatMessage(messages.title)} | ||
onPin={handlePin} | ||
onClick={handleHeaderClick} | ||
multiColumn={multiColumn} | ||
> | ||
<ColumnSettings /> | ||
</ColumnHeader> | ||
|
||
<div className='scrollable scrollable--flex'> | ||
<div className='account__section-headline'> | ||
<NavLink exact to='/public/local'> | ||
<FormattedMessage tagName='div' id='firehose.local' defaultMessage='Local' /> | ||
</NavLink> | ||
|
||
<NavLink exact to='/public/remote'> | ||
<FormattedMessage tagName='div' id='firehose.remote' defaultMessage='Remote' /> | ||
</NavLink> | ||
|
||
<NavLink exact to='/public'> | ||
<FormattedMessage tagName='div' id='firehose.all' defaultMessage='All' /> | ||
</NavLink> | ||
</div> | ||
|
||
<StatusListContainer | ||
prepend={prependBanner} | ||
timelineId={`${feedType}${onlyMedia ? ':media' : ''}`} | ||
onLoadMore={handleLoadMore} | ||
trackScroll | ||
scrollKey='firehose' | ||
emptyMessage={emptyMessage} | ||
bindToDocument={!multiColumn} | ||
/> | ||
</div> | ||
|
||
<Helmet> | ||
<title>{intl.formatMessage(messages.title)}</title> | ||
<meta name='robots' content='noindex' /> | ||
</Helmet> | ||
</Column> | ||
); | ||
} | ||
|
||
Firehose.propTypes = { | ||
multiColumn: PropTypes.bool, | ||
feedType: PropTypes.string, | ||
}; | ||
|
||
export default Firehose; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters