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: '',