Change paste-link-to-quote loading state from generic loading bar to compose placeholder (#36695)
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
|
||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||
import type { Status, StatusVisibility } from '../models/status';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
import { showAlert } from './alerts';
|
||||
import { changeCompose, focusCompose } from './compose';
|
||||
@@ -212,6 +213,17 @@ export const quoteComposeById = createAppThunk(
|
||||
},
|
||||
);
|
||||
|
||||
const composeStateForbidsLink = (composeState: RootState['compose']) => {
|
||||
return (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id') ||
|
||||
composeState.get('privacy') === 'direct'
|
||||
);
|
||||
};
|
||||
|
||||
export const pasteLinkCompose = createDataLoadingThunk(
|
||||
'compose/pasteLink',
|
||||
async ({ url }: { url: string }) => {
|
||||
@@ -222,16 +234,12 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
limit: 2,
|
||||
});
|
||||
},
|
||||
(data, { dispatch, getState }) => {
|
||||
(data, { dispatch, getState, requestId }) => {
|
||||
const composeState = getState().compose;
|
||||
|
||||
if (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id') ||
|
||||
composeState.get('privacy') === 'direct'
|
||||
composeStateForbidsLink(composeState) ||
|
||||
composeState.get('fetching_link') !== requestId // Request has been cancelled
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -247,6 +255,17 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
dispatch(quoteComposeById(data.statuses[0].id));
|
||||
}
|
||||
},
|
||||
{
|
||||
useLoadingBar: false,
|
||||
condition: (_, { getState }) =>
|
||||
!getState().compose.get('fetching_link') &&
|
||||
!composeStateForbidsLink(getState().compose),
|
||||
},
|
||||
);
|
||||
|
||||
// Ideally this would cancel the action and the HTTP request, but this is good enough
|
||||
export const cancelPasteLinkCompose = createAction(
|
||||
'compose/cancelPasteLinkCompose',
|
||||
);
|
||||
|
||||
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { cancelPasteLinkCompose } from '@/mastodon/actions/compose_typed';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
|
||||
const messages = defineMessages({
|
||||
quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' },
|
||||
});
|
||||
|
||||
export const QuotePlaceholder: FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const handleQuoteCancel = useCallback(() => {
|
||||
dispatch(cancelPasteLinkCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className='status__quote'>
|
||||
<div className='status'>
|
||||
<div className='status__info'>
|
||||
<div className='status__avatar'>
|
||||
<Skeleton width='32px' height='32px' />
|
||||
</div>
|
||||
<div className='status__display-name'>
|
||||
<DisplayName />
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={handleQuoteCancel}
|
||||
className='status__quote-cancel'
|
||||
title={intl.formatMessage(messages.quote_cancel)}
|
||||
icon='cancel-fill'
|
||||
iconComponent={CancelFillIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className='status__content'>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/mastodon/actions/compose_typed';
|
||||
import { QuotedStatus } from '@/mastodon/components/status_quoted';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { QuotePlaceholder } from './quote_placeholder';
|
||||
|
||||
export const ComposeQuotedStatus: FC = () => {
|
||||
const quotedStatusId = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||
);
|
||||
|
||||
const isFetchingLink = useAppSelector(
|
||||
(state) => !!state.compose.get('fetching_link'),
|
||||
);
|
||||
|
||||
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||
|
||||
const quote = useMemo(
|
||||
@@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => {
|
||||
dispatch(quoteComposeCancel());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!quote) {
|
||||
if (isFetchingLink && !quote) {
|
||||
return <QuotePlaceholder />;
|
||||
} else if (!quote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
quoteCompose,
|
||||
quoteComposeCancel,
|
||||
setComposeQuotePolicy,
|
||||
pasteLinkCompose,
|
||||
cancelPasteLinkCompose,
|
||||
} from '@/mastodon/actions/compose_typed';
|
||||
import { timelineDelete } from 'mastodon/actions/timelines_typed';
|
||||
|
||||
@@ -93,6 +95,7 @@ const initialState = ImmutableMap({
|
||||
quoted_status_id: null,
|
||||
quote_policy: 'public',
|
||||
default_quote_policy: 'public', // Set in hydration.
|
||||
fetching_link: null,
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
@@ -350,6 +353,12 @@ export const composeReducer = (state = initialState, action) => {
|
||||
return state.set('quoted_status_id', null);
|
||||
} else if (setComposeQuotePolicy.match(action)) {
|
||||
return state.set('quote_policy', action.payload);
|
||||
} else if (pasteLinkCompose.pending.match(action)) {
|
||||
return state.set('fetching_link', action.meta.requestId);
|
||||
} else if (pasteLinkCompose.fulfilled.match(action) || pasteLinkCompose.rejected.match(action)) {
|
||||
return action.meta.requestId === state.get('fetching_link') ? state.set('fetching_link', null) : state;
|
||||
} else if (cancelPasteLinkCompose.match(action)) {
|
||||
return state.set('fetching_link', null);
|
||||
}
|
||||
|
||||
switch(action.type) {
|
||||
|
||||
@@ -42,7 +42,7 @@ interface AppThunkConfig {
|
||||
}
|
||||
export type AppThunkApi = Pick<
|
||||
GetThunkAPI<AppThunkConfig>,
|
||||
'getState' | 'dispatch'
|
||||
'getState' | 'dispatch' | 'requestId'
|
||||
>;
|
||||
|
||||
interface AppThunkOptions<Arg> {
|
||||
@@ -60,7 +60,7 @@ type AppThunk<Arg = void, Returned = void> = (
|
||||
|
||||
type AppThunkCreator<Arg = void, Returned = void, ExtraArg = unknown> = (
|
||||
arg: Arg,
|
||||
api: AppThunkApi,
|
||||
api: Pick<AppThunkApi, 'getState' | 'dispatch'>,
|
||||
extra?: ExtraArg,
|
||||
) => Returned;
|
||||
|
||||
@@ -143,10 +143,10 @@ export function createAsyncThunk<Arg = void, Returned = void>(
|
||||
name,
|
||||
async (
|
||||
arg: Arg,
|
||||
{ getState, dispatch, fulfillWithValue, rejectWithValue },
|
||||
{ getState, dispatch, requestId, fulfillWithValue, rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const result = await creator(arg, { dispatch, getState });
|
||||
const result = await creator(arg, { dispatch, getState, requestId });
|
||||
|
||||
return fulfillWithValue(result, {
|
||||
useLoadingBar: options.useLoadingBar,
|
||||
@@ -280,10 +280,11 @@ export function createDataLoadingThunk<
|
||||
|
||||
return createAsyncThunk<Args, Returned>(
|
||||
name,
|
||||
async (arg, { getState, dispatch }) => {
|
||||
async (arg, { getState, dispatch, requestId }) => {
|
||||
const data = await loadData(arg, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
});
|
||||
|
||||
if (!onData) return data as Returned;
|
||||
@@ -291,6 +292,7 @@ export function createDataLoadingThunk<
|
||||
const result = await onData(data, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
discardLoadData: discardLoadDataInPayload,
|
||||
actionArg: arg,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user