session-desktop/ts/components/session/conversation/SessionRightPanel.tsx

319 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import { SessionIconButton } from '../icon';
import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionDropdown } from '../SessionDropdown';
import { MediaGallery } from '../../conversation/media-gallery/MediaGallery';
import _ from 'lodash';
import { Constants } from '../../../session';
import { AttachmentTypeWithPath } from '../../../types/Attachment';
import {
getMessagesWithFileAttachments,
getMessagesWithVisualMediaAttachments,
} from '../../../data/data';
import { SpacerLG } from '../../basic/Text';
import {
deleteAllMessagesByConvoIdWithConfirmation,
setDisappearingMessagesByConvoId,
showAddModeratorsByConvoId,
showInviteContactByConvoId,
showLeaveGroupByConvoId,
showRemoveModeratorsByConvoId,
showUpdateGroupMembersByConvoId,
showUpdateGroupNameByConvoId,
} from '../../../interactions/conversationInteractions';
import { MediaItemType } from '../../LightboxGallery';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import { getTimerOptions } from '../../../state/selectors/timerOptions';
import {
getSelectedConversation,
isRightPanelShowing,
} from '../../../state/selectors/conversations';
import { closeRightPanel } from '../../../state/ducks/conversations';
async function getMediaGalleryProps(
conversationId: string
): Promise<{
documents: Array<MediaItemType>;
media: Array<MediaItemType>;
}> {
// We fetch more documents than media as they dont require to be loaded
// into memory right away. Revisit this once we have infinite scrolling:
const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, {
limit: Constants.CONVERSATION.DEFAULT_MEDIA_FETCH_COUNT,
});
const rawDocuments = await getMessagesWithFileAttachments(conversationId, {
limit: Constants.CONVERSATION.DEFAULT_DOCUMENTS_FETCH_COUNT,
});
const media = _.flatten(
rawMedia.map(attributes => {
const { attachments, source, id, timestamp, serverTimestamp, received_at } = attributes;
return (attachments || [])
.filter(
(attachment: AttachmentTypeWithPath) =>
attachment.thumbnail && !attachment.pending && !attachment.error
)
.map((attachment: AttachmentTypeWithPath, index: number) => {
const { thumbnail } = attachment;
const mediaItem: MediaItemType = {
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(attachment.path),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(thumbnail.path)
: null,
contentType: attachment.contentType || '',
index,
messageTimestamp: timestamp || serverTimestamp || received_at || 0,
messageSender: source,
messageId: id,
attachment,
};
return mediaItem;
});
})
);
// Unlike visual media, only one non-image attachment is supported
const documents = rawDocuments.map(attributes => {
// this is to not fail if the attachment is invalid (could be a Long Attachment type which is not supported)
if (!attributes.attachments?.length) {
// window?.log?.info(
// 'Got a message with an empty list of attachment. Skipping...'
// );
return null;
}
const attachment = attributes.attachments[0];
const { source, id, timestamp, serverTimestamp, received_at } = attributes;
return {
contentType: attachment.contentType,
index: 0,
attachment,
messageTimestamp: timestamp || serverTimestamp || received_at || 0,
messageSender: source,
messageId: id,
};
});
return {
media,
documents: _.compact(documents), // remove null
};
}
const HeaderItem = () => {
const selectedConversation = useSelector(getSelectedConversation);
const dispatch = useDispatch();
if (!selectedConversation) {
return null;
}
const { id, isGroup, isKickedFromGroup, isBlocked, left } = selectedConversation;
const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked && !left;
return (
<div className="group-settings-header">
<SessionIconButton
iconType="chevron"
iconSize="medium"
iconRotation={270}
onClick={() => {
dispatch(closeRightPanel());
}}
/>
<Avatar size={AvatarSize.XL} pubkey={id} />
<div className="invite-friends-container">
{showInviteContacts && (
<SessionIconButton
iconType="addUser"
iconSize="medium"
onClick={() => {
if (selectedConversation) {
showInviteContactByConvoId(selectedConversation.id);
}
}}
/>
)}
</div>
</div>
);
};
// tslint:disable: cyclomatic-complexity
// tslint:disable: max-func-body-length
export const SessionRightPanelWithDetails = () => {
const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
const [media, setMedia] = useState<Array<MediaItemType>>([]);
const selectedConversation = useSelector(getSelectedConversation);
const isShowing = useSelector(isRightPanelShowing);
useEffect(() => {
let isRunning = true;
if (isShowing && selectedConversation) {
void getMediaGalleryProps(selectedConversation.id).then(results => {
if (isRunning) {
if (!_.isEqual(documents, results.documents)) {
setDocuments(results.documents);
}
if (!_.isEqual(media, results.media)) {
setMedia(results.media);
}
}
});
}
return () => {
isRunning = false;
return;
};
}, [isShowing, selectedConversation?.id]);
useInterval(async () => {
if (isShowing && selectedConversation) {
const results = await getMediaGalleryProps(selectedConversation.id);
if (results.documents.length !== documents.length || results.media.length !== media.length) {
setDocuments(results.documents);
setMedia(results.media);
}
}
}, 10000);
if (!selectedConversation) {
return null;
}
const {
id,
subscriberCount,
name,
isKickedFromGroup,
left,
isPublic,
weAreAdmin,
isBlocked,
isGroup,
} = selectedConversation;
const showMemberCount = !!(subscriberCount && subscriberCount > 0);
const commonNoShow = isKickedFromGroup || left || isBlocked;
const hasDisappearingMessages = !isPublic && !commonNoShow;
const leaveGroupString = isPublic
? window.i18n('leaveGroup')
: isKickedFromGroup
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
const timerOptions = useSelector(getTimerOptions).timerOptions;
const disappearingMessagesOptions = timerOptions.map(option => {
return {
content: option.name,
onClick: () => {
void setDisappearingMessagesByConvoId(id, option.value);
},
};
});
const showUpdateGroupNameButton =
isGroup && (!isPublic || (isPublic && weAreAdmin)) && !commonNoShow;
const showAddRemoveModeratorsButton = weAreAdmin && !commonNoShow && isPublic;
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow;
const deleteConvoAction = isPublic
? () => {
deleteAllMessagesByConvoIdWithConfirmation(id);
}
: () => {
showLeaveGroupByConvoId(id);
};
return (
<div className="group-settings">
<HeaderItem />
<h2>{name}</h2>
{showMemberCount && (
<>
<SpacerLG />
<div role="button" className="subtle">
{window.i18n('members', [`${subscriberCount}`])}
</div>
<SpacerLG />
</>
)}
{showUpdateGroupNameButton && (
<div
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupNameByConvoId(id);
}}
>
{isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
</div>
)}
{showAddRemoveModeratorsButton && (
<>
<div
className="group-settings-item"
role="button"
onClick={() => {
showAddModeratorsByConvoId(id);
}}
>
{window.i18n('addModerators')}
</div>
<div
className="group-settings-item"
role="button"
onClick={() => {
showRemoveModeratorsByConvoId(id);
}}
>
{window.i18n('removeModerators')}
</div>
</>
)}
{showUpdateGroupMembersButton && (
<div
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupMembersByConvoId(id);
}}
>
{window.i18n('groupMembers')}
</div>
)}
{hasDisappearingMessages && (
<SessionDropdown
label={window.i18n('disappearingMessages')}
options={disappearingMessagesOptions}
/>
)}
<MediaGallery documents={documents} media={media} />
{isGroup && (
// tslint:disable-next-line: use-simple-attributes
<SessionButton
text={leaveGroupString}
buttonColor={SessionButtonColor.Danger}
disabled={isKickedFromGroup || left}
buttonType={SessionButtonType.SquareOutline}
onClick={deleteConvoAction}
/>
)}
</div>
);
};