mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-21 21:57:19 +00:00
Add support for notices in WebUI
This commit is contained in:
parent
c6710769ca
commit
3f361c2bf2
36
app/javascript/mastodon/actions/notices.ts
Normal file
36
app/javascript/mastodon/actions/notices.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
export interface ApiNoticeActionJSON {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ApiNoticeJSON {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
icon?: string;
|
||||
actions: ApiNoticeActionJSON[];
|
||||
}
|
||||
|
||||
export const fetchNotices = createAppAsyncThunk(
|
||||
'notices/fetch',
|
||||
async (_, { getState }) => {
|
||||
const response = await api(getState).get<ApiNoticeJSON[]>(
|
||||
'/api/v1/notices',
|
||||
);
|
||||
|
||||
return { notices: response.data };
|
||||
},
|
||||
);
|
||||
|
||||
export const dismissNotice = createAppAsyncThunk(
|
||||
'notices/dismiss',
|
||||
async (args: { id: string }, { getState }) => {
|
||||
await api(getState).delete<unknown>(`/api/v1/notices/${args.id}`);
|
||||
|
||||
return {};
|
||||
},
|
||||
);
|
|
@ -0,0 +1,61 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import background from 'mastodon/../images/friends-cropped.png';
|
||||
import type { ApiNoticeJSON } from 'mastodon/actions/notices';
|
||||
import { dismissNotice } from 'mastodon/actions/notices';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
|
||||
});
|
||||
|
||||
interface Props {
|
||||
notice: ApiNoticeJSON;
|
||||
}
|
||||
|
||||
export const Notice: React.FC<Props> = ({ notice }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
void dispatch(dismissNotice({ id: notice.id }));
|
||||
}, [dispatch, notice.id]);
|
||||
|
||||
return (
|
||||
<div className='dismissable-banner'>
|
||||
<div className='dismissable-banner__message'>
|
||||
<img
|
||||
src={notice.icon ?? background}
|
||||
alt=''
|
||||
className='dismissable-banner__background-image'
|
||||
/>
|
||||
|
||||
<h1>{notice.title}</h1>
|
||||
|
||||
<p>{notice.message}</p>
|
||||
|
||||
<div className='dismissable-banner__message__wrapper'>
|
||||
<div className='dismissable-banner__message__actions'>
|
||||
{notice.actions.map((action, i) => (
|
||||
<a key={`action-${i}`} href={action.url} className='button'>
|
||||
{action.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='dismissable-banner__action'>
|
||||
<IconButton
|
||||
icon='times'
|
||||
title={intl.formatMessage(messages.dismiss)}
|
||||
onClick={handleDismiss}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -25,6 +25,7 @@ import StatusListContainer from '../ui/containers/status_list_container';
|
|||
import { ColumnSettings } from './components/column_settings';
|
||||
import { CriticalUpdateBanner } from './components/critical_update_banner';
|
||||
import { ExplorePrompt } from './components/explore_prompt';
|
||||
import { Notice } from './components/notice';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||
|
@ -66,6 +67,7 @@ const mapStateToProps = state => ({
|
|||
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')),
|
||||
showAnnouncements: state.getIn(['announcements', 'show']),
|
||||
tooSlow: homeTooSlow(state),
|
||||
notices: state.get('notices'),
|
||||
});
|
||||
|
||||
class HomeTimeline extends PureComponent {
|
||||
|
@ -85,6 +87,7 @@ class HomeTimeline extends PureComponent {
|
|||
unreadAnnouncements: PropTypes.number,
|
||||
showAnnouncements: PropTypes.bool,
|
||||
tooSlow: PropTypes.bool,
|
||||
notices: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
|
@ -154,7 +157,7 @@ class HomeTimeline extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
||||
const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements, notices } = this.props;
|
||||
const pinned = !!columnId;
|
||||
const { signedIn } = this.context.identity;
|
||||
const banners = [];
|
||||
|
@ -179,7 +182,9 @@ class HomeTimeline extends PureComponent {
|
|||
banners.push(<CriticalUpdateBanner key='critical-update-banner' />);
|
||||
}
|
||||
|
||||
if (tooSlow) {
|
||||
if (notices.length) {
|
||||
banners.push(<Notice key='notice' notice={notices[0]} />);
|
||||
} else if (tooSlow) {
|
||||
banners.push(<ExplorePrompt key='explore-prompt' />);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { HotKeys } from 'react-hotkeys';
|
|||
|
||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
import { fetchNotices } from 'mastodon/actions/notices';
|
||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
import PictureInPicture from 'mastodon/features/picture_in_picture';
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
|
@ -401,6 +402,7 @@ class UI extends PureComponent {
|
|||
}
|
||||
|
||||
if (signedIn) {
|
||||
this.props.dispatch(fetchNotices());
|
||||
this.props.dispatch(fetchMarkers());
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
this.props.dispatch(expandNotifications());
|
||||
|
|
|
@ -28,6 +28,7 @@ import media_attachments from './media_attachments';
|
|||
import meta from './meta';
|
||||
import { modalReducer } from './modal';
|
||||
import mutes from './mutes';
|
||||
import { noticesReducer } from './notices';
|
||||
import notifications from './notifications';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
import polls from './polls';
|
||||
|
@ -86,6 +87,7 @@ const reducers = {
|
|||
history,
|
||||
tags,
|
||||
followed_tags,
|
||||
notices: noticesReducer,
|
||||
};
|
||||
|
||||
// We want the root state to be an ImmutableRecord, which is an object with a defined list of keys,
|
||||
|
|
15
app/javascript/mastodon/reducers/notices.ts
Normal file
15
app/javascript/mastodon/reducers/notices.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiNoticeJSON } from '../actions/notices';
|
||||
import { fetchNotices, dismissNotice } from '../actions/notices';
|
||||
|
||||
const initialState: ApiNoticeJSON[] = [];
|
||||
|
||||
export const noticesReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(
|
||||
fetchNotices.fulfilled,
|
||||
(_state, { payload: { notices } }) => notices,
|
||||
)
|
||||
.addCase(dismissNotice.fulfilled, () => []);
|
||||
});
|
Loading…
Reference in a new issue