1
0
Fork 0
forked from fedi/mastodon

Rewrite actions/modal and reducers/modal with typescript (#24833)

This commit is contained in:
fusagiko / takayamaki 2023-05-25 22:42:37 +09:00 committed by GitHub
parent 4197b5e4c8
commit 38c6216082
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 503 additions and 261 deletions

View file

@ -95,6 +95,6 @@ export function initBlockModal(account) {
account, account,
}); });
dispatch(openModal('BLOCK')); dispatch(openModal({ modalType: 'BLOCK' }));
}; };
} }

View file

@ -14,7 +14,10 @@ export function initBoostModal(props) {
privacy, privacy,
}); });
dispatch(openModal('BOOST', props)); dispatch(openModal({
modalType: 'BOOST',
modalProps: props,
}));
}; };
} }

View file

@ -381,7 +381,10 @@ export function initMediaEditModal(id) {
id, id,
}); });
dispatch(openModal('FOCAL_POINT', { id })); dispatch(openModal({
modalType: 'FOCAL_POINT',
modalProps: { id },
}));
}; };
} }

View file

@ -15,9 +15,12 @@ export const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS';
export const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL'; export const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL';
export const initAddFilter = (status, { contextType }) => dispatch => export const initAddFilter = (status, { contextType }) => dispatch =>
dispatch(openModal('FILTER', { dispatch(openModal({
statusId: status?.get('id'), modalType: 'FILTER',
contextType: contextType, modalProps: {
statusId: status?.get('id'),
contextType: contextType,
},
})); }));
export const fetchFilters = () => (dispatch, getState) => { export const fetchFilters = () => (dispatch, getState) => {

View file

@ -1,18 +0,0 @@
export const MODAL_OPEN = 'MODAL_OPEN';
export const MODAL_CLOSE = 'MODAL_CLOSE';
export function openModal(type, props) {
return {
type: MODAL_OPEN,
modalType: type,
modalProps: props,
};
}
export function closeModal(type, options = { ignoreFocus: false }) {
return {
type: MODAL_CLOSE,
modalType: type,
ignoreFocus: options.ignoreFocus,
};
}

View file

@ -0,0 +1,17 @@
import { createAction } from '@reduxjs/toolkit';
import type { MODAL_COMPONENTS } from '../features/ui/components/modal_root';
export type ModalType = keyof typeof MODAL_COMPONENTS;
interface OpenModalPayload {
modalType: ModalType;
modalProps: unknown;
}
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');
interface CloseModalPayload {
modalType: ModalType | undefined;
ignoreFocus: boolean;
}
export const closeModal = createAction<CloseModalPayload>('MODAL_CLOSE');

View file

@ -97,7 +97,7 @@ export function initMuteModal(account) {
account, account,
}); });
dispatch(openModal('MUTE')); dispatch(openModal({ modalType: 'MUTE' }));
}; };
} }

View file

@ -7,9 +7,12 @@ export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const initReport = (account, status) => dispatch => export const initReport = (account, status) => dispatch =>
dispatch(openModal('REPORT', { dispatch(openModal({
accountId: account.get('id'), modalType: 'REPORT',
statusId: status?.get('id'), modalProps: {
accountId: account.get('id'),
statusId: status?.get('id'),
},
})); }));
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {

View file

@ -15,7 +15,10 @@ import DropdownMenu from './containers/dropdown_menu_container';
const mapDispatchToProps = (dispatch, { statusId }) => ({ const mapDispatchToProps = (dispatch, { statusId }) => ({
onItemClick (index) { onItemClick (index) {
dispatch(openModal('COMPARE_HISTORY', { index, statusId })); dispatch(openModal({
modalType: 'COMPARE_HISTORY',
modalProps: { index, statusId },
}));
}, },
}); });

View file

@ -35,10 +35,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow (account) { onFollow (account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
if (unfollowModal) { if (unfollowModal) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.unfollowConfirm), modalProps: {
onConfirm: () => dispatch(unfollowAccount(account.get('id'))), message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
})); }));
} else { } else {
dispatch(unfollowAccount(account.get('id'))); dispatch(unfollowAccount(account.get('id')));

View file

@ -18,10 +18,13 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onBlockDomain (domain) { onBlockDomain (domain) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.blockDomainConfirm), modalProps: {
onConfirm: () => dispatch(blockDomain(domain)), message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
},
})); }));
}, },

