Introduce useCompose hook

Signed-off-by: marcin mikolajczak <git@mkljczk.pl>
environments/review-compose-fe5g2p/deployments/978
marcin mikolajczak 2022-09-14 20:01:00 +02:00
rodzic 2b6d06ce01
commit 6cce0a0291
25 zmienionych plików z 98 dodań i 76 usunięć

Wyświetl plik

@ -215,7 +215,7 @@ const needsDescriptions = (state: RootState, composeId: string) => {
};
const validateSchedule = (state: RootState, composeId: string) => {
const schedule = state.compose.get(composeId)!.schedule;
const schedule = state.compose.get(composeId)?.schedule;
if (!schedule) return true;
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000);

Wyświetl plik

@ -24,8 +24,8 @@ export const checkComposeContent = compose => {
};
const mapStateToProps = state => ({
hasComposeContent: checkComposeContent(state.compose.get('modal')),
isEditing: state.compose.get('modal')?.id !== null,
hasComposeContent: checkComposeContent(state.compose.get('compose-modal')),
isEditing: state.compose.get('compose-modal')?.id !== null,
});
const mapDispatchToProps = (dispatch) => ({

Wyświetl plik

@ -18,7 +18,7 @@ import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest
import AutosuggestTextarea from 'soapbox/components/autosuggest_textarea';
import Icon from 'soapbox/components/icon';
import { Button, Stack } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useCompose, useFeatures } from 'soapbox/hooks';
import { isMobile } from 'soapbox/is_mobile';
import EmojiPickerDropdown from '../components/emoji-picker/emoji-picker-dropdown';
@ -68,7 +68,7 @@ const ComposeForm: React.FC<IComposeForm> = ({ id, shouldCondense, autoFocus, cl
const intl = useIntl();
const dispatch = useAppDispatch();
const compose = useAppSelector((state) => state.compose.get(id)!);
const compose = useCompose(id);
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
const isModalOpen = useAppSelector((state) => !!(state.modals.size && state.modals.last()!.modalType === 'COMPOSE'));
const maxTootChars = useAppSelector((state) => state.instance.getIn(['configuration', 'statuses', 'max_characters'])) as number;
@ -218,6 +218,18 @@ const ComposeForm: React.FC<IComposeForm> = ({ id, shouldCondense, autoFocus, cl
}
}, [focusDate]);
const renderButtons = useCallback(() => (
<div className='flex items-center space-x-2'>
{features.media && <UploadButtonContainer composeId={id} />}
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} />
{features.polls && <PollButton composeId={id} />}
{features.privacyScopes && <PrivacyDropdown composeId={id} />}
{features.scheduledStatuses && <ScheduleButton composeId={id} />}
{features.spoilers && <SpoilerButton composeId={id} />}
{features.richText && <MarkdownButton composeId={id} />}
</div>
), [features, id]);
const condensed = shouldCondense && !composeFocused && isEmpty() && !isUploading;
const disabled = isSubmitting;
const countedText = [spoilerText, countableText(text)].join('');
@ -335,15 +347,7 @@ const ComposeForm: React.FC<IComposeForm> = ({ id, shouldCondense, autoFocus, cl
'hidden': condensed,
})}
>
<div className='flex items-center space-x-2'>
{features.media && <UploadButtonContainer composeId={id} />}
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} />
{features.polls && <PollButton composeId={id} />}
{features.privacyScopes && <PrivacyDropdown composeId={id} />}
{features.scheduledStatuses && <ScheduleButton composeId={id} />}
{features.spoilers && <SpoilerButton composeId={id} />}
{features.richText && <MarkdownButton composeId={id} />}
</div>
{renderButtons()}
<div className='flex items-center space-x-4 ml-auto'>
{maxTootChars && (

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { changeComposeContentType } from 'soapbox/actions/compose';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import ComposeFormButton from './compose_form_button';
@ -19,7 +19,7 @@ const MarkdownButton: React.FC<IMarkdownButton> = ({ composeId }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const active = useAppSelector((state) => state.compose.get(composeId)!.content_type === 'text/markdown');
const active = useCompose(composeId).content_type === 'text/markdown';
const onClick = () => dispatch(changeComposeContentType(composeId, active ? 'text/plain' : 'text/markdown'));

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { addPoll, removePoll } from 'soapbox/actions/compose';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import ComposeFormButton from './compose_form_button';
@ -20,8 +20,10 @@ const PollButton: React.FC<IPollButton> = ({ composeId, disabled }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const unavailable = useAppSelector((state) => state.compose.get(composeId)!.is_uploading);
const active = useAppSelector((state) => state.compose.get(composeId)!.poll !== null);
const compose = useCompose(composeId);
const unavailable = compose.is_uploading;
const active = compose.poll !== null;
const onClick = () => {
if (active) {

Wyświetl plik

@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { addPollOption, changePollOption, changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, removePoll, removePollOption, selectComposeSuggestion } from 'soapbox/actions/compose';
import AutosuggestInput from 'soapbox/components/autosuggest_input';
import { Button, Divider, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useCompose } from 'soapbox/hooks';
import DurationSelector from './duration-selector';
@ -49,7 +49,7 @@ const Option: React.FC<IOption> = ({
const dispatch = useAppDispatch();
const intl = useIntl();
const suggestions = useAppSelector((state) => state.compose.get(composeId)!.suggestions);
const suggestions = useCompose(composeId).suggestions;
const handleOptionTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => onChange(index, event.target.value);
@ -110,10 +110,12 @@ const PollForm: React.FC<IPollForm> = ({ composeId }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const compose = useCompose(composeId);
const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any);
const options = useAppSelector((state) => state.compose.get(composeId)!.poll?.options);
const expiresIn = useAppSelector((state) => state.compose.get(composeId)!.poll?.expires_in);
const isMultiple = useAppSelector((state) => state.compose.get(composeId)!.poll?.multiple);
const options = compose.poll?.options;
const expiresIn = compose.poll?.expires_in;
const isMultiple = compose.poll?.multiple;
const maxOptions = pollLimits.get('max_options');
const maxOptionChars = pollLimits.get('max_characters_per_option');

Wyświetl plik

@ -10,7 +10,7 @@ import { changeComposeVisibility } from 'soapbox/actions/compose';
import { closeModal, openModal } from 'soapbox/actions/modals';
import Icon from 'soapbox/components/icon';
import { IconButton } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import { isUserTouching } from 'soapbox/is_mobile';
import Motion from '../../ui/util/optional_motion';
@ -151,8 +151,10 @@ const PrivacyDropdown: React.FC<IPrivacyDropdown> = ({
const node = useRef<HTMLDivElement>(null);
const activeElement = useRef<HTMLElement | null>(null);
const value = useAppSelector(state => state.compose.get(composeId)!.privacy);
const unavailable = useAppSelector(state => !!state.compose.get(composeId)!.id);
const compose = useCompose(composeId);
const value = compose.privacy;
const unavailable = compose.id;
const [open, setOpen] = useState(false);
const [placement, setPlacement] = useState('bottom');

Wyświetl plik

@ -3,7 +3,7 @@ import { FormattedList, FormattedMessage } from 'react-intl';
import { useDispatch } from 'react-redux';
import { openModal } from 'soapbox/actions/modals';
import { useAppSelector } from 'soapbox/hooks';
import { useAppSelector, useCompose } from 'soapbox/hooks';
import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose';
import { makeGetStatus } from 'soapbox/selectors';
import { getFeatures } from 'soapbox/utils/features';
@ -16,10 +16,12 @@ interface IReplyMentions {
const ReplyMentions: React.FC<IReplyMentions> = ({ composeId }) => {
const dispatch = useDispatch();
const instance = useAppSelector((state) => state.instance);
const status = useAppSelector<StatusEntity | null>(state => makeGetStatus()(state, { id: state.compose.get(composeId)!.in_reply_to! }));
const to = useAppSelector((state) => state.compose.get(composeId)!.to);
const compose = useCompose(composeId);
const instance = useAppSelector((state) => state.instance);
const status = useAppSelector<StatusEntity | null>(state => makeGetStatus()(state, { id: compose.in_reply_to! }));
const to = compose.to;
const account = useAppSelector((state) => state.accounts.get(state.me));
const { explicitAddressing } = getFeatures(instance);

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { addSchedule, removeSchedule } from 'soapbox/actions/compose';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import ComposeFormButton from './compose_form_button';
@ -20,8 +20,10 @@ const ScheduleButton: React.FC<IScheduleButton> = ({ composeId, disabled }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const active = useAppSelector((state) => !!state.compose.get(composeId)!.schedule);
const unavailable = useAppSelector((state) => !!state.compose.get(composeId)!.id);
const compose = useCompose(composeId);
const active = compose.schedule;
const unavailable = compose.id;
const handleClick = () => {
if (active) {

Wyświetl plik

@ -9,7 +9,7 @@ import IconButton from 'soapbox/components/icon_button';
import { HStack, Stack, Text } from 'soapbox/components/ui';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { DatePicker } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
const isCurrentOrFutureDate = (date: Date) => {
return date && new Date().setHours(0, 0, 0, 0) <= new Date(date).setHours(0, 0, 0, 0);
@ -35,7 +35,7 @@ const ScheduleForm: React.FC<IScheduleForm> = ({ composeId }) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const scheduledAt = useAppSelector((state) => state.compose.get(composeId)!.schedule);
const scheduledAt = useCompose(composeId).schedule;
const active = !!scheduledAt;
const onSchedule = (date: Date) => {

Wyświetl plik

@ -3,7 +3,7 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { changeComposeSensitivity } from 'soapbox/actions/compose';
import { FormGroup, Checkbox } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
const messages = defineMessages({
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
@ -19,8 +19,10 @@ const SensitiveButton: React.FC<ISensitiveButton> = ({ composeId }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const active = useAppSelector(state => state.compose.get(composeId)!.sensitive === true);
const disabled = useAppSelector(state => state.compose.get(composeId)!.spoiler === true);
const compose = useCompose(composeId);
const active = compose.sensitive === true;
const disabled = compose.spoiler === true;
const onClick = () => {
dispatch(changeComposeSensitivity(composeId));

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { changeComposeSpoilerness } from 'soapbox/actions/compose';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import ComposeFormButton from './compose_form_button';
@ -19,7 +19,7 @@ const SpoilerButton: React.FC<ISpoilerButton> = ({ composeId }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const active = useAppSelector((state) => state.compose.get(composeId)!.spoiler);
const active = useCompose(composeId).spoiler;
const onClick = () =>
dispatch(changeComposeSpoilerness(composeId));

Wyświetl plik

@ -1,7 +1,7 @@
import React from 'react';
import UploadProgress from 'soapbox/components/upload-progress';
import { useAppSelector } from 'soapbox/hooks';
import { useCompose } from 'soapbox/hooks';
interface IComposeUploadProgress {
composeId: string,
@ -9,8 +9,10 @@ interface IComposeUploadProgress {
/** File upload progress bar for post composer. */
const ComposeUploadProgress: React.FC<IComposeUploadProgress> = ({ composeId }) => {
const active = useAppSelector((state) => state.compose.get(composeId)!.is_uploading);
const progress = useAppSelector((state) => state.compose.get(composeId)!.progress);
const compose = useCompose(composeId);
const active = compose.is_uploading;
const progress = compose.progress;
if (!active) {
return null;

Wyświetl plik

@ -10,7 +10,7 @@ import { openModal } from 'soapbox/actions/modals';
import Blurhash from 'soapbox/components/blurhash';
import Icon from 'soapbox/components/icon';
import IconButton from 'soapbox/components/icon_button';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useCompose } from 'soapbox/hooks';
import Motion from '../../ui/util/optional_motion';
@ -71,7 +71,7 @@ const Upload: React.FC<IUpload> = ({ composeId, id }) => {
const history = useHistory();
const dispatch = useAppDispatch();
const media = useAppSelector((state) => state.compose.get(composeId)!.media_attachments.find(item => item.get('id') === id)!);
const media = useCompose(composeId).media_attachments.find(item => item.get('id') === id)!;
const descriptionLimit = useAppSelector((state) => state.instance.get('description_limit'));
const [hovered, setHovered] = useState(false);

Wyświetl plik

@ -1,7 +1,7 @@
import classNames from 'clsx';
import React from 'react';
import { useAppSelector } from 'soapbox/hooks';
import { useCompose } from 'soapbox/hooks';
import SensitiveButton from './sensitive-button';
import Upload from './upload';
@ -14,7 +14,7 @@ interface IUploadForm {
}
const UploadForm: React.FC<IUploadForm> = ({ composeId }) => {
const mediaIds = useAppSelector((state) => state.compose.get(composeId)!.media_attachments.map((item: AttachmentEntity) => item.id));
const mediaIds = useCompose(composeId).media_attachments.map((item: AttachmentEntity) => item.id);
const classes = classNames('compose-form__uploads-wrapper', {
'contains-media': mediaIds.size !== 0,
});

Wyświetl plik

@ -14,7 +14,7 @@ interface IQuotedStatusContainer {
/** QuotedStatus shown in post composer. */
const QuotedStatusContainer: React.FC<IQuotedStatusContainer> = ({ composeId }) => {
const dispatch = useAppDispatch();
const status = useAppSelector(state => getStatus(state, { id: state.compose.get(composeId)!.quote! }));
const status = useAppSelector(state => getStatus(state, { id: state.compose.get(composeId)?.quote! }));
const onCancel = () => {
dispatch(cancelQuoteCompose());

Wyświetl plik

@ -12,8 +12,8 @@ const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = (state: RootState, { composeId }: { composeId: string }) => {
const statusId = state.compose.get(composeId)!.in_reply_to!;
const editing = !!state.compose.get(composeId)!.id;
const statusId = state.compose.get(composeId)?.in_reply_to!;
const editing = !!state.compose.get(composeId)?.id;
return {
status: getStatus(state, { id: statusId }) as Status,

Wyświetl plik

@ -8,8 +8,8 @@ import type { IntlShape } from 'react-intl';
import type { AppDispatch, RootState } from 'soapbox/store';
const mapStateToProps = (state: RootState, { composeId }: { composeId: string }) => ({
disabled: state.compose.get(composeId)!.is_uploading,
resetFileKey: state.compose.get(composeId)!.resetFileKey,
disabled: state.compose.get(composeId)?.is_uploading,
resetFileKey: state.compose.get(composeId)?.resetFileKey,
});
const mapDispatchToProps = (dispatch: AppDispatch, { composeId }: { composeId: string }) => ({

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { useAppSelector } from 'soapbox/hooks';
import { useAppSelector, useCompose } from 'soapbox/hooks';
import Warning from '../components/warning';
@ -13,11 +13,13 @@ interface IWarningWrapper {
}
const WarningWrapper: React.FC<IWarningWrapper> = ({ composeId }) => {
const compose = useCompose(composeId);
const me = useAppSelector((state) => state.me);
const needsLockWarning = useAppSelector(state => state.compose.get(composeId)!.privacy === 'private' && !state.accounts.get(me)!.locked);
const hashtagWarning = useAppSelector(state => state.compose.get(composeId)!.privacy !== 'public' && APPROX_HASHTAG_RE.test(state.compose.get(composeId)!.text));
const directMessageWarning = useAppSelector(state => state.compose.get(composeId)!.privacy === 'direct');
const needsLockWarning = useAppSelector((state) => compose.privacy === 'private' && !state.accounts.get(me)!.locked);
const hashtagWarning = compose.privacy !== 'public' && APPROX_HASHTAG_RE.test(compose.text);
const directMessageWarning = compose.privacy === 'direct';
if (needsLockWarning) {
return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <Link to='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></Link> }} />} />;

Wyświetl plik

@ -6,7 +6,7 @@ import { addToMentions, removeFromMentions } from 'soapbox/actions/compose';
import Avatar from 'soapbox/components/avatar';
import DisplayName from 'soapbox/components/display-name';
import IconButton from 'soapbox/components/icon_button';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useCompose } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';
const messages = defineMessages({
@ -27,7 +27,7 @@ const Account: React.FC<IAccount> = ({ composeId, accountId, author }) => {
const dispatch = useAppDispatch();
const account = useAppSelector((state) => getAccount(state, accountId));
const added = useAppSelector((state) => !!account && state.compose.get(composeId)!.to?.includes(account.acct));
const added = !!account && useCompose(composeId).to?.includes(account.acct);
const onRemove = () => dispatch(removeFromMentions(accountId));
const onAdd = () => dispatch(addToMentions(accountId));

Wyświetl plik

@ -5,7 +5,7 @@ import { cancelReplyCompose } from 'soapbox/actions/compose';
import { openModal, closeModal } from 'soapbox/actions/modals';
import { checkComposeContent } from 'soapbox/components/modal_root';
import { Modal } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import ComposeForm from '../../compose/components/compose-form';
@ -23,7 +23,7 @@ const ComposeModal: React.FC<IComposeModal> = ({ onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const compose = useAppSelector((state) => state.compose.get('compose-modal'));
const compose = useCompose('compose-modal');
const { id: statusId, privacy, in_reply_to: inReplyTo, quote } = compose!;

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Modal } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import { useAppSelector, useCompose } from 'soapbox/hooks';
import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose';
import { makeGetStatus } from 'soapbox/selectors';
@ -16,7 +16,9 @@ interface IReplyMentionsModal {
}
const ReplyMentionsModal: React.FC<IReplyMentionsModal> = ({ composeId, onClose }) => {
const status = useAppSelector<StatusEntity | null>(state => makeGetStatus()(state, { id: state.compose.get(composeId)?.in_reply_to! }));
const compose = useCompose(composeId);
const status = useAppSelector<StatusEntity | null>(state => makeGetStatus()(state, { id: compose.in_reply_to! }));
const account = useAppSelector((state) => state.accounts.get(state.me));
const mentions = statusToMentionsAccountIdsArray(status!, account!);

Wyświetl plik

@ -2,6 +2,7 @@ export { useAccount } from './useAccount';
export { useApi } from './useApi';
export { useAppDispatch } from './useAppDispatch';
export { useAppSelector } from './useAppSelector';
export { useCompose } from './useCompose';
export { useDimensions } from './useDimensions';
export { useFeatures } from './useFeatures';
export { useLocale } from './useLocale';

Wyświetl plik

@ -0,0 +1,8 @@
import { useAppSelector } from 'soapbox/hooks';
import type { ReducerCompose } from 'soapbox/reducers/compose';
/** Get compose for given key with fallback to 'default' */
export const useCompose = <ID extends string>(composeId: ID extends 'default' ? never : ID): ReturnType<typeof ReducerCompose> => {
return useAppSelector((state) => state.compose.get(composeId, state.compose.get('default')!));
};

Wyświetl plik

@ -66,7 +66,7 @@ import type {
Tag,
} from 'soapbox/types/entities';
// const getResetFileKey = () => Math.floor((Math.random() * 0x10000));
const getResetFileKey = () => Math.floor((Math.random() * 0x10000));
const PollRecord = ImmutableRecord({
options: ImmutableList(['', '']),
@ -138,14 +138,6 @@ export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: A
.delete(account.id) as ImmutableOrderedSet<string>;
};
function clearAll(compose: Compose) {
return ReducerCompose({
content_type: compose.default_content_type,
privacy: compose.default_privacy,
idempotencyKey: uuid(),
});
}
function appendMedia(compose: Compose, media: APIEntity) {
const prevSize = compose.media_attachments.size;
@ -284,11 +276,10 @@ const updateSetting = (compose: Compose, path: string[], value: string) => {
};
const updateCompose = (state: State, key: string, updater: (compose: Compose) => Compose) =>
state.update(key, ReducerCompose(), updater);
state.update(key, state.get('default')!, updater);
const initialState: State = ImmutableMap({
default: ReducerCompose(),
home: ReducerCompose(),
default: ReducerCompose({ idempotencyKey: uuid(), resetFileKey: getResetFileKey() }),
});
export default function compose(state = initialState, action: AnyAction) {
@ -370,7 +361,7 @@ export default function compose(state = initialState, action: AnyAction) {
case COMPOSE_QUOTE_CANCEL:
case COMPOSE_RESET:
case COMPOSE_SUBMIT_SUCCESS:
return updateCompose(state, action.id, clearAll);
return state.get('default')!.set('idempotencyKey', uuid());
case COMPOSE_SUBMIT_FAIL:
return updateCompose(state, action.id, compose => compose.set('is_submitting', false));
case COMPOSE_UPLOAD_CHANGE_FAIL: