diff --git a/app/javascript/flavours/glitch/features/firehose/index.jsx b/app/javascript/flavours/glitch/features/firehose/index.jsx new file mode 100644 index 0000000000..19bb6d791e --- /dev/null +++ b/app/javascript/flavours/glitch/features/firehose/index.jsx @@ -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 'flavours/glitch/actions/columns'; +import { changeSetting } from 'flavours/glitch/actions/settings'; +import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming'; +import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; +import initialState, { domain } from 'flavours/glitch/initial_state'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/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 ( +
+
+ } + /> +
+
+ ); +}; + +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' ? ( + + + + ) : ( + + + + ); + + const emptyMessage = feedType === 'community' ? ( + + ) : ( + + ); + + return ( + + + + + +
+
+ + + + + + + + + + + +
+ + +
+ + + {intl.formatMessage(messages.title)} + + +
+ ); +} + +Firehose.propTypes = { + multiColumn: PropTypes.bool, + feedType: PropTypes.string, +}; + +export default Firehose; diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx index 56b6477016..683a2d79d9 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx @@ -18,8 +18,7 @@ const messages = defineMessages({ home: { id: 'tabs_bar.home', defaultMessage: 'Home' }, notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, explore: { id: 'explore.title', defaultMessage: 'Explore' }, - local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' }, - federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' }, + firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' }, direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, @@ -43,6 +42,10 @@ class NavigationPanel extends Component { onOpenSettings: PropTypes.func, }; + isFirehoseActive = (match, location) => { + return match || location.pathname.startsWith('/public'); + }; + render() { const { intl, onOpenSettings } = this.props; const { signedIn, disabledAccountId } = this.context.identity; @@ -64,10 +67,7 @@ class NavigationPanel extends Component { )} {(signedIn || timelinePreview) && ( - <> - - - + )} {!signedIn && ( diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index a6a7489e45..5a14e396cc 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -37,8 +37,7 @@ import { Status, GettingStarted, KeyboardShortcuts, - PublicTimeline, - CommunityTimeline, + Firehose, AccountTimeline, AccountGallery, HomeTimeline, @@ -196,8 +195,11 @@ class SwitchingColumnsArea extends PureComponent { - - + + + + + diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index 0e632bc816..24e8a42a68 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -22,6 +22,10 @@ export function CommunityTimeline () { return import(/* webpackChunkName: "flavours/glitch/async/community_timeline" */'flavours/glitch/features/community_timeline'); } +export function Firehose () { + return import(/* webpackChunkName: "flavours/glitch/async/firehose" */'../../firehose'); +} + export function HashtagTimeline () { return import(/* webpackChunkName: "flavours/glitch/async/hashtag_timeline" */'flavours/glitch/features/hashtag_timeline'); } diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 103702d8d7..0f038670ed 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -84,6 +84,10 @@ const initialState = ImmutableMap({ }), }), + firehose: ImmutableMap({ + onlyMedia: false, + }), + community: ImmutableMap({ regex: ImmutableMap({ body: '',