View file

@ -18,15 +18,21 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
dispatch(fetchRelationships([status.getIn(['account', 'id'])])); dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
} }
dispatch(isUserTouching() ? openModal('ACTIONS', { dispatch(isUserTouching() ? openModal({
status, modalType: 'ACTIONS',
actions: items, modalProps: {
onClick: onItemClick, status,
actions: items,
onClick: onItemClick,
},
}) : openDropdownMenu(id, keyboard, scrollKey)); }) : openDropdownMenu(id, keyboard, scrollKey));
}, },
onClose(id) { onClose(id) {
dispatch(closeModal('ACTIONS')); dispatch(closeModal({
modalType: 'ACTIONS',
ignoreFocus: false,
}));
dispatch(closeDropdownMenu(id)); dispatch(closeDropdownMenu(id));
}, },
}); });

View file

@ -82,10 +82,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
let state = getState(); let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) { if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.replyMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.replyConfirm), modalProps: {
onConfirm: () => dispatch(replyCompose(status, router)), message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, router)) },
})); }));
} else { } else {
dispatch(replyCompose(status, router)); dispatch(replyCompose(status, router));
@ -134,9 +136,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}, },
onEmbed (status) { onEmbed (status) {
dispatch(openModal('EMBED', { dispatch(openModal({
url: status.get('url'), modalType: 'EMBED',
onError: error => dispatch(showAlertForError(error)), modalProps: {
url: status.get('url'),
onError: error => dispatch(showAlertForError(error)),
},
})); }));
}, },
@ -144,10 +149,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
if (!deleteModal) { if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft)); dispatch(deleteStatus(status.get('id'), history, withRedraft));
} else { } else {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), modalProps: {
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
},
})); }));
} }
}, },
@ -156,10 +164,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch((_, getState) => { dispatch((_, getState) => {
let state = getState(); let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) { if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.editMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.editConfirm), modalProps: {
onConfirm: () => dispatch(editStatus(status.get('id'), history)), message: intl.formatMessage(messages.editMessage),
confirm: intl.formatMessage(messages.editConfirm),
onConfirm: () => dispatch(editStatus(status.get('id'), history)),
},
})); }));
} else { } else {
dispatch(editStatus(status.get('id'), history)); dispatch(editStatus(status.get('id'), history));
@ -184,11 +195,17 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}, },
onOpenMedia (statusId, media, index, lang) { onOpenMedia (statusId, media, index, lang) {
dispatch(openModal('MEDIA', { statusId, media, index, lang })); dispatch(openModal({
modalType: 'MEDIA',
modalProps: { statusId, media, index, lang },
}));
}, },
onOpenVideo (statusId, media, lang, options) { onOpenVideo (statusId, media, lang, options) {
dispatch(openModal('VIDEO', { statusId, media, lang, options })); dispatch(openModal({
modalType: 'VIDEO',
modalProps: { statusId, media, lang, options },
}));
}, },
onBlock (status) { onBlock (status) {
@ -237,10 +254,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}, },
onBlockDomain (domain) { onBlockDomain (domain) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.blockDomainConfirm), modalProps: {
onConfirm: () => dispatch(blockDomain(domain)), message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
},
})); }));
}, },
@ -253,10 +273,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}, },
onInteractionModal (type, status) { onInteractionModal (type, status) {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type, modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type,
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
}, },

View file

@ -143,14 +143,23 @@ class AccountGallery extends ImmutablePureComponent {
const lang = attachment.getIn(['status', 'language']); const lang = attachment.getIn(['status', 'language']);
if (attachment.get('type') === 'video') { if (attachment.get('type') === 'video') {
dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } })); dispatch(openModal({
modalType: 'VIDEO',
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
}));
} else if (attachment.get('type') === 'audio') { } else if (attachment.get('type') === 'audio') {
dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } })); dispatch(openModal({
modalType: 'AUDIO',
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
}));
} else { } else {
const media = attachment.getIn(['status', 'media_attachments']); const media = attachment.getIn(['status', 'media_attachments']);
const index = media.findIndex(x => x.get('id') === attachment.get('id')); const index = media.findIndex(x => x.get('id') === attachment.get('id'));
dispatch(openModal('MEDIA', { media, index, statusId, lang })); dispatch(openModal({
modalType: 'MEDIA',
modalProps: { media, index, statusId, lang },
}));
} }
}; };

