move event source to top level

this way, activities are always logged while the app is open, and the
activity log survives local navigations
This commit is contained in:
Sebastian Jambor 2023-01-15 19:58:48 +01:00
parent 4c6949ec46
commit 26f3b35cc7
5 changed files with 105 additions and 68 deletions

View file

@ -0,0 +1,11 @@
export const ACTIVITY_LOG_RESET ='ACTIVITY_LOG_RESET';
export const ACTIVITY_LOG_ADD = 'ACTIVITY_LOG_ADD';
export const resetActivityLog = () => ({
type: ACTIVITY_LOG_RESET,
});
export const addActivityLog = value => ({
type: ACTIVITY_LOG_ADD,
value,
});

View file

@ -13,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { getLocale } from 'mastodon/locales';
import { addActivityLog } from 'mastodon/actions/activity_log';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
@ -61,6 +62,14 @@ export default class Mastodon extends React.PureComponent {
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
this.eventSource = new EventSource('/api/v1/activity_log');
this.eventSource.onmessage = (event) => {
const parsed = JSON.parse(event.data);
if (parsed.type !== 'keep-alive') {
store.dispatch(addActivityLog(parsed));
}
};
}
}
@ -68,6 +77,7 @@ export default class Mastodon extends React.PureComponent {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
this.eventSource.close();
}
}

View file

@ -1,4 +1,6 @@
import React, { useEffect, useReducer, useRef } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import Column from 'mastodon/components/column';
@ -8,79 +10,74 @@ import DismissableBanner from 'mastodon/components/dismissable_banner';
import ActivityPubVisualization from 'activitypub-visualization';
export default function ActivityLog({ multiColumn }) {
const [logs, dispatch] = useReducer((state, [type, data]) => {
switch (type) {
case 'add-log-event':
return [...state, data];
case 'reset-logs':
return [];
default:
return state;
}
}, []);
const mapStateToProps = (state) => {
return {
logs: state.getIn(['activity_log', 'logs']),
};
};
const columnElement = useRef(null);
export default @connect(mapStateToProps)
class ActivityLog extends ImmutablePureComponent {
useEffect(() => {
const eventSource = new EventSource('/api/v1/activity_log');
eventSource.onmessage = (event) => {
const parsed = JSON.parse(event.data);
if (parsed.type !== 'keep-alive') {
dispatch(['add-log-event', parsed]);
}
};
return function() {
eventSource.close();
};
}, []);
const darkMode = !(document.body && document.body.classList.contains('theme-mastodon-light'));
// hijack the toggleHidden shortcut to copy the logs to clipbaord
const handlers = {
toggleHidden: () => navigator.clipboard.writeText(JSON.stringify(logs, null, 2)),
static propTypes = {
multiColumn: PropTypes.bool,
};
return (
<Column bindToDocument={!multiColumn} ref={columnElement} label='Activity Log'>
<ColumnHeader
icon='comments'
title='Activity Log'
onClick={() => { columnElement.current.scrollTop() }}
multiColumn={multiColumn}
/>
handleHeaderClick = () => {
this.column.scrollTop();
}
<DismissableBanner id='activity_log'>
<p>
<FormattedMessage
id='dismissable_banner.activity_log_information'
defaultMessage='Open Mastodon in another tab and interact with another instance (for example, follow an account on another instance). The resulting Activities will be shown here. You can find more information on my {blog}.'
values={{
blog: <a href='//seb.jambor.dev/' style={{ color: darkMode ? '#8c8dff' : '#3a3bff', textDecoration: 'none' }}>blog</a>,
}}
/>
</p>
<p style={{ paddingTop: '5px' }}>
<FormattedMessage
id='dismissable_banner.activity_log_clear'
defaultMessage='Note: Activities will only be logged while this view is open. When you navigate elsewhere, the log will be cleared.'
/>
</p>
</DismissableBanner>
setRef = c => {
this.column = c;
}
<HotKeys handlers={handlers}>
<div className={`${darkMode ? 'dark' : ''}`}>
<ActivityPubVisualization logs={logs} />
</div>
</HotKeys>
render() {
const { logs, multiColumn } = this.props;
const darkMode = !(document.body && document.body.classList.contains('theme-mastodon-light'));
// hijack the toggleHidden shortcut to copy the logs to clipbaord
const handlers = {
toggleHidden: () => navigator.clipboard.writeText(JSON.stringify(logs, null, 2)),
};
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label='Activity Log'>
<ColumnHeader
icon='comments'
title='Activity Log'
onClick={this.handleHeaderClick}
multiColumn={multiColumn}
/>
<DismissableBanner id='activity_log'>
<p>
<FormattedMessage
id='dismissable_banner.activity_log_information'
defaultMessage='When you interact with another instance (for example, follow an account on another instance), the resulting Activities will be shown here. You can find more information on my {blog}.'
values={{
blog: <a href='//seb.jambor.dev/' style={{ color: darkMode ? '#8c8dff' : '#3a3bff', textDecoration: 'none' }}>blog</a>,
}}
/>
</p>
<p style={{ paddingTop: '5px' }}>
<FormattedMessage
id='dismissable_banner.activity_log_clear'
defaultMessage='Note: Activities will only be logged while Mastodon is open. When you navigate elsewhere or reload the page, the log will be cleared.'
/>
</p>
</DismissableBanner>
<HotKeys handlers={handlers}>
<div className={`${darkMode ? 'dark' : ''}`}>
<ActivityPubVisualization logs={logs} />
</div>
</HotKeys>
</Column>
);
}
</Column>
);
}
ActivityLog.propTypes = {
multiColumn: PropTypes.bool,
};

View file

@ -0,0 +1,17 @@
import { Map as ImmutableMap } from 'immutable';
import { ACTIVITY_LOG_ADD, ACTIVITY_LOG_RESET } from '../actions/activity_log';
const initialState = ImmutableMap({
logs: [],
});
export default function activity_log(state = initialState, action) {
switch (action.type) {
case ACTIVITY_LOG_ADD:
return state.set('logs', [...state.get('logs'), action.value]);
case ACTIVITY_LOG_RESET:
return state.set('logs', []);
default:
return state;
}
}

View file

@ -9,6 +9,7 @@ import user_lists from './user_lists';
import domain_lists from './domain_lists';
import accounts from './accounts';
import accounts_counters from './accounts_counters';
import activity_log from './activity_log';
import statuses from './statuses';
import relationships from './relationships';
import settings from './settings';
@ -44,6 +45,7 @@ import followed_tags from './followed_tags';
const reducers = {
announcements,
activity_log,
dropdown_menu,
timelines,
meta,