|
|
|
@ -1,18 +1,18 @@
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
import { SessionIconButton, SessionIconSize, SessionIconType } 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 _, { noop } from 'lodash';
|
|
|
|
|
import { TimerOption } from '../../conversation/ConversationHeader';
|
|
|
|
|
import { Constants } from '../../../session';
|
|
|
|
|
import {
|
|
|
|
|
ConversationAvatar,
|
|
|
|
|
usingClosedConversationDetails,
|
|
|
|
|
} from '../usingClosedConversationDetails';
|
|
|
|
|
import { save } from '../../../types/Attachment';
|
|
|
|
|
import { DefaultTheme, withTheme } from 'styled-components';
|
|
|
|
|
import { AttachmentTypeWithPath, save } from '../../../types/Attachment';
|
|
|
|
|
import { DefaultTheme, useTheme, withTheme } from 'styled-components';
|
|
|
|
|
import {
|
|
|
|
|
getMessagesWithFileAttachments,
|
|
|
|
|
getMessagesWithVisualMediaAttachments,
|
|
|
|
@ -32,345 +32,203 @@ import {
|
|
|
|
|
showUpdateGroupMembersByConvoId,
|
|
|
|
|
showUpdateGroupNameByConvoId,
|
|
|
|
|
} from '../../../interactions/conversationInteractions';
|
|
|
|
|
import { ItemClickEvent } from '../../conversation/media-gallery/types/ItemClickEvent';
|
|
|
|
|
import { MediaItemType } from '../../LightboxGallery';
|
|
|
|
|
// tslint:disable-next-line: no-submodule-imports
|
|
|
|
|
import useInterval from 'react-use/lib/useInterval';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
type Props = {
|
|
|
|
|
id: string;
|
|
|
|
|
name?: string;
|
|
|
|
|
profileName?: string;
|
|
|
|
|
phoneNumber: string;
|
|
|
|
|
memberCount: number;
|
|
|
|
|
description: string;
|
|
|
|
|
avatarPath: string;
|
|
|
|
|
avatarPath: string | null;
|
|
|
|
|
timerOptions: Array<TimerOption>;
|
|
|
|
|
isPublic: boolean;
|
|
|
|
|
isAdmin: boolean;
|
|
|
|
|
isKickedFromGroup: boolean;
|
|
|
|
|
left: boolean;
|
|
|
|
|
isBlocked: boolean;
|
|
|
|
|
isShowing: boolean;
|
|
|
|
|
isGroup: boolean;
|
|
|
|
|
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
|
|
|
|
|
|
|
|
|
|
onGoBack: () => void;
|
|
|
|
|
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void;
|
|
|
|
|
theme: DefaultTheme;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface State {
|
|
|
|
|
documents: Array<any>;
|
|
|
|
|
media: Array<any>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async function getMediaGalleryProps(
|
|
|
|
|
conversationId: string,
|
|
|
|
|
medias: Array<MediaItemType>,
|
|
|
|
|
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void
|
|
|
|
|
): Promise<{
|
|
|
|
|
documents: Array<MediaItemType>;
|
|
|
|
|
media: Array<MediaItemType>;
|
|
|
|
|
onItemClick: any;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SessionRightPanel extends React.Component<Props, State> {
|
|
|
|
|
public constructor(props: Props) {
|
|
|
|
|
super(props);
|
|
|
|
|
}> {
|
|
|
|
|
// We fetch more documents than media as they don’t 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;
|
|
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
documents: Array<any>(),
|
|
|
|
|
media: Array<any>(),
|
|
|
|
|
onItemClick: undefined,
|
|
|
|
|
return {
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index: 0,
|
|
|
|
|
attachment,
|
|
|
|
|
messageTimestamp: timestamp || serverTimestamp || received_at || 0,
|
|
|
|
|
messageSender: source,
|
|
|
|
|
messageId: id,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public componentWillMount() {
|
|
|
|
|
void this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
documents,
|
|
|
|
|
media,
|
|
|
|
|
onItemClick,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const saveAttachment = async ({
|
|
|
|
|
attachment,
|
|
|
|
|
messageTimestamp,
|
|
|
|
|
messageSender,
|
|
|
|
|
}: {
|
|
|
|
|
attachment: AttachmentTypeWithPath;
|
|
|
|
|
messageTimestamp: number;
|
|
|
|
|
messageSender: string;
|
|
|
|
|
}) => {
|
|
|
|
|
const timestamp = messageTimestamp;
|
|
|
|
|
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
|
|
|
|
|
save({
|
|
|
|
|
attachment,
|
|
|
|
|
document,
|
|
|
|
|
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
|
|
|
|
|
timestamp,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
await sendDataExtractionNotification(conversationId, messageSender, timestamp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public componentDidUpdate() {
|
|
|
|
|
const mediaScanInterval = 1000;
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
void this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => {
|
|
|
|
|
const { documents: oldDocs, media: oldMedias } = this.state;
|
|
|
|
|
if (oldDocs.length !== documents.length || oldMedias.length !== media.length) {
|
|
|
|
|
this.setState({
|
|
|
|
|
documents,
|
|
|
|
|
media,
|
|
|
|
|
onItemClick,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}, mediaScanInterval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async getMediaGalleryProps(): Promise<{
|
|
|
|
|
documents: Array<any>;
|
|
|
|
|
media: Array<any>;
|
|
|
|
|
onItemClick: any;
|
|
|
|
|
}> {
|
|
|
|
|
// We fetch more documents than media as they don’t require to be loaded
|
|
|
|
|
// into memory right away. Revisit this once we have infinite scrolling:
|
|
|
|
|
const conversationId = this.props.id;
|
|
|
|
|
const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, {
|
|
|
|
|
limit: Constants.CONVERSATION.DEFAULT_MEDIA_FETCH_COUNT,
|
|
|
|
|
});
|
|
|
|
|
const rawDocuments = await getMessagesWithFileAttachments(conversationId, {
|
|
|
|
|
limit: Constants.CONVERSATION.DEFAULT_DOCUMENTS_FETCH_COUNT,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// First we upgrade these messages to ensure that they have thumbnails
|
|
|
|
|
const max = rawMedia.length;
|
|
|
|
|
for (let i = 0; i < max; i += 1) {
|
|
|
|
|
const message = rawMedia[i];
|
|
|
|
|
const { schemaVersion } = message;
|
|
|
|
|
|
|
|
|
|
if (schemaVersion < message.VERSION_NEEDED_FOR_DISPLAY) {
|
|
|
|
|
// Yep, we really do want to wait for each of these
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
|
rawMedia[i] = await window.Signal.Migrations.upgradeMessageSchema(message);
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
|
await rawMedia[i].commit();
|
|
|
|
|
}
|
|
|
|
|
const onItemClick = (event: ItemClickEvent) => {
|
|
|
|
|
if (!event) {
|
|
|
|
|
console.warn('no event');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const media = _.flatten(
|
|
|
|
|
rawMedia.map((message: { attachments: any }) => {
|
|
|
|
|
const { attachments } = message;
|
|
|
|
|
|
|
|
|
|
return (attachments || [])
|
|
|
|
|
.filter(
|
|
|
|
|
(attachment: { thumbnail: any; pending: any; error: any }) =>
|
|
|
|
|
attachment.thumbnail && !attachment.pending && !attachment.error
|
|
|
|
|
)
|
|
|
|
|
.map((attachment: { path?: any; contentType?: any; thumbnail?: any }, index: any) => {
|
|
|
|
|
const { thumbnail } = attachment;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(attachment.path),
|
|
|
|
|
thumbnailObjectUrl: thumbnail
|
|
|
|
|
? window.Signal.Migrations.getAbsoluteAttachmentPath(thumbnail.path)
|
|
|
|
|
: null,
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index,
|
|
|
|
|
attachment,
|
|
|
|
|
message,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Unlike visual media, only one non-image attachment is supported
|
|
|
|
|
const documents = rawDocuments.map((message: { attachments: Array<any> }) => {
|
|
|
|
|
// this is to not fail if the attachment is invalid (could be a Long Attachment type which is not supported)
|
|
|
|
|
if (!message.attachments?.length) {
|
|
|
|
|
// window?.log?.info(
|
|
|
|
|
// 'Got a message with an empty list of attachment. Skipping...'
|
|
|
|
|
// );
|
|
|
|
|
return null;
|
|
|
|
|
const { mediaItem, type } = event;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'documents': {
|
|
|
|
|
void saveAttachment({
|
|
|
|
|
messageSender: mediaItem.messageSender,
|
|
|
|
|
messageTimestamp: mediaItem.messageTimestamp,
|
|
|
|
|
attachment: mediaItem.attachment,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const attachment = message.attachments[0];
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index: 0,
|
|
|
|
|
attachment,
|
|
|
|
|
message,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const saveAttachment = async ({ attachment, message }: any = {}) => {
|
|
|
|
|
const timestamp = message.received_at as number | undefined;
|
|
|
|
|
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
|
|
|
|
|
save({
|
|
|
|
|
attachment,
|
|
|
|
|
document,
|
|
|
|
|
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
|
|
|
|
|
timestamp,
|
|
|
|
|
});
|
|
|
|
|
await sendDataExtractionNotification(this.props.id, message?.source, timestamp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onItemClick = ({ message, attachment, type }: any) => {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 'documents': {
|
|
|
|
|
void saveAttachment({ message, attachment });
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'media': {
|
|
|
|
|
// don't set the messageTimestamp when we are the sender, so we don't trigger a notification when
|
|
|
|
|
// we save the same attachment we sent ourself to another user
|
|
|
|
|
const messageTimestamp =
|
|
|
|
|
message.source !== UserUtils.getOurPubKeyStrFromCache()
|
|
|
|
|
? message.sent_at || message.received_at
|
|
|
|
|
: undefined;
|
|
|
|
|
const lightBoxOptions = {
|
|
|
|
|
media,
|
|
|
|
|
attachment,
|
|
|
|
|
messageTimestamp,
|
|
|
|
|
} as LightBoxOptions;
|
|
|
|
|
this.onShowLightBox(lightBoxOptions);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'media': {
|
|
|
|
|
const lightBoxOptions: LightBoxOptions = {
|
|
|
|
|
media: medias,
|
|
|
|
|
attachment: mediaItem.attachment,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new TypeError(`Unknown attachment type: '${type}'`);
|
|
|
|
|
onShowLightBox(lightBoxOptions);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
media,
|
|
|
|
|
documents: _.compact(documents), // remove null
|
|
|
|
|
onItemClick,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public onShowLightBox(lightboxOptions: LightBoxOptions) {
|
|
|
|
|
this.props.onShowLightBox(lightboxOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tslint:disable-next-line: cyclomatic-complexity
|
|
|
|
|
public render() {
|
|
|
|
|
const {
|
|
|
|
|
id,
|
|
|
|
|
memberCount,
|
|
|
|
|
name,
|
|
|
|
|
timerOptions,
|
|
|
|
|
isKickedFromGroup,
|
|
|
|
|
left,
|
|
|
|
|
isPublic,
|
|
|
|
|
isAdmin,
|
|
|
|
|
isBlocked,
|
|
|
|
|
isGroup,
|
|
|
|
|
} = this.props;
|
|
|
|
|
const { documents, media, onItemClick } = this.state;
|
|
|
|
|
const showMemberCount = !!(memberCount && memberCount > 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 disappearingMessagesOptions = timerOptions.map(option => {
|
|
|
|
|
return {
|
|
|
|
|
content: option.name,
|
|
|
|
|
onClick: async () => {
|
|
|
|
|
await setDisappearingMessagesByConvoId(id, option.value);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const showUpdateGroupNameButton = isAdmin && !commonNoShow;
|
|
|
|
|
const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic;
|
|
|
|
|
default:
|
|
|
|
|
throw new TypeError(`Unknown attachment type: '${type}'`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow;
|
|
|
|
|
return {
|
|
|
|
|
media,
|
|
|
|
|
documents: _.compact(documents), // remove null
|
|
|
|
|
onItemClick,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteConvoAction = isPublic
|
|
|
|
|
? () => {
|
|
|
|
|
deleteMessagesByConvoIdWithConfirmation(id);
|
|
|
|
|
// tslint:disable: cyclomatic-complexity
|
|
|
|
|
// tslint:disable: max-func-body-length
|
|
|
|
|
export const SessionRightPanelWithDetails = (props: Props) => {
|
|
|
|
|
const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
|
|
|
|
|
const [media, setMedia] = useState<Array<MediaItemType>>([]);
|
|
|
|
|
const [onItemClick, setOnItemClick] = useState<any>(undefined);
|
|
|
|
|
const theme = useTheme();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let isRunning = true;
|
|
|
|
|
|
|
|
|
|
if (props.isShowing) {
|
|
|
|
|
void getMediaGalleryProps(props.id, media, props.onShowLightBox).then(results => {
|
|
|
|
|
console.warn('results2', results);
|
|
|
|
|
|
|
|
|
|
if (isRunning) {
|
|
|
|
|
setDocuments(results.documents);
|
|
|
|
|
setMedia(results.media);
|
|
|
|
|
setOnItemClick(results.onItemClick);
|
|
|
|
|
}
|
|
|
|
|
: () => {
|
|
|
|
|
showLeaveGroupByConvoId(id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="group-settings">
|
|
|
|
|
{this.renderHeader()}
|
|
|
|
|
<h2>{name}</h2>
|
|
|
|
|
{showMemberCount && (
|
|
|
|
|
<>
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
<div role="button" className="subtle">
|
|
|
|
|
{window.i18n('members', memberCount)}
|
|
|
|
|
</div>
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
<input className="description" placeholder={window.i18n('description')} />
|
|
|
|
|
{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} onItemClick={onItemClick} />
|
|
|
|
|
{isGroup && (
|
|
|
|
|
// tslint:disable-next-line: use-simple-attributes
|
|
|
|
|
<SessionButton
|
|
|
|
|
text={leaveGroupString}
|
|
|
|
|
buttonColor={SessionButtonColor.Danger}
|
|
|
|
|
disabled={isKickedFromGroup || left}
|
|
|
|
|
buttonType={SessionButtonType.SquareOutline}
|
|
|
|
|
onClick={deleteConvoAction}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return () => {
|
|
|
|
|
isRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
}, [props.isShowing, props.id]);
|
|
|
|
|
|
|
|
|
|
useInterval(async () => {
|
|
|
|
|
if (props.isShowing) {
|
|
|
|
|
const results = await getMediaGalleryProps(props.id, media, props.onShowLightBox);
|
|
|
|
|
console.warn('results', results);
|
|
|
|
|
if (results.documents.length !== documents.length || results.media.length !== media.length) {
|
|
|
|
|
setDocuments(results.documents);
|
|
|
|
|
setMedia(results.media);
|
|
|
|
|
setOnItemClick(results.onItemClick);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 10000);
|
|
|
|
|
|
|
|
|
|
private renderHeader() {
|
|
|
|
|
const {
|
|
|
|
|
memberAvatars,
|
|
|
|
|
id,
|
|
|
|
|
onGoBack,
|
|
|
|
|
avatarPath,
|
|
|
|
|
isAdmin,
|
|
|
|
|
isPublic,
|
|
|
|
|
isKickedFromGroup,
|
|
|
|
|
isBlocked,
|
|
|
|
|
name,
|
|
|
|
|
profileName,
|
|
|
|
|
phoneNumber,
|
|
|
|
|
left,
|
|
|
|
|
} = this.props;
|
|
|
|
|
function renderHeader() {
|
|
|
|
|
const { memberAvatars, onGoBack, avatarPath, profileName, phoneNumber } = props;
|
|
|
|
|
|
|
|
|
|
const showInviteContacts = (isPublic || isAdmin) && !isKickedFromGroup && !isBlocked && !left;
|
|
|
|
|
const userName = name || profileName || phoneNumber;
|
|
|
|
@ -382,10 +240,10 @@ class SessionRightPanel extends React.Component<Props, State> {
|
|
|
|
|
iconSize={SessionIconSize.Medium}
|
|
|
|
|
iconRotation={270}
|
|
|
|
|
onClick={onGoBack}
|
|
|
|
|
theme={this.props.theme}
|
|
|
|
|
theme={theme}
|
|
|
|
|
/>
|
|
|
|
|
<Avatar
|
|
|
|
|
avatarPath={avatarPath}
|
|
|
|
|
avatarPath={avatarPath || ''}
|
|
|
|
|
name={userName}
|
|
|
|
|
size={AvatarSize.XL}
|
|
|
|
|
memberAvatars={memberAvatars}
|
|
|
|
@ -397,17 +255,139 @@ class SessionRightPanel extends React.Component<Props, State> {
|
|
|
|
|
iconType={SessionIconType.AddUser}
|
|
|
|
|
iconSize={SessionIconSize.Medium}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
showInviteContactByConvoId(this.props.id);
|
|
|
|
|
showInviteContactByConvoId(props.id);
|
|
|
|
|
}}
|
|
|
|
|
theme={this.props.theme}
|
|
|
|
|
theme={theme}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const SessionRightPanelWithDetails = usingClosedConversationDetails(
|
|
|
|
|
withTheme(SessionRightPanel)
|
|
|
|
|
);
|
|
|
|
|
const {
|
|
|
|
|
id,
|
|
|
|
|
memberCount,
|
|
|
|
|
name,
|
|
|
|
|
timerOptions,
|
|
|
|
|
isKickedFromGroup,
|
|
|
|
|
left,
|
|
|
|
|
isPublic,
|
|
|
|
|
isAdmin,
|
|
|
|
|
isBlocked,
|
|
|
|
|
isGroup,
|
|
|
|
|
} = props;
|
|
|
|
|
const showMemberCount = !!(memberCount && memberCount > 0);
|
|
|
|
|
const commonNoShow = isKickedFromGroup || left || isBlocked;
|
|
|
|
|
console.warn('AUDRIC: render right panel');
|
|
|
|
|
const hasDisappearingMessages = !isPublic && !commonNoShow;
|
|
|
|
|
const leaveGroupString = isPublic
|
|
|
|
|
? window.i18n('leaveGroup')
|
|
|
|
|
: isKickedFromGroup
|
|
|
|
|
? window.i18n('youGotKickedFromGroup')
|
|
|
|
|
: left
|
|
|
|
|
? window.i18n('youLeftTheGroup')
|
|
|
|
|
: window.i18n('leaveGroup');
|
|
|
|
|
|
|
|
|
|
const disappearingMessagesOptions = timerOptions.map(option => {
|
|
|
|
|
return {
|
|
|
|
|
content: option.name,
|
|
|
|
|
onClick: async () => {
|
|
|
|
|
await setDisappearingMessagesByConvoId(id, option.value);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const showUpdateGroupNameButton = isAdmin && !commonNoShow;
|
|
|
|
|
const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic;
|
|
|
|
|
|
|
|
|
|
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow;
|
|
|
|
|
|
|
|
|
|
const deleteConvoAction = isPublic
|
|
|
|
|
? () => {
|
|
|
|
|
deleteMessagesByConvoIdWithConfirmation(id);
|
|
|
|
|
}
|
|
|
|
|
: () => {
|
|
|
|
|
showLeaveGroupByConvoId(id);
|
|
|
|
|
};
|
|
|
|
|
console.warn('onItemClick', onItemClick);
|
|
|
|
|
return (
|
|
|
|
|
<div className="group-settings">
|
|
|
|
|
{renderHeader()}
|
|
|
|
|
<h2>{name}</h2>
|
|
|
|
|
{showMemberCount && (
|
|
|
|
|
<>
|
|
|
|
|
<SpacerLG />
|
|
|
|
|
<div role="button" className="subtle">
|
|
|
|
|
{window.i18n('members', memberCount)}
|
|
|
|
|
</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} onItemClick={onItemClick} />
|
|
|
|
|
{isGroup && (
|
|
|
|
|
// tslint:disable-next-line: use-simple-attributes
|
|
|
|
|
<SessionButton
|
|
|
|
|
text={leaveGroupString}
|
|
|
|
|
buttonColor={SessionButtonColor.Danger}
|
|
|
|
|
disabled={isKickedFromGroup || left}
|
|
|
|
|
buttonType={SessionButtonType.SquareOutline}
|
|
|
|
|
onClick={deleteConvoAction}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|