View file

@ -48,20 +48,26 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow (account) { onFollow (account) {
if (account.getIn(['relationship', 'following'])) { if (account.getIn(['relationship', 'following'])) {
if (unfollowModal) { if (unfollowModal) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.unfollowConfirm), modalProps: {
onConfirm: () => dispatch(unfollowAccount(account.get('id'))), message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
})); }));
} else { } else {
dispatch(unfollowAccount(account.get('id'))); dispatch(unfollowAccount(account.get('id')));
} }
} else if (account.getIn(['relationship', 'requested'])) { } else if (account.getIn(['relationship', 'requested'])) {
if (unfollowModal) { if (unfollowModal) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), modalProps: {
onConfirm: () => dispatch(unfollowAccount(account.get('id'))), message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
})); }));
} else { } else {
dispatch(unfollowAccount(account.get('id'))); dispatch(unfollowAccount(account.get('id')));
@ -72,10 +78,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onInteractionModal (account) { onInteractionModal (account) {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'follow', modalType: 'INTERACTION',
accountId: account.get('id'), modalProps: {
url: account.get('url'), type: 'follow',
accountId: account.get('id'),
url: account.get('url'),
},
})); }));
}, },
@ -132,10 +141,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onBlockDomain (domain) { onBlockDomain (domain) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.blockDomainConfirm), modalProps: {
onConfirm: () => dispatch(blockDomain(domain)), message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
},
})); }));
}, },
@ -144,21 +156,30 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onAddToList (account) { onAddToList (account) {
dispatch(openModal('LIST_ADDER', { dispatch(openModal({
accountId: account.get('id'), modalType: 'LIST_ADDER',
modalProps: {
accountId: account.get('id'),
},
})); }));
}, },
onChangeLanguages (account) { onChangeLanguages (account) {
dispatch(openModal('SUBSCRIBED_LANGUAGES', { dispatch(openModal({
accountId: account.get('id'), modalType: 'SUBSCRIBED_LANGUAGES',
modalProps: {
accountId: account.get('id'),
},
})); }));
}, },
onOpenAvatar (account) { onOpenAvatar (account) {
dispatch(openModal('IMAGE', { dispatch(openModal({
src: account.get('avatar'), modalType: 'IMAGE',
alt: account.get('acct'), modalProps: {
src: account.get('avatar'),
alt: account.get('acct'),
},
})); }));
}, },

View file

@ -21,11 +21,14 @@ const mapStateToProps = state => {
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () { onLogout () {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.logoutMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.logoutConfirm), modalProps: {
closeWhenConfirm: false, message: intl.formatMessage(messages.logoutMessage),
onConfirm: () => logOut(), confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
})); }));
}, },
}); });

View file

@ -16,8 +16,14 @@ const mapDispatchToProps = dispatch => ({
}, },
isUserTouching, isUserTouching,
onModalOpen: props => dispatch(openModal('ACTIONS', props)), onModalOpen: props => dispatch(openModal({
onModalClose: () => dispatch(closeModal()), modalType: 'ACTIONS',
modalProps: props,
})),
onModalClose: () => dispatch(closeModal({
modalType: undefined,
ignoreFocus: false,
})),
}); });

View file

@ -71,11 +71,14 @@ class Compose extends PureComponent {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.logoutMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.logoutConfirm), modalProps: {
closeWhenConfirm: false, message: intl.formatMessage(messages.logoutMessage),
onConfirm: () => logOut(), confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
})); }));
return false; return false;

View file

