Keep story creator around until we've verified contacts and queued job
This commit is contained in:
parent
4fc1b6388c
commit
9fba33943a
11 changed files with 178 additions and 41 deletions
|
@ -587,6 +587,7 @@ export const CompositionArea = ({
|
||||||
<MediaEditor
|
<MediaEditor
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
imageSrc={attachmentToEdit.url}
|
imageSrc={attachmentToEdit.url}
|
||||||
|
isSending={false}
|
||||||
onClose={() => setAttachmentToEdit(undefined)}
|
onClose={() => setAttachmentToEdit(undefined)}
|
||||||
onDone={data => {
|
onDone={data => {
|
||||||
const newAttachment = {
|
const newAttachment = {
|
||||||
|
|
|
@ -27,6 +27,7 @@ const getDefaultProps = (): PropsType => ({
|
||||||
imageSrc: IMAGE_2,
|
imageSrc: IMAGE_2,
|
||||||
onClose: action('onClose'),
|
onClose: action('onClose'),
|
||||||
onDone: action('onDone'),
|
onDone: action('onDone'),
|
||||||
|
isSending: false,
|
||||||
|
|
||||||
// StickerButtonProps
|
// StickerButtonProps
|
||||||
installedPacks,
|
installedPacks,
|
||||||
|
@ -49,6 +50,10 @@ export const Portrait = (): JSX.Element => (
|
||||||
<MediaEditor {...getDefaultProps()} imageSrc={IMAGE_4} />
|
<MediaEditor {...getDefaultProps()} imageSrc={IMAGE_4} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const Sending = (): JSX.Element => (
|
||||||
|
<MediaEditor {...getDefaultProps()} isSending />
|
||||||
|
);
|
||||||
|
|
||||||
export const WithCaption = (): JSX.Element => (
|
export const WithCaption = (): JSX.Element => (
|
||||||
<MediaEditor
|
<MediaEditor
|
||||||
{...getDefaultProps()}
|
{...getDefaultProps()}
|
||||||
|
|
|
@ -39,11 +39,13 @@ import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTe
|
||||||
import { Emojify } from './conversation/Emojify';
|
import { Emojify } from './conversation/Emojify';
|
||||||
import { AddNewLines } from './conversation/AddNewLines';
|
import { AddNewLines } from './conversation/AddNewLines';
|
||||||
import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
|
import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
|
||||||
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
doneButtonLabel?: string;
|
doneButtonLabel?: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
|
isSending: boolean;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onDone: (data: Uint8Array, caption?: string | undefined) => unknown;
|
onDone: (data: Uint8Array, caption?: string | undefined) => unknown;
|
||||||
} & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
|
} & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
|
||||||
|
@ -106,6 +108,7 @@ export const MediaEditor = ({
|
||||||
doneButtonLabel,
|
doneButtonLabel,
|
||||||
i18n,
|
i18n,
|
||||||
imageSrc,
|
imageSrc,
|
||||||
|
isSending,
|
||||||
onClose,
|
onClose,
|
||||||
onDone,
|
onDone,
|
||||||
|
|
||||||
|
@ -1102,7 +1105,7 @@ export const MediaEditor = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
disabled={!image || isSaving}
|
disabled={!image || isSaving || isSending}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!fabricCanvas) {
|
if (!fabricCanvas) {
|
||||||
return;
|
return;
|
||||||
|
@ -1160,7 +1163,11 @@ export const MediaEditor = ({
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
variant={ButtonVariant.Primary}
|
variant={ButtonVariant.Primary}
|
||||||
>
|
>
|
||||||
{doneButtonLabel || i18n('save')}
|
{isSending ? (
|
||||||
|
<Spinner svgSize="small" />
|
||||||
|
) : (
|
||||||
|
doneButtonLabel || i18n('save')
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,9 +14,9 @@ import type {
|
||||||
} from '../types/Stories';
|
} from '../types/Stories';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { PropsType as SmartStoryCreatorPropsType } from '../state/smart/StoryCreator';
|
|
||||||
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
import type { ShowToastActionCreatorType } from '../state/ducks/toast';
|
||||||
import type {
|
import type {
|
||||||
|
AddStoryData,
|
||||||
ViewUserStoriesActionCreatorType,
|
ViewUserStoriesActionCreatorType,
|
||||||
ViewStoryActionCreatorType,
|
ViewStoryActionCreatorType,
|
||||||
} from '../state/ducks/stories';
|
} from '../state/ducks/stories';
|
||||||
|
@ -27,6 +27,7 @@ import { getWidthFromPreferredWidth } from '../util/leftPaneWidth';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
addStoryData: AddStoryData;
|
||||||
deleteStoryForEveryone: (story: StoryViewType) => unknown;
|
deleteStoryForEveryone: (story: StoryViewType) => unknown;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
hiddenStories: Array<ConversationStoryType>;
|
hiddenStories: Array<ConversationStoryType>;
|
||||||
|
@ -37,7 +38,8 @@ export type PropsType = {
|
||||||
onSaveStory: (story: StoryViewType) => unknown;
|
onSaveStory: (story: StoryViewType) => unknown;
|
||||||
preferredWidthFromStorage: number;
|
preferredWidthFromStorage: number;
|
||||||
queueStoryDownload: (storyId: string) => unknown;
|
queueStoryDownload: (storyId: string) => unknown;
|
||||||
renderStoryCreator: (props: SmartStoryCreatorPropsType) => JSX.Element;
|
renderStoryCreator: () => JSX.Element;
|
||||||
|
setAddStoryData: (data: AddStoryData) => unknown;
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
showStoriesSettings: () => unknown;
|
showStoriesSettings: () => unknown;
|
||||||
showToast: ShowToastActionCreatorType;
|
showToast: ShowToastActionCreatorType;
|
||||||
|
@ -51,15 +53,8 @@ export type PropsType = {
|
||||||
hasViewReceiptSetting: boolean;
|
hasViewReceiptSetting: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddStoryType =
|
|
||||||
| {
|
|
||||||
type: 'Media';
|
|
||||||
file: File;
|
|
||||||
}
|
|
||||||
| { type: 'Text' }
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
export const Stories = ({
|
export const Stories = ({
|
||||||
|
addStoryData,
|
||||||
deleteStoryForEveryone,
|
deleteStoryForEveryone,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
hiddenStories,
|
hiddenStories,
|
||||||
|
@ -71,6 +66,7 @@ export const Stories = ({
|
||||||
preferredWidthFromStorage,
|
preferredWidthFromStorage,
|
||||||
queueStoryDownload,
|
queueStoryDownload,
|
||||||
renderStoryCreator,
|
renderStoryCreator,
|
||||||
|
setAddStoryData,
|
||||||
showConversation,
|
showConversation,
|
||||||
showStoriesSettings,
|
showStoriesSettings,
|
||||||
showToast,
|
showToast,
|
||||||
|
@ -87,7 +83,6 @@ export const Stories = ({
|
||||||
requiresFullWidth: true,
|
requiresFullWidth: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [addStoryData, setAddStoryData] = useState<AddStoryType>();
|
|
||||||
const [isMyStories, setIsMyStories] = useState(false);
|
const [isMyStories, setIsMyStories] = useState(false);
|
||||||
|
|
||||||
// only handle ESC if not showing a child that handles their own ESC
|
// only handle ESC if not showing a child that handles their own ESC
|
||||||
|
@ -102,11 +97,7 @@ export const Stories = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('Stories', themeClassName(Theme.Dark))}>
|
<div className={classNames('Stories', themeClassName(Theme.Dark))}>
|
||||||
{addStoryData &&
|
{addStoryData && renderStoryCreator()}
|
||||||
renderStoryCreator({
|
|
||||||
file: addStoryData.type === 'Media' ? addStoryData.file : undefined,
|
|
||||||
onClose: () => setAddStoryData(undefined),
|
|
||||||
})}
|
|
||||||
<div className="Stories__pane" style={{ width }}>
|
<div className="Stories__pane" style={{ width }}>
|
||||||
{isMyStories && myStories.length ? (
|
{isMyStories && myStories.length ? (
|
||||||
<MyStories
|
<MyStories
|
||||||
|
|
|
@ -40,6 +40,9 @@ export default {
|
||||||
installedPacks: {
|
installedPacks: {
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
},
|
},
|
||||||
|
isSending: {
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
linkPreview: {
|
linkPreview: {
|
||||||
defaultValue: undefined,
|
defaultValue: undefined,
|
||||||
},
|
},
|
||||||
|
@ -95,3 +98,8 @@ FirstTime.args = {
|
||||||
FirstTime.story = {
|
FirstTime.story = {
|
||||||
name: 'First time posting a story',
|
name: 'First time posting a story',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Sending = Template.bind({});
|
||||||
|
Sending.args = {
|
||||||
|
isSending: true,
|
||||||
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@ export type PropsType = {
|
||||||
) => unknown;
|
) => unknown;
|
||||||
file?: File;
|
file?: File;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isSending: boolean;
|
||||||
linkPreview?: LinkPreviewType;
|
linkPreview?: LinkPreviewType;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onSend: (
|
onSend: (
|
||||||
|
@ -78,6 +79,7 @@ export const StoryCreator = ({
|
||||||
hasFirstStoryPostExperience,
|
hasFirstStoryPostExperience,
|
||||||
i18n,
|
i18n,
|
||||||
installedPacks,
|
installedPacks,
|
||||||
|
isSending,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
me,
|
me,
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -162,7 +164,6 @@ export const StoryCreator = ({
|
||||||
onSend={(listIds, groupIds) => {
|
onSend={(listIds, groupIds) => {
|
||||||
onSend(listIds, groupIds, draftAttachment);
|
onSend(listIds, groupIds, draftAttachment);
|
||||||
setDraftAttachment(undefined);
|
setDraftAttachment(undefined);
|
||||||
onClose();
|
|
||||||
}}
|
}}
|
||||||
onViewersUpdated={onViewersUpdated}
|
onViewersUpdated={onViewersUpdated}
|
||||||
setMyStoriesToAllSignalConnections={
|
setMyStoriesToAllSignalConnections={
|
||||||
|
@ -179,6 +180,7 @@ export const StoryCreator = ({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
imageSrc={attachmentUrl}
|
imageSrc={attachmentUrl}
|
||||||
installedPacks={installedPacks}
|
installedPacks={installedPacks}
|
||||||
|
isSending={isSending}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
supportsCaption
|
supportsCaption
|
||||||
renderCompositionTextArea={renderCompositionTextArea}
|
renderCompositionTextArea={renderCompositionTextArea}
|
||||||
|
@ -197,6 +199,7 @@ export const StoryCreator = ({
|
||||||
<TextStoryCreator
|
<TextStoryCreator
|
||||||
debouncedMaybeGrabLinkPreview={debouncedMaybeGrabLinkPreview}
|
debouncedMaybeGrabLinkPreview={debouncedMaybeGrabLinkPreview}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isSending={isSending}
|
||||||
linkPreview={linkPreview}
|
linkPreview={linkPreview}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onDone={textAttachment => {
|
onDone={textAttachment => {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
import { objectMap } from '../util/objectMap';
|
import { objectMap } from '../util/objectMap';
|
||||||
import { handleOutsideClick } from '../util/handleOutsideClick';
|
import { handleOutsideClick } from '../util/handleOutsideClick';
|
||||||
import { ConfirmDiscardDialog } from './ConfirmDiscardDialog';
|
import { ConfirmDiscardDialog } from './ConfirmDiscardDialog';
|
||||||
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
debouncedMaybeGrabLinkPreview: (
|
debouncedMaybeGrabLinkPreview: (
|
||||||
|
@ -37,6 +38,7 @@ export type PropsType = {
|
||||||
options?: MaybeGrabLinkPreviewOptionsType
|
options?: MaybeGrabLinkPreviewOptionsType
|
||||||
) => unknown;
|
) => unknown;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
isSending: boolean;
|
||||||
linkPreview?: LinkPreviewType;
|
linkPreview?: LinkPreviewType;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
onDone: (textAttachment: TextAttachmentType) => unknown;
|
onDone: (textAttachment: TextAttachmentType) => unknown;
|
||||||
|
@ -122,6 +124,7 @@ function getBgButtonAriaLabel(
|
||||||
export const TextStoryCreator = ({
|
export const TextStoryCreator = ({
|
||||||
debouncedMaybeGrabLinkPreview,
|
debouncedMaybeGrabLinkPreview,
|
||||||
i18n,
|
i18n,
|
||||||
|
isSending,
|
||||||
linkPreview,
|
linkPreview,
|
||||||
onClose,
|
onClose,
|
||||||
onDone,
|
onDone,
|
||||||
|
@ -566,12 +569,16 @@ export const TextStoryCreator = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
disabled={!hasChanges}
|
disabled={!hasChanges || isSending}
|
||||||
onClick={() => onDone(textAttachment)}
|
onClick={() => onDone(textAttachment)}
|
||||||
theme={Theme.Dark}
|
theme={Theme.Dark}
|
||||||
variant={ButtonVariant.Primary}
|
variant={ButtonVariant.Primary}
|
||||||
>
|
>
|
||||||
{i18n('StoryCreator__next')}
|
{isSending ? (
|
||||||
|
<Spinner svgSize="small" />
|
||||||
|
) : (
|
||||||
|
i18n('StoryCreator__next')
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
|
||||||
import { isEqual, pick } from 'lodash';
|
import { isEqual, pick } from 'lodash';
|
||||||
|
|
||||||
|
import * as Errors from '../../types/errors';
|
||||||
import type { AttachmentType } from '../../types/Attachment';
|
import type { AttachmentType } from '../../types/Attachment';
|
||||||
import type { BodyRangeType } from '../../types/Util';
|
import type { BodyRangeType } from '../../types/Util';
|
||||||
import type { ConversationModel } from '../../models/conversations';
|
import type { ConversationModel } from '../../models/conversations';
|
||||||
|
@ -87,6 +89,18 @@ export type SelectedStoryDataType = {
|
||||||
viewTarget?: StoryViewTargetType;
|
viewTarget?: StoryViewTargetType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AddStoryData =
|
||||||
|
| {
|
||||||
|
type: 'Media';
|
||||||
|
file: File;
|
||||||
|
sending?: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'Text';
|
||||||
|
sending?: boolean;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
export type StoriesStateType = {
|
export type StoriesStateType = {
|
||||||
|
@ -97,6 +111,7 @@ export type StoriesStateType = {
|
||||||
replies: Array<MessageAttributesType>;
|
replies: Array<MessageAttributesType>;
|
||||||
};
|
};
|
||||||
readonly selectedStoryData?: SelectedStoryDataType;
|
readonly selectedStoryData?: SelectedStoryDataType;
|
||||||
|
readonly addStoryData: AddStoryData;
|
||||||
readonly sendStoryModalData?: {
|
readonly sendStoryModalData?: {
|
||||||
untrustedUuids: Array<string>;
|
untrustedUuids: Array<string>;
|
||||||
verifiedUuids: Array<string>;
|
verifiedUuids: Array<string>;
|
||||||
|
@ -117,6 +132,8 @@ const STORY_CHANGED = 'stories/STORY_CHANGED';
|
||||||
const TOGGLE_VIEW = 'stories/TOGGLE_VIEW';
|
const TOGGLE_VIEW = 'stories/TOGGLE_VIEW';
|
||||||
const VIEW_STORY = 'stories/VIEW_STORY';
|
const VIEW_STORY = 'stories/VIEW_STORY';
|
||||||
const REMOVE_ALL_STORIES = 'stories/REMOVE_ALL_STORIES';
|
const REMOVE_ALL_STORIES = 'stories/REMOVE_ALL_STORIES';
|
||||||
|
const SET_ADD_STORY_DATA = 'stories/SET_ADD_STORY_DATA';
|
||||||
|
const SET_STORY_SENDING = 'stories/SET_STORY_SENDING';
|
||||||
|
|
||||||
type DOEStoryActionType = {
|
type DOEStoryActionType = {
|
||||||
type: typeof DOE_STORY;
|
type: typeof DOE_STORY;
|
||||||
|
@ -175,6 +192,16 @@ type RemoveAllStoriesActionType = {
|
||||||
type: typeof REMOVE_ALL_STORIES;
|
type: typeof REMOVE_ALL_STORIES;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SetAddStoryDataType = {
|
||||||
|
type: typeof SET_ADD_STORY_DATA;
|
||||||
|
payload: AddStoryData;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SetStorySendingType = {
|
||||||
|
type: typeof SET_STORY_SENDING;
|
||||||
|
payload: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type StoriesActionType =
|
export type StoriesActionType =
|
||||||
| DOEStoryActionType
|
| DOEStoryActionType
|
||||||
| ListMembersVerified
|
| ListMembersVerified
|
||||||
|
@ -188,7 +215,9 @@ export type StoriesActionType =
|
||||||
| StoryChangedActionType
|
| StoryChangedActionType
|
||||||
| ToggleViewActionType
|
| ToggleViewActionType
|
||||||
| ViewStoryActionType
|
| ViewStoryActionType
|
||||||
| RemoveAllStoriesActionType;
|
| RemoveAllStoriesActionType
|
||||||
|
| SetAddStoryDataType
|
||||||
|
| SetStorySendingType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
@ -451,10 +480,22 @@ function sendStoryMessage(
|
||||||
listIds: Array<UUIDStringType>,
|
listIds: Array<UUIDStringType>,
|
||||||
conversationIds: Array<string>,
|
conversationIds: Array<string>,
|
||||||
attachment: AttachmentType
|
attachment: AttachmentType
|
||||||
): ThunkAction<void, RootStateType, unknown, SendStoryModalOpenStateChanged> {
|
): ThunkAction<
|
||||||
|
void,
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
SendStoryModalOpenStateChanged | SetStorySendingType | SetAddStoryDataType
|
||||||
|
> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const { stories } = getState();
|
const { stories } = getState();
|
||||||
const { openedAtTimestamp, sendStoryModalData } = stories;
|
const { openedAtTimestamp, sendStoryModalData } = stories;
|
||||||
|
|
||||||
|
// Add spinners in the story creator
|
||||||
|
dispatch({
|
||||||
|
type: SET_STORY_SENDING,
|
||||||
|
payload: true,
|
||||||
|
});
|
||||||
|
|
||||||
assertDev(
|
assertDev(
|
||||||
openedAtTimestamp,
|
openedAtTimestamp,
|
||||||
'sendStoryMessage: openedAtTimestamp is undefined, cannot send'
|
'sendStoryMessage: openedAtTimestamp is undefined, cannot send'
|
||||||
|
@ -464,11 +505,6 @@ function sendStoryMessage(
|
||||||
'sendStoryMessage: sendStoryModalData is not defined, cannot send'
|
'sendStoryMessage: sendStoryModalData is not defined, cannot send'
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: SEND_STORY_MODAL_OPEN_STATE_CHANGED,
|
|
||||||
payload: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sendStoryModalData.untrustedUuids.length) {
|
if (sendStoryModalData.untrustedUuids.length) {
|
||||||
log.info('sendStoryMessage: SN changed for some conversations');
|
log.info('sendStoryMessage: SN changed for some conversations');
|
||||||
|
|
||||||
|
@ -491,12 +527,39 @@ function sendStoryMessage(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
log.info('sendStoryMessage: did not send');
|
log.info('sendStoryMessage: failed to verify untrusted; stopping send');
|
||||||
|
dispatch({
|
||||||
|
type: SET_STORY_SENDING,
|
||||||
|
payload: false,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all untrusted and verified uuids; we're clear to send!
|
||||||
|
dispatch({
|
||||||
|
type: SEND_STORY_MODAL_OPEN_STATE_CHANGED,
|
||||||
|
payload: undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await doSendStoryMessage(listIds, conversationIds, attachment);
|
try {
|
||||||
|
await doSendStoryMessage(listIds, conversationIds, attachment);
|
||||||
|
|
||||||
|
// Note: Only when we've successfully queued the message do we dismiss the story
|
||||||
|
// composer view.
|
||||||
|
dispatch({
|
||||||
|
type: SET_ADD_STORY_DATA,
|
||||||
|
payload: undefined,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.error('sendStoryMessage:', Errors.toLogFormat(error));
|
||||||
|
|
||||||
|
// Get rid of spinners in the story creator
|
||||||
|
dispatch({
|
||||||
|
type: SET_STORY_SENDING,
|
||||||
|
payload: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,6 +1174,20 @@ const viewStory: ViewStoryActionCreatorType = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function setAddStoryData(addStoryData: AddStoryData): SetAddStoryDataType {
|
||||||
|
return {
|
||||||
|
type: SET_ADD_STORY_DATA,
|
||||||
|
payload: addStoryData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStorySending(sending: boolean): SetStorySendingType {
|
||||||
|
return {
|
||||||
|
type: SET_STORY_SENDING,
|
||||||
|
payload: sending,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setStoriesDisabled(
|
function setStoriesDisabled(
|
||||||
value: boolean
|
value: boolean
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
|
@ -1134,7 +1211,9 @@ export const actions = {
|
||||||
verifyStoryListMembers,
|
verifyStoryListMembers,
|
||||||
viewUserStories,
|
viewUserStories,
|
||||||
viewStory,
|
viewStory,
|
||||||
|
setAddStoryData,
|
||||||
setStoriesDisabled,
|
setStoriesDisabled,
|
||||||
|
setStorySending,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStoriesActions = (): typeof actions => useBoundActions(actions);
|
export const useStoriesActions = (): typeof actions => useBoundActions(actions);
|
||||||
|
@ -1147,6 +1226,7 @@ export function getEmptyState(
|
||||||
return {
|
return {
|
||||||
lastOpenedAtTimestamp: undefined,
|
lastOpenedAtTimestamp: undefined,
|
||||||
openedAtTimestamp: undefined,
|
openedAtTimestamp: undefined,
|
||||||
|
addStoryData: undefined,
|
||||||
stories: [],
|
stories: [],
|
||||||
...overrideState,
|
...overrideState,
|
||||||
};
|
};
|
||||||
|
@ -1475,5 +1555,31 @@ export function reducer(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type === SET_ADD_STORY_DATA) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
addStoryData: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type === SET_STORY_SENDING) {
|
||||||
|
const existing = state.addStoryData;
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
log.warn(
|
||||||
|
'stories/reducer: Set story sending, but no existing addStoryData'
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
addStoryData: {
|
||||||
|
...existing,
|
||||||
|
sending: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import type {
|
||||||
SelectedStoryDataType,
|
SelectedStoryDataType,
|
||||||
StoryDataType,
|
StoryDataType,
|
||||||
StoriesStateType,
|
StoriesStateType,
|
||||||
|
AddStoryData,
|
||||||
} from '../ducks/stories';
|
} from '../ducks/stories';
|
||||||
import { HasStories, MY_STORIES_ID } from '../../types/Stories';
|
import { HasStories, MY_STORIES_ID } from '../../types/Stories';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
|
@ -58,6 +59,11 @@ export const getSelectedStoryData = createSelector(
|
||||||
selectedStoryData
|
selectedStoryData
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getAddStoryData = createSelector(
|
||||||
|
getStoriesState,
|
||||||
|
({ addStoryData }): AddStoryData => addStoryData
|
||||||
|
);
|
||||||
|
|
||||||
function getReactionUniqueId(reaction: MessageReactionType): string {
|
function getReactionUniqueId(reaction: MessageReactionType): string {
|
||||||
return `${reaction.fromId}:${reaction.targetAuthorUuid}:${reaction.timestamp}`;
|
return `${reaction.fromId}:${reaction.targetAuthorUuid}:${reaction.timestamp}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import type { PropsType as SmartStoryCreatorPropsType } from './StoryCreator';
|
|
||||||
import { SmartStoryCreator } from './StoryCreator';
|
import { SmartStoryCreator } from './StoryCreator';
|
||||||
import { Stories } from '../../components/Stories';
|
import { Stories } from '../../components/Stories';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getMe } from '../selectors/conversations';
|
||||||
|
@ -17,6 +16,7 @@ import {
|
||||||
getPreferredLeftPaneWidth,
|
getPreferredLeftPaneWidth,
|
||||||
} from '../selectors/items';
|
} from '../selectors/items';
|
||||||
import {
|
import {
|
||||||
|
getAddStoryData,
|
||||||
getSelectedStoryData,
|
getSelectedStoryData,
|
||||||
getStories,
|
getStories,
|
||||||
shouldShowStoriesView,
|
shouldShowStoriesView,
|
||||||
|
@ -27,11 +27,8 @@ import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useStoriesActions } from '../ducks/stories';
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
|
||||||
function renderStoryCreator({
|
function renderStoryCreator(): JSX.Element {
|
||||||
file,
|
return <SmartStoryCreator />;
|
||||||
onClose,
|
|
||||||
}: SmartStoryCreatorPropsType): JSX.Element {
|
|
||||||
return <SmartStoryCreator file={file} onClose={onClose} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartStories(): JSX.Element | null {
|
export function SmartStories(): JSX.Element | null {
|
||||||
|
@ -52,6 +49,7 @@ export function SmartStories(): JSX.Element | null {
|
||||||
);
|
);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
|
||||||
|
const addStoryData = useSelector(getAddStoryData);
|
||||||
const { hiddenStories, myStories, stories } = useSelector(getStories);
|
const { hiddenStories, myStories, stories } = useSelector(getStories);
|
||||||
|
|
||||||
const me = useSelector(getMe);
|
const me = useSelector(getMe);
|
||||||
|
@ -70,6 +68,7 @@ export function SmartStories(): JSX.Element | null {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stories
|
<Stories
|
||||||
|
addStoryData={addStoryData}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
hiddenStories={hiddenStories}
|
hiddenStories={hiddenStories}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
|
|
@ -31,21 +31,20 @@ import { useLinkPreviewActions } from '../ducks/linkPreviews';
|
||||||
import { useStoriesActions } from '../ducks/stories';
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
||||||
import { SmartCompositionTextArea } from './CompositionTextArea';
|
import { SmartCompositionTextArea } from './CompositionTextArea';
|
||||||
|
import { getAddStoryData } from '../selectors/stories';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
file?: File;
|
file?: File;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartStoryCreator({
|
export function SmartStoryCreator(): JSX.Element | null {
|
||||||
file,
|
|
||||||
onClose,
|
|
||||||
}: PropsType): JSX.Element | null {
|
|
||||||
const { debouncedMaybeGrabLinkPreview } = useLinkPreviewActions();
|
const { debouncedMaybeGrabLinkPreview } = useLinkPreviewActions();
|
||||||
const {
|
const {
|
||||||
sendStoryModalOpenStateChanged,
|
sendStoryModalOpenStateChanged,
|
||||||
sendStoryMessage,
|
sendStoryMessage,
|
||||||
verifyStoryListMembers,
|
verifyStoryListMembers,
|
||||||
|
setAddStoryData,
|
||||||
} = useStoriesActions();
|
} = useStoriesActions();
|
||||||
const { toggleGroupsForStorySend } = useConversationsActions();
|
const { toggleGroupsForStorySend } = useConversationsActions();
|
||||||
const {
|
const {
|
||||||
|
@ -72,6 +71,10 @@ export function SmartStoryCreator({
|
||||||
const recentStickers = useSelector(getRecentStickers);
|
const recentStickers = useSelector(getRecentStickers);
|
||||||
const signalConnections = useSelector(getAllSignalConnections);
|
const signalConnections = useSelector(getAllSignalConnections);
|
||||||
|
|
||||||
|
const addStoryData = useSelector(getAddStoryData);
|
||||||
|
const file = addStoryData?.type === 'Media' ? addStoryData.file : undefined;
|
||||||
|
const isSending = addStoryData?.sending || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoryCreator
|
<StoryCreator
|
||||||
candidateConversations={candidateConversations}
|
candidateConversations={candidateConversations}
|
||||||
|
@ -84,9 +87,10 @@ export function SmartStoryCreator({
|
||||||
hasFirstStoryPostExperience={!hasSetMyStoriesPrivacy}
|
hasFirstStoryPostExperience={!hasSetMyStoriesPrivacy}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
installedPacks={installedPacks}
|
installedPacks={installedPacks}
|
||||||
|
isSending={isSending}
|
||||||
linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)}
|
linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)}
|
||||||
me={me}
|
me={me}
|
||||||
onClose={onClose}
|
onClose={() => setAddStoryData(undefined)}
|
||||||
onDeleteList={deleteDistributionList}
|
onDeleteList={deleteDistributionList}
|
||||||
onDistributionListCreated={createDistributionList}
|
onDistributionListCreated={createDistributionList}
|
||||||
onHideMyStoriesFrom={hideMyStoriesFrom}
|
onHideMyStoriesFrom={hideMyStoriesFrom}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue