signal-desktop/ts/components/StoryCreator.tsx

270 lines
8.2 KiB
TypeScript
Raw Normal View History

2022-06-16 20:48:57 -04:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
2022-08-04 15:23:24 -04:00
import React, { useEffect, useState } from 'react';
2022-06-16 20:48:57 -04:00
import { get, has } from 'lodash';
2022-08-04 15:23:24 -04:00
import type {
AttachmentType,
InMemoryAttachmentDraftType,
} from '../types/Attachment';
import type { LinkPreviewSourceType } from '../types/LinkPreview';
2022-06-16 20:48:57 -04:00
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { LocalizerType } from '../types/Util';
2022-08-04 15:23:24 -04:00
import type { Props as StickerButtonProps } from './stickers/StickerButton';
2022-08-23 13:24:55 -04:00
import type { PropsType as SendStoryModalPropsType } from './SendStoryModal';
2022-08-02 15:31:55 -04:00
import type { UUIDStringType } from '../types/UUID';
2022-11-16 13:41:38 -08:00
import type { imageToBlurHash } from '../util/imageToBlurHash';
import type { PropsType as TextStoryCreatorPropsType } from './TextStoryCreator';
2022-06-16 20:48:57 -04:00
2022-11-16 13:41:38 -08:00
import { TEXT_ATTACHMENT } from '../types/MIME';
2022-08-04 15:23:24 -04:00
import { isVideoAttachment } from '../types/Attachment';
2022-08-02 15:31:55 -04:00
import { SendStoryModal } from './SendStoryModal';
2022-08-04 15:23:24 -04:00
import { MediaEditor } from './MediaEditor';
import { TextStoryCreator } from './TextStoryCreator';
2022-10-04 17:17:15 -06:00
import type { SmartCompositionTextAreaProps } from '../state/smart/CompositionTextArea';
import type { DraftBodyRanges } from '../types/BodyRange';
2022-06-16 20:48:57 -04:00
export type PropsType = {
debouncedMaybeGrabLinkPreview: (
message: string,
source: LinkPreviewSourceType
) => unknown;
2022-08-04 15:23:24 -04:00
file?: File;
2022-06-16 20:48:57 -04:00
i18n: LocalizerType;
isSending: boolean;
2022-06-16 20:48:57 -04:00
linkPreview?: LinkPreviewType;
onClose: () => unknown;
2022-08-02 15:31:55 -04:00
onSend: (
listIds: Array<UUIDStringType>,
2022-08-08 23:26:21 -04:00
conversationIds: Array<string>,
attachment: AttachmentType,
bodyRanges: DraftBodyRanges | undefined
2022-08-02 15:31:55 -04:00
) => unknown;
2022-11-16 13:41:38 -08:00
imageToBlurHash: typeof imageToBlurHash;
2022-08-04 15:23:24 -04:00
processAttachment: (
file: File
) => Promise<void | InMemoryAttachmentDraftType>;
2022-10-04 17:17:15 -06:00
renderCompositionTextArea: (
props: SmartCompositionTextAreaProps
) => JSX.Element;
sendStoryModalOpenStateChanged: (isOpen: boolean) => unknown;
2022-08-23 13:24:55 -04:00
} & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
Pick<
SendStoryModalPropsType,
| 'candidateConversations'
| 'distributionLists'
| 'getPreferredBadge'
| 'groupConversations'
| 'groupStories'
| 'hasFirstStoryPostExperience'
| 'me'
| 'ourConversationId'
2022-08-30 15:13:32 -04:00
| 'onDeleteList'
2022-08-23 13:24:55 -04:00
| 'onDistributionListCreated'
| 'onHideMyStoriesFrom'
| 'onRemoveMembers'
2022-08-30 15:13:32 -04:00
| 'onRepliesNReactionsChanged'
| 'onSelectedStoryList'
2022-08-23 13:24:55 -04:00
| 'onViewersUpdated'
| 'setMyStoriesToAllSignalConnections'
| 'signalConnections'
2022-08-30 15:13:32 -04:00
| 'toggleGroupsForStorySend'
| 'mostRecentActiveStoryTimestampByGroupOrDistributionList'
2022-08-23 13:24:55 -04:00
| 'toggleSignalConnectionsModal'
2023-02-24 16:18:57 -07:00
| 'onMediaPlaybackStart'
> &
Pick<
TextStoryCreatorPropsType,
'onUseEmoji' | 'skinTone' | 'onSetSkinTone' | 'recentEmojis'
2022-08-23 13:24:55 -04:00
>;
2022-06-16 20:48:57 -04:00
2022-11-17 16:45:19 -08:00
export function StoryCreator({
2022-08-10 14:37:19 -04:00
candidateConversations,
2022-06-16 20:48:57 -04:00
debouncedMaybeGrabLinkPreview,
2022-08-02 15:31:55 -04:00
distributionLists,
2022-08-04 15:23:24 -04:00
file,
2022-08-10 14:37:19 -04:00
getPreferredBadge,
groupConversations,
groupStories,
2022-08-23 13:24:55 -04:00
hasFirstStoryPostExperience,
2022-06-16 20:48:57 -04:00
i18n,
2022-11-16 13:41:38 -08:00
imageToBlurHash,
2022-08-04 15:23:24 -04:00
installedPacks,
isSending,
2022-06-16 20:48:57 -04:00
linkPreview,
2022-08-02 15:31:55 -04:00
me,
mostRecentActiveStoryTimestampByGroupOrDistributionList,
2022-06-16 20:48:57 -04:00
onClose,
2022-08-30 15:13:32 -04:00
onDeleteList,
2022-08-10 14:37:19 -04:00
onDistributionListCreated,
2022-08-23 13:24:55 -04:00
onHideMyStoriesFrom,
onRemoveMembers,
2022-08-30 15:13:32 -04:00
onRepliesNReactionsChanged,
onSelectedStoryList,
2022-08-02 15:31:55 -04:00
onSend,
onSetSkinTone,
onUseEmoji,
2022-08-23 13:24:55 -04:00
onViewersUpdated,
2023-02-24 16:18:57 -07:00
onMediaPlaybackStart,
ourConversationId,
2022-08-04 15:23:24 -04:00
processAttachment,
recentEmojis,
2022-08-04 15:23:24 -04:00
recentStickers,
2022-10-04 17:17:15 -06:00
renderCompositionTextArea,
sendStoryModalOpenStateChanged,
2022-08-23 13:24:55 -04:00
setMyStoriesToAllSignalConnections,
2022-08-02 15:31:55 -04:00
signalConnections,
skinTone,
2022-08-30 15:13:32 -04:00
toggleGroupsForStorySend,
2022-08-23 13:24:55 -04:00
toggleSignalConnectionsModal,
2022-11-17 16:45:19 -08:00
}: PropsType): JSX.Element {
2022-08-04 15:23:24 -04:00
const [draftAttachment, setDraftAttachment] = useState<
AttachmentType | undefined
>();
2022-11-16 13:41:38 -08:00
const [isReadyToSend, setIsReadyToSend] = useState(false);
2022-08-04 15:23:24 -04:00
const [attachmentUrl, setAttachmentUrl] = useState<string | undefined>();
const [bodyRanges, setBodyRanges] = useState<DraftBodyRanges | undefined>();
2022-06-16 20:48:57 -04:00
useEffect(() => {
2022-08-04 15:23:24 -04:00
let url: string | undefined;
let unmounted = false;
2022-06-16 20:48:57 -04:00
2022-08-04 15:23:24 -04:00
async function loadAttachment(): Promise<void> {
if (!file || unmounted) {
return;
}
2022-06-16 20:48:57 -04:00
2022-08-04 15:23:24 -04:00
const attachment = await processAttachment(file);
if (!attachment || unmounted) {
return;
2022-06-16 20:48:57 -04:00
}
2022-08-04 15:23:24 -04:00
2022-11-16 13:41:38 -08:00
setDraftAttachment(attachment);
2022-08-04 15:23:24 -04:00
if (isVideoAttachment(attachment)) {
2022-11-16 13:41:38 -08:00
setAttachmentUrl(undefined);
setIsReadyToSend(true);
2022-08-04 15:23:24 -04:00
} else if (attachment && has(attachment, 'data')) {
url = URL.createObjectURL(new Blob([get(attachment, 'data')]));
setAttachmentUrl(url);
2022-11-16 13:41:38 -08:00
// Needs editing in MediaEditor
setIsReadyToSend(false);
2022-06-16 20:48:57 -04:00
}
2022-08-04 15:23:24 -04:00
}
2022-06-16 20:48:57 -04:00
void loadAttachment();
2022-06-16 20:48:57 -04:00
return () => {
2022-08-04 15:23:24 -04:00
unmounted = true;
if (url) {
URL.revokeObjectURL(url);
}
2022-06-16 20:48:57 -04:00
};
2022-08-04 15:23:24 -04:00
}, [file, processAttachment]);
2022-08-02 15:31:55 -04:00
useEffect(() => {
2022-11-16 13:41:38 -08:00
if (draftAttachment === undefined) {
sendStoryModalOpenStateChanged(false);
setIsReadyToSend(false);
} else {
sendStoryModalOpenStateChanged(true);
}
}, [draftAttachment, sendStoryModalOpenStateChanged]);
2022-06-16 20:48:57 -04:00
return (
2022-08-02 15:31:55 -04:00
<>
2022-11-16 13:41:38 -08:00
{draftAttachment && isReadyToSend && (
2022-08-02 15:31:55 -04:00
<SendStoryModal
draftAttachment={draftAttachment}
2022-08-10 14:37:19 -04:00
candidateConversations={candidateConversations}
2022-08-02 15:31:55 -04:00
distributionLists={distributionLists}
2022-08-10 14:37:19 -04:00
getPreferredBadge={getPreferredBadge}
groupConversations={groupConversations}
groupStories={groupStories}
2022-08-23 13:24:55 -04:00
hasFirstStoryPostExperience={hasFirstStoryPostExperience}
ourConversationId={ourConversationId}
2022-08-02 15:31:55 -04:00
i18n={i18n}
me={me}
2022-08-04 15:23:24 -04:00
onClose={() => setDraftAttachment(undefined)}
2022-08-30 15:13:32 -04:00
onDeleteList={onDeleteList}
2022-08-10 14:37:19 -04:00
onDistributionListCreated={onDistributionListCreated}
2022-08-23 13:24:55 -04:00
onHideMyStoriesFrom={onHideMyStoriesFrom}
onRemoveMembers={onRemoveMembers}
2022-08-30 15:13:32 -04:00
onRepliesNReactionsChanged={onRepliesNReactionsChanged}
onSelectedStoryList={onSelectedStoryList}
2022-08-10 14:37:19 -04:00
onSend={(listIds, groupIds) => {
onSend(listIds, groupIds, draftAttachment, bodyRanges);
2022-08-04 15:23:24 -04:00
setDraftAttachment(undefined);
2022-08-02 15:31:55 -04:00
}}
2022-08-23 13:24:55 -04:00
onViewersUpdated={onViewersUpdated}
2023-02-24 16:18:57 -07:00
onMediaPlaybackStart={onMediaPlaybackStart}
2022-08-23 13:24:55 -04:00
setMyStoriesToAllSignalConnections={
setMyStoriesToAllSignalConnections
}
2022-08-02 15:31:55 -04:00
signalConnections={signalConnections}
2022-08-30 15:13:32 -04:00
toggleGroupsForStorySend={toggleGroupsForStorySend}
mostRecentActiveStoryTimestampByGroupOrDistributionList={
mostRecentActiveStoryTimestampByGroupOrDistributionList
}
2022-08-23 13:24:55 -04:00
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
2022-08-02 15:31:55 -04:00
/>
)}
2022-11-16 13:41:38 -08:00
{draftAttachment && !isReadyToSend && attachmentUrl && (
2022-08-04 15:23:24 -04:00
<MediaEditor
2023-03-29 17:03:25 -07:00
doneButtonLabel={i18n('icu:next2')}
2022-08-04 15:23:24 -04:00
i18n={i18n}
imageSrc={attachmentUrl}
installedPacks={installedPacks}
isSending={isSending}
2022-08-04 15:23:24 -04:00
onClose={onClose}
2022-10-04 17:17:15 -06:00
supportsCaption
renderCompositionTextArea={renderCompositionTextArea}
2022-11-16 13:41:38 -08:00
imageToBlurHash={imageToBlurHash}
onDone={({
contentType,
data,
blurHash,
caption,
captionBodyRanges,
}) => {
2022-08-04 15:23:24 -04:00
setDraftAttachment({
2022-11-16 13:41:38 -08:00
...draftAttachment,
contentType,
2022-08-04 15:23:24 -04:00
data,
size: data.byteLength,
2022-11-16 13:41:38 -08:00
blurHash,
2022-10-04 17:17:15 -06:00
caption,
2022-08-04 15:23:24 -04:00
});
setBodyRanges(captionBodyRanges);
2022-11-16 13:41:38 -08:00
setIsReadyToSend(true);
2022-08-04 15:23:24 -04:00
}}
recentStickers={recentStickers}
/>
)}
{!file && (
<TextStoryCreator
debouncedMaybeGrabLinkPreview={debouncedMaybeGrabLinkPreview}
i18n={i18n}
isSending={isSending}
2022-08-04 15:23:24 -04:00
linkPreview={linkPreview}
onClose={onClose}
onDone={textAttachment => {
setDraftAttachment({
contentType: TEXT_ATTACHMENT,
textAttachment,
size: textAttachment.text?.length || 0,
});
2022-11-16 13:41:38 -08:00
setIsReadyToSend(true);
2022-08-04 15:23:24 -04:00
}}
onUseEmoji={onUseEmoji}
onSetSkinTone={onSetSkinTone}
recentEmojis={recentEmojis}
skinTone={skinTone}
2022-08-04 15:23:24 -04:00
/>
)}
2022-08-02 15:31:55 -04:00
</>
2022-06-16 20:48:57 -04:00
);
2022-11-17 16:45:19 -08:00
}