@ -41,10 +41,13 @@ const mapDispatchToProps = (dispatch, { intl, conversationId }) => ({
let state = getState(); let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) { if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.replyMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.replyConfirm), modalProps: {
onConfirm: () => dispatch(replyCompose(status, router)), message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, router)),
},
})); }));
} else { } else {
dispatch(replyCompose(status, router)); dispatch(replyCompose(status, router));

View file

@ -50,27 +50,32 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (account.getIn(['relationship', 'following'])) { if (account.getIn(['relationship', 'following'])) {
if (unfollowModal) { if (unfollowModal) {
dispatch( dispatch(
openModal('CONFIRM', { openModal({
message: ( modalType: 'CONFIRM',
<FormattedMessage modalProps: {
id='confirmations.unfollow.message' message: (
defaultMessage='Are you sure you want to unfollow {name}?' <FormattedMessage
values={{ name: <strong>@{account.get('acct')}</strong> }} id='confirmations.unfollow.message'
/> defaultMessage='Are you sure you want to unfollow {name}?'
), values={{ name: <strong>@{account.get('acct')}</strong> }}
confirm: intl.formatMessage(messages.unfollowConfirm), />
onConfirm: () => dispatch(unfollowAccount(account.get('id'))), ),
}), confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
} }),
); );
} else { } else {
dispatch(unfollowAccount(account.get('id'))); dispatch(unfollowAccount(account.get('id')));
} }
} else if (account.getIn(['relationship', 'requested'])) { } else if (account.getIn(['relationship', 'requested'])) {
if (unfollowModal) { if (unfollowModal) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), modalProps: {
onConfirm: () => dispatch(unfollowAccount(account.get('id'))), message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
})); }));
} else { } else {
dispatch(unfollowAccount(account.get('id'))); dispatch(unfollowAccount(account.get('id')));

View file

@ -18,8 +18,11 @@ const mapStateToProps = (state, { accountId }) => ({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
onSignupClick() { onSignupClick() {
dispatch(closeModal()); dispatch(closeModal({
dispatch(openModal('CLOSED_REGISTRATIONS')); modalType: undefined,
ignoreFocus: false,
}));
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
}, },
}); });

View file

@ -114,24 +114,30 @@ class ListTimeline extends PureComponent {
}; };
handleEditClick = () => { handleEditClick = () => {
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id })); this.props.dispatch(openModal({
modalType: 'LIST_EDITOR',
modalProps: { listId: this.props.params.id },
}));
}; };
handleDeleteClick = () => { handleDeleteClick = () => {
const { dispatch, columnId, intl } = this.props; const { dispatch, columnId, intl } = this.props;
const { id } = this.props.params; const { id } = this.props.params;
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.deleteMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.deleteConfirm), modalProps: {
onConfirm: () => { message: intl.formatMessage(messages.deleteMessage),
dispatch(deleteList(id)); confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteList(id));
if (columnId) { if (columnId) {
dispatch(removeColumn(columnId)); dispatch(removeColumn(columnId));
} else { } else {
this.context.router.history.push('/lists'); this.context.router.history.push('/lists');
} }
},
}, },
})); }));
}; };

View file

@ -59,10 +59,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onClear () { onClear () {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.clearMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.clearConfirm), modalProps: {
onConfirm: () => dispatch(clearNotifications()), message: intl.formatMessage(messages.clearMessage),
confirm: intl.formatMessage(messages.clearConfirm),
onConfirm: () => dispatch(clearNotifications()),
},
})); }));
}, },

View file

@ -74,19 +74,25 @@ class Footer extends ImmutablePureComponent {
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.replyMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.replyConfirm), modalProps: {
onConfirm: this._performReply, message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: this._performReply,
},
})); }));
} else { } else {
this._performReply(); this._performReply();
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'reply', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'reply',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };
@ -102,10 +108,13 @@ class Footer extends ImmutablePureComponent {
dispatch(favourite(status)); dispatch(favourite(status));
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'favourite', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'favourite',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };
@ -128,10 +137,13 @@ class Footer extends ImmutablePureComponent {
dispatch(initBoostModal({ status, onReblog: this._performReblog })); dispatch(initBoostModal({ status, onReblog: this._performReblog }));
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'reblog', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'reblog',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };

View file

@ -60,10 +60,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch((_, getState) => { dispatch((_, getState) => {
let state = getState(); let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) { if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.replyMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.replyConfirm), modalProps: {
onConfirm: () => dispatch(replyCompose(status, router)), message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, router)),
},
})); }));
} else { } else {
dispatch(replyCompose(status, router)); dispatch(replyCompose(status, router));
@ -104,9 +107,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onEmbed (status) { onEmbed (status) {
dispatch(openModal('EMBED', { dispatch(openModal({
url: status.get('url'), modalType: 'EMBED',
onError: error => dispatch(showAlertForError(error)), modalProps: {
url: status.get('url'),
onError: error => dispatch(showAlertForError(error)),
},
})); }));
}, },
@ -114,10 +120,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (!deleteModal) { if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft)); dispatch(deleteStatus(status.get('id'), history, withRedraft));
} else { } else {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), modalProps: {
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
},
})); }));
} }
}, },
@ -131,11 +140,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}, },
onOpenMedia (media, index, lang) { onOpenMedia (media, index, lang) {
dispatch(openModal('MEDIA', { media, index, lang })); dispatch(openModal({
modalType: 'MEDIA',
modalProps: { media, index, lang },
}));
}, },
onOpenVideo (media, lang, options) { onOpenVideo (media, lang, options) {
dispatch(openModal('VIDEO', { media, lang, options })); dispatch(openModal({
modalType: 'VIDEO',
modalProps: { media, lang, options },
}));
}, },
onBlock (status) { onBlock (status) {

View file

@ -251,10 +251,13 @@ class Status extends ImmutablePureComponent {
dispatch(favourite(status)); dispatch(favourite(status));
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'favourite', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'favourite',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };
@ -273,19 +276,25 @@ class Status extends ImmutablePureComponent {
if (signedIn) { if (signedIn) {
if (askReplyConfirmation) { if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.replyMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.replyConfirm), modalProps: {
onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
},
})); }));
} else { } else {
dispatch(replyCompose(status, this.context.router.history)); dispatch(replyCompose(status, this.context.router.history));
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'reply', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'reply',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };
@ -309,10 +318,13 @@ class Status extends ImmutablePureComponent {
} }
} }
} else { } else {
dispatch(openModal('INTERACTION', { dispatch(openModal({
type: 'reblog', modalType: 'INTERACTION',
accountId: status.getIn(['account', 'id']), modalProps: {
url: status.get('url'), type: 'reblog',
accountId: status.getIn(['account', 'id']),
url: status.get('url'),
},
})); }));
} }
}; };
@ -331,10 +343,13 @@ class Status extends ImmutablePureComponent {
if (!deleteModal) { if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft)); dispatch(deleteStatus(status.get('id'), history, withRedraft));
} else { } else {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), modalProps: {
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)), message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
},
})); }));
} }
}; };
@ -352,11 +367,17 @@ class Status extends ImmutablePureComponent {
}; };
handleOpenMedia = (media, index, lang) => { handleOpenMedia = (media, index, lang) => {
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); this.props.dispatch(openModal({
modalType: 'MEDIA',
modalProps: { statusId: this.props.status.get('id'), media, index, lang },
}));
}; };
handleOpenVideo = (media, lang, options) => { handleOpenVideo = (media, lang, options) => {
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); this.props.dispatch(openModal({
modalType: 'VIDEO',
modalProps: { statusId: this.props.status.get('id'), media, lang, options },
}));
}; };
handleHotkeyOpenMedia = e => { handleHotkeyOpenMedia = e => {
@ -425,7 +446,10 @@ class Status extends ImmutablePureComponent {
}; };
handleEmbed = (status) => { handleEmbed = (status) => {
this.props.dispatch(openModal('EMBED', { url: status.get('url') })); this.props.dispatch(openModal({
modalType: 'EMBED',
modalProps: { url: status.get('url') },
}));
}; };
handleUnmuteClick = account => { handleUnmuteClick = account => {
@ -437,10 +461,13 @@ class Status extends ImmutablePureComponent {
}; };
handleBlockDomainClick = domain => { handleBlockDomainClick = domain => {
this.props.dispatch(openModal('CONFIRM', { this.props.dispatch(openModal({
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />, modalType: 'CONFIRM',
confirm: this.props.intl.formatMessage(messages.blockDomainConfirm), modalProps: {
onConfirm: () => this.props.dispatch(blockDomain(domain)), message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: this.props.intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => this.props.dispatch(blockDomain(domain)),
},
})); }));
}; };

View file

@ -33,7 +33,10 @@ const mapDispatchToProps = dispatch => {
}, },
onClose() { onClose() {
dispatch(closeModal()); dispatch(closeModal({
modalType: undefined,
ignoreFocus: false,
}));
}, },
}; };
}; };

View file

@ -23,7 +23,10 @@ const mapStateToProps = (state, { statusId }) => ({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onClose() { onClose() {
dispatch(closeModal()); dispatch(closeModal({
modalType: undefined,
ignoreFocus: false,
}));
}, },
}); });

View file

@ -23,11 +23,14 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () { onLogout () {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.logoutMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.logoutConfirm), modalProps: {
closeWhenConfirm: false, message: intl.formatMessage(messages.logoutMessage),
onConfirm: () => logOut(), confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
})); }));
}, },
}); });

View file

@ -26,7 +26,7 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
openClosedRegistrationsModal() { openClosedRegistrationsModal() {
dispatch(openModal('CLOSED_REGISTRATIONS')); dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
}, },
}); });

View file

@ -19,11 +19,14 @@ const messages = defineMessages({
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onLogout () { onLogout () {
dispatch(openModal('CONFIRM', { dispatch(openModal({
message: intl.formatMessage(messages.logoutMessage), modalType: 'CONFIRM',
confirm: intl.formatMessage(messages.logoutConfirm), modalProps: {
closeWhenConfirm: false, message: intl.formatMessage(messages.logoutMessage),
onConfirm: () => logOut(), confirm: intl.formatMessage(messages.logoutConfirm),
closeWhenConfirm: false,
onConfirm: () => logOut(),
},
})); }));
}, },
}); });

View file

@ -32,7 +32,7 @@ import MediaModal from './media_modal';
import ModalLoading from './modal_loading'; import ModalLoading from './modal_loading';
import VideoModal from './video_modal'; import VideoModal from './video_modal';
const MODAL_COMPONENTS = { export const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }), 'MEDIA': () => Promise.resolve({ default: MediaModal }),
'VIDEO': () => Promise.resolve({ default: VideoModal }), 'VIDEO': () => Promise.resolve({ default: VideoModal }),
'AUDIO': () => Promise.resolve({ default: AudioModal }), 'AUDIO': () => Promise.resolve({ default: AudioModal }),

View file

@ -34,7 +34,10 @@ const mapDispatchToProps = dispatch => {
}, },
onClose() { onClose() {
dispatch(closeModal()); dispatch(closeModal({
modalType: undefined,
ignoreFocus: false,
}));
}, },
onToggleNotifications() { onToggleNotifications() {

View file

@ -11,7 +11,7 @@ const SignInBanner = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const openClosedRegistrationsModal = useCallback( const openClosedRegistrationsModal = useCallback(
() => dispatch(openModal('CLOSED_REGISTRATIONS')), () => dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' })),
[dispatch], [dispatch],
); );

View file

@ -13,14 +13,22 @@ const mapDispatchToProps = dispatch => ({
onClose (confirmationMessage, ignoreFocus = false) { onClose (confirmationMessage, ignoreFocus = false) {
if (confirmationMessage) { if (confirmationMessage) {
dispatch( dispatch(
openModal('CONFIRM', { openModal({
message: confirmationMessage.message, modalType: 'CONFIRM',
confirm: confirmationMessage.confirm, modalProps: {
onConfirm: () => dispatch(closeModal(undefined, { ignoreFocus })), message: confirmationMessage.message,
}), confirm: confirmationMessage.confirm,
onConfirm: () => dispatch(closeModal({
modalType: undefined,
ignoreFocus: { ignoreFocus },
})),
} }),
); );
} else { } else {
dispatch(closeModal(undefined, { ignoreFocus })); dispatch(closeModal({
modalType: undefined,
ignoreFocus: { ignoreFocus },
}));
} }
}, },
}); });

View file

@ -25,7 +25,7 @@ import markers from './markers';
import media_attachments from './media_attachments'; import media_attachments from './media_attachments';
import meta from './meta'; import meta from './meta';
import { missedUpdatesReducer } from './missed_updates'; import { missedUpdatesReducer } from './missed_updates';
import modal from './modal'; import { modalReducer } from './modal';
import mutes from './mutes'; import mutes from './mutes';
import notifications from './notifications'; import notifications from './notifications';
import picture_in_picture from './picture_in_picture'; import picture_in_picture from './picture_in_picture';
@ -50,7 +50,7 @@ const reducers = {
meta, meta,
alerts, alerts,
loadingBar: loadingBarReducer, loadingBar: loadingBarReducer,
modal, modal: modalReducer,
user_lists, user_lists,
domain_lists, domain_lists,
status_lists, status_lists,

View file

@ -1,40 +0,0 @@
import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable';
import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose';
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
import { TIMELINE_DELETE } from '../actions/timelines';
const initialState = ImmutableMap({
ignoreFocus: false,
stack: ImmutableStack(),
});
const popModal = (state, { modalType, ignoreFocus }) => {
if (modalType === undefined || modalType === state.getIn(['stack', 0, 'modalType'])) {
return state.set('ignoreFocus', !!ignoreFocus).update('stack', stack => stack.shift());
} else {
return state;
}
};
const pushModal = (state, modalType, modalProps) => {
return state.withMutations(map => {
map.set('ignoreFocus', false);
map.update('stack', stack => stack.unshift(ImmutableMap({ modalType, modalProps })));
});
};
export default function modal(state = initialState, action) {
switch(action.type) {
case MODAL_OPEN:
return pushModal(state, action.modalType, action.modalProps);
case MODAL_CLOSE:
return popModal(state, action);
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false });
case TIMELINE_DELETE:
return state.update('stack', stack => stack.filterNot((modal) => modal.get('modalProps').statusId === action.id));
default:
return state;
}
}

View file

@ -0,0 +1,94 @@
import { Record as ImmutableRecord, Stack } from 'immutable';
import type { PayloadAction } from '@reduxjs/toolkit';
import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose';
import type { ModalType } from '../actions/modal';
import { openModal, closeModal } from '../actions/modal';
import { TIMELINE_DELETE } from '../actions/timelines';
type ModalProps = Record<string, unknown>;
interface Modal {
modalType: ModalType;
modalProps: ModalProps;
}
const Modal = ImmutableRecord<Modal>({
modalType: 'ACTIONS',
modalProps: ImmutableRecord({})(),
});
interface ModalState {
ignoreFocus: boolean;
stack: Stack<ImmutableRecord<Modal>>;
}
const initialState = ImmutableRecord<ModalState>({
ignoreFocus: false,
stack: Stack(),
})();
type State = typeof initialState;
interface PopModalOption {
modalType: ModalType | undefined;
ignoreFocus: boolean;
}
const popModal = (
state: State,
{ modalType, ignoreFocus }: PopModalOption
): State => {
if (
modalType === undefined ||
modalType === state.get('stack').get(0)?.get('modalType')
) {
return state
.set('ignoreFocus', !!ignoreFocus)
.update('stack', (stack) => stack.shift());
} else {
return state;
}
};
const pushModal = (
state: State,
modalType: ModalType,
modalProps: ModalProps
): State => {
return state.withMutations((record) => {
record.set('ignoreFocus', false);
record.update('stack', (stack) =>
stack.unshift(Modal({ modalType, modalProps }))
);
});
};
export function modalReducer(
state: State = initialState,
action: PayloadAction<{
modalType: ModalType;
ignoreFocus: boolean;
modalProps: Record<string, unknown>;
}>
) {
switch (action.type) {
case openModal.type:
return pushModal(
state,
action.payload.modalType,
action.payload.modalProps
);
case closeModal.type:
return popModal(state, action.payload);
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false });
case TIMELINE_DELETE:
return state.update('stack', (stack) =>
stack.filterNot(
// @ts-expect-error TIMELINE_DELETE action is not typed yet.
(modal) => modal.get('modalProps').statusId === action.id
)
);
default:
return state;
}
}