refactored right panel to hook

pull/1783/head
Audric Ackermann 4 years ago
parent 4ca5a4f093
commit 511adcf388
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -200,6 +200,7 @@
.module-message__generic-attachment__icon-container { .module-message__generic-attachment__icon-container {
position: relative; position: relative;
cursor: pointer;
} }
.module-message__generic-attachment__spinner-container { .module-message__generic-attachment__spinner-container {
padding-inline-start: 4px; padding-inline-start: 4px;

@ -32,14 +32,15 @@
height: 100%; height: 100%;
right: 0vw; right: 0vw;
transition: transform 1.5 * $session-transition-duration ease-in-out; transition: transform 0.3s ease-in-out;
transform: translateX(100%); transform: translateX(100%);
will-change: transform; will-change: transform;
width: 25vw; width: 25vw;
z-index: 1;
&.show { &.show {
transform: none; transform: none;
transition: transform $session-transition-duration ease-in-out; transition: transform 0.3s ease-in-out;
z-index: 2; z-index: 2;
} }
} }

@ -14,6 +14,8 @@ import { DefaultTheme } from 'styled-components';
import useUnmount from 'react-use/lib/useUnmount'; import useUnmount from 'react-use/lib/useUnmount';
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
import { darkTheme } from '../state/ducks/SessionTheme'; import { darkTheme } from '../state/ducks/SessionTheme';
import { useDispatch } from 'react-redux';
import { showLightBox } from '../state/ducks/conversations';
const Colors = { const Colors = {
TEXT_SECONDARY: '#bbb', TEXT_SECONDARY: '#bbb',
@ -29,7 +31,6 @@ const colorSVG = (url: string, color: string) => {
}; };
type Props = { type Props = {
close: () => void;
contentType: MIME.MIMEType | undefined; contentType: MIME.MIMEType | undefined;
objectURL: string; objectURL: string;
caption?: string; caption?: string;
@ -285,18 +286,19 @@ export const Lightbox = (props: Props) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// there is no theme in use on the lightbox // there is no theme in use on the lightbox
const theme = darkTheme; const theme = darkTheme;
const dispatch = useDispatch();
const { caption, contentType, objectURL, onNext, onPrevious, onSave } = props; const { caption, contentType, objectURL, onNext, onPrevious, onSave } = props;
const onObjectClick = (event: any) => { const onObjectClick = (event: any) => {
event.stopPropagation(); event.stopPropagation();
props.close?.(); dispatch(showLightBox(undefined));
}; };
const onContainerClick = (event: React.MouseEvent<HTMLDivElement>) => { const onContainerClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (containerRef && event.target !== containerRef.current) { if (containerRef && event.target !== containerRef.current) {
return; return;
} }
props.close?.(); dispatch(showLightBox(undefined));
}; };
return ( return (
@ -326,7 +328,7 @@ export const Lightbox = (props: Props) => {
<IconButton <IconButton
type="close" type="close"
onClick={() => { onClick={() => {
props.close?.(); dispatch(showLightBox(undefined));
}} }}
theme={theme} theme={theme}
/> />

@ -9,6 +9,8 @@ import { Lightbox } from './Lightbox';
import { AttachmentTypeWithPath } from '../types/Attachment'; import { AttachmentTypeWithPath } from '../types/Attachment';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
import { showLightBox } from '../state/ducks/conversations';
import { useDispatch } from 'react-redux';
export interface MediaItemType { export interface MediaItemType {
objectURL?: string; objectURL?: string;
@ -22,16 +24,17 @@ export interface MediaItemType {
} }
type Props = { type Props = {
close: () => void;
media: Array<MediaItemType>; media: Array<MediaItemType>;
onSave?: (saveData: MediaItemType) => void; onSave?: (saveData: MediaItemType) => void;
selectedIndex: number; selectedIndex: number;
}; };
export const LightboxGallery = (props: Props) => { export const LightboxGallery = (props: Props) => {
const { close, media, onSave } = props; const { media, onSave } = props;
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const dispatch = useDispatch();
// just run once, when the component is mounted. It's to show the lightbox on the specified index at start. // just run once, when the component is mounted. It's to show the lightbox on the specified index at start.
useEffect(() => { useEffect(() => {
setCurrentIndex(props.selectedIndex); setCurrentIndex(props.selectedIndex);
@ -86,12 +89,11 @@ export const LightboxGallery = (props: Props) => {
); );
useKey('Escape', () => { useKey('Escape', () => {
props.close?.(); dispatch(showLightBox(undefined));
}); });
return ( return (
<Lightbox <Lightbox
close={close}
onPrevious={onPrevious} onPrevious={onPrevious}
onNext={onNext} onNext={onNext}
onSave={saveCallback} onSave={saveCallback}

@ -1,67 +1,66 @@
import React from 'react'; import React from 'react';
import { DocumentListItem } from './DocumentListItem'; import { DocumentListItem } from './DocumentListItem';
import { ItemClickEvent } from './types/ItemClickEvent';
import { MediaGridItem } from './MediaGridItem'; import { MediaGridItem } from './MediaGridItem';
import { MediaItemType } from '../../LightboxGallery'; import { MediaItemType } from '../../LightboxGallery';
import { missingCaseError } from '../../../util/missingCaseError'; import { missingCaseError } from '../../../util/missingCaseError';
import { useSelector } from 'react-redux';
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
interface Props { type Props = {
type: 'media' | 'documents'; type: 'media' | 'documents';
mediaItems: Array<MediaItemType>; mediaItems: Array<MediaItemType>;
onItemClick?: (event: ItemClickEvent) => void; };
}
export class AttachmentSection extends React.Component<Props> { const Items = (props: Props): JSX.Element => {
public render() { const { mediaItems, type } = props;
const { type } = this.props; const selectedConversationKey = useSelector(getSelectedConversationKey);
return ( return (
<div className="module-attachment-section"> <>
<div className="module-attachment-section__items"> {mediaItems.map((mediaItem, position, array) => {
<div className={`module-attachment-section__items-${type}`}>{this.renderItems()}</div> const shouldShowSeparator = position < array.length - 1;
</div> const { index, attachment, messageTimestamp, messageId } = mediaItem;
</div>
);
}
private renderItems() {
const { mediaItems, type } = this.props;
return mediaItems.map((mediaItem, position, array) => {
const shouldShowSeparator = position < array.length - 1;
const { index, attachment, messageTimestamp, messageId } = mediaItem;
const onClick = this.createClickHandler(mediaItem); switch (type) {
switch (type) { case 'media':
case 'media': return (
return ( <MediaGridItem
<MediaGridItem key={`${messageId}-${index}`} mediaItem={mediaItem} onClick={onClick} /> key={`${messageId}-${index}`}
); mediaItem={mediaItem}
case 'documents': mediaItems={mediaItems}
return ( />
<DocumentListItem );
key={`${messageId}-${index}`} case 'documents':
fileName={attachment.fileName} return (
fileSize={attachment.size} <DocumentListItem
shouldShowSeparator={shouldShowSeparator} key={`${messageId}-${index}`}
onClick={onClick} fileName={attachment.fileName}
timestamp={messageTimestamp} fileSize={attachment.size}
/> shouldShowSeparator={shouldShowSeparator}
); timestamp={messageTimestamp}
default: mediaItem={mediaItem}
return missingCaseError(type); conversationId={selectedConversationKey as string}
} />
}); );
} default:
return missingCaseError(type);
}
})}
</>
);
};
private readonly createClickHandler = (mediaItem: MediaItemType) => () => { export const AttachmentSection = (props: Props) => {
const { onItemClick, type } = this.props; const { type } = props;
if (!onItemClick) { return (
return; <div className="module-attachment-section">
} <div className="module-attachment-section__items">
<div className={`module-attachment-section__items-${type}`}>
onItemClick({ mediaItem, type }); <Items {...props} />
}; </div>
} </div>
</div>
);
};

@ -4,6 +4,10 @@ import classNames from 'classnames';
import moment from 'moment'; import moment from 'moment';
// tslint:disable-next-line:match-default-export-name // tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize'; import formatFileSize from 'filesize';
import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmentsManager';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { AttachmentTypeWithPath, save } from '../../../types/Attachment';
import { MediaItemType } from '../../LightboxGallery';
type Props = { type Props = {
// Required // Required
@ -12,8 +16,31 @@ type Props = {
// Optional // Optional
fileName?: string; fileName?: string;
fileSize?: number | null; fileSize?: number | null;
onClick?: () => void;
shouldShowSeparator?: boolean; shouldShowSeparator?: boolean;
mediaItem: MediaItemType;
conversationId: string;
};
const saveAttachment = async ({
attachment,
messageTimestamp,
messageSender,
conversationId,
}: {
attachment: AttachmentTypeWithPath;
messageTimestamp: number;
messageSender: string;
conversationId: 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);
}; };
export const DocumentListItem = (props: Props) => { export const DocumentListItem = (props: Props) => {
@ -28,7 +55,18 @@ export const DocumentListItem = (props: Props) => {
defaultShowSeparator ? 'module-document-list-item--with-separator' : null defaultShowSeparator ? 'module-document-list-item--with-separator' : null
)} )}
> >
<div className="module-document-list-item__content" role="button" onClick={props.onClick}> <div
className="module-document-list-item__content"
role="button"
onClick={() => {
void saveAttachment({
messageSender: props.mediaItem.messageSender,
messageTimestamp: props.mediaItem.messageTimestamp,
attachment: props.mediaItem.attachment,
conversationId: props.conversationId,
});
}}
>
<div className="module-document-list-item__icon" /> <div className="module-document-list-item__icon" />
<div className="module-document-list-item__metadata"> <div className="module-document-list-item__metadata">
<span className="module-document-list-item__file-name">{fileName}</span> <span className="module-document-list-item__file-name">{fileName}</span>

@ -1,26 +1,17 @@
import React from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { AttachmentSection } from './AttachmentSection'; import { AttachmentSection } from './AttachmentSection';
import { EmptyState } from './EmptyState'; import { EmptyState } from './EmptyState';
import { ItemClickEvent } from './types/ItemClickEvent';
import { missingCaseError } from '../../../util/missingCaseError'; import { missingCaseError } from '../../../util/missingCaseError';
import { MediaItemType } from '../../LightboxGallery'; import { MediaItemType } from '../../LightboxGallery';
type Props = {
interface Props {
documents: Array<MediaItemType>; documents: Array<MediaItemType>;
media: Array<MediaItemType>; media: Array<MediaItemType>;
onItemClick?: (event: ItemClickEvent) => void; };
}
interface State {
selectedTab: 'media' | 'documents';
}
interface TabSelectEvent { type TabType = 'media' | 'documents';
type: 'media' | 'documents';
}
const Tab = ({ const Tab = ({
isSelected, isSelected,
@ -30,22 +21,16 @@ const Tab = ({
}: { }: {
isSelected: boolean; isSelected: boolean;
label: string; label: string;
onSelect?: (event: TabSelectEvent) => void; onSelect: () => void;
type: 'media' | 'documents'; type: TabType;
}) => { }) => {
const handleClick = onSelect
? () => {
onSelect({ type });
}
: undefined;
return ( return (
<div <div
className={classNames( className={classNames(
'module-media-gallery__tab', 'module-media-gallery__tab',
isSelected ? 'module-media-gallery__tab--active' : null isSelected ? 'module-media-gallery__tab--active' : null
)} )}
onClick={handleClick} onClick={onSelect}
role="tab" role="tab"
> >
{label} {label}
@ -53,72 +38,65 @@ const Tab = ({
); );
}; };
export class MediaGallery extends React.Component<Props, State> { const Sections = (props: Props & { selectedTab: TabType }) => {
public state: State = { const { media, documents, selectedTab } = props;
selectedTab: 'media',
};
public render() { const mediaItems = selectedTab === 'media' ? media : documents;
const { selectedTab } = this.state; const type = selectedTab;
return ( if (!mediaItems || mediaItems.length === 0) {
<div className="module-media-gallery"> const label = (() => {
<div className="module-media-gallery__tab-container"> switch (type) {
<Tab case 'media':
label={window.i18n('media')} return window.i18n('mediaEmptyState');
type="media"
isSelected={selectedTab === 'media'}
onSelect={this.handleTabSelect}
/>
<Tab
label={window.i18n('documents')}
type="documents"
isSelected={selectedTab === 'documents'}
onSelect={this.handleTabSelect}
/>
</div>
<div className="module-media-gallery__content">{this.renderSections()}</div>
</div>
);
}
private readonly handleTabSelect = (event: TabSelectEvent): void => {
this.setState({ selectedTab: event.type });
};
private renderSections() { case 'documents':
const { media, documents, onItemClick } = this.props; return window.i18n('documentsEmptyState');
const { selectedTab } = this.state;
const mediaItems = selectedTab === 'media' ? media : documents; default:
const type = selectedTab; throw missingCaseError(type);
}
})();
if (!mediaItems || mediaItems.length === 0) { return <EmptyState data-test="EmptyState" label={label} />;
const label = (() => { }
switch (type) {
case 'media':
return window.i18n('mediaEmptyState');
case 'documents': return (
return window.i18n('documentsEmptyState'); <div className="module-media-gallery__sections">
<AttachmentSection key="mediaItems" type={type} mediaItems={mediaItems} />
</div>
);
};
default: export const MediaGallery = (props: Props) => {
throw missingCaseError(type); const [selectedTab, setSelectedTab] = useState<TabType>('media');
}
})();
return <EmptyState data-test="EmptyState" label={label} />; const isDocumentSelected = selectedTab === 'documents';
} const isMediaSelected = selectedTab === 'media';
return ( return (
<div className="module-media-gallery__sections"> <div className="module-media-gallery">
<AttachmentSection <div className="module-media-gallery__tab-container">
key="mediaItems" <Tab
type={type} label={window.i18n('media')}
mediaItems={mediaItems} type="media"
onItemClick={onItemClick} isSelected={isMediaSelected}
onSelect={() => {
setSelectedTab('media');
}}
/>
<Tab
label={window.i18n('documents')}
type="documents"
isSelected={isDocumentSelected}
onSelect={() => {
setSelectedTab('documents');
}}
/> />
</div> </div>
); <div className="module-media-gallery__content">
} <Sections {...props} selectedTab={selectedTab} />
} </div>
</div>
);
};

@ -4,10 +4,12 @@ import classNames from 'classnames';
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome'; import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
import { MediaItemType } from '../../LightboxGallery'; import { MediaItemType } from '../../LightboxGallery';
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch'; import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
import { showLightBox } from '../../../state/ducks/conversations';
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
type Props = { type Props = {
mediaItem: MediaItemType; mediaItem: MediaItemType;
onClick?: () => void; mediaItems: Array<MediaItemType>;
}; };
const MediaGridItemContent = (props: Props) => { const MediaGridItemContent = (props: Props) => {
@ -88,7 +90,18 @@ const MediaGridItemContent = (props: Props) => {
export const MediaGridItem = (props: Props) => { export const MediaGridItem = (props: Props) => {
return ( return (
<div className="module-media-grid-item" role="button" onClick={props.onClick}> <div
className="module-media-grid-item"
role="button"
onClick={() => {
const lightBoxOptions: LightBoxOptions = {
media: props.mediaItems,
attachment: props.mediaItem.attachment,
};
window.inboxStore?.dispatch(showLightBox(lightBoxOptions));
}}
>
<MediaGridItemContent {...props} /> <MediaGridItemContent {...props} />
</div> </div>
); );

@ -1,6 +0,0 @@
import { MediaItemType } from '../../../LightboxGallery';
export interface ItemClickEvent {
mediaItem: MediaItemType;
type: 'media' | 'documents';
}

@ -23,6 +23,7 @@ import {
PropsForMessage, PropsForMessage,
ReduxConversationType, ReduxConversationType,
resetSelectedMessageIds, resetSelectedMessageIds,
showLightBox,
SortedMessageModelProps, SortedMessageModelProps,
} from '../../../state/ducks/conversations'; } from '../../../state/ducks/conversations';
import { MessageView } from '../../MainViewController'; import { MessageView } from '../../MainViewController';
@ -47,9 +48,6 @@ interface State {
// quoted message // quoted message
quotedMessageTimestamp?: number; quotedMessageTimestamp?: number;
quotedMessageProps?: any; quotedMessageProps?: any;
// lightbox options
lightBoxOptions?: LightBoxOptions;
} }
export interface LightBoxOptions { export interface LightBoxOptions {
@ -66,6 +64,9 @@ interface Props {
selectedMessages: Array<string>; selectedMessages: Array<string>;
showMessageDetails: boolean; showMessageDetails: boolean;
isRightPanelShowing: boolean; isRightPanelShowing: boolean;
// lightbox options
lightBoxOptions?: LightBoxOptions;
} }
export class SessionConversation extends React.Component<Props, State> { export class SessionConversation extends React.Component<Props, State> {
@ -175,13 +176,7 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~ RENDER METHODS ~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~ RENDER METHODS ~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public render() { public render() {
const { const { showRecordingView, quotedMessageProps, isDraggingFile, stagedAttachments } = this.state;
showRecordingView,
quotedMessageProps,
lightBoxOptions,
isDraggingFile,
stagedAttachments,
} = this.state;
const { const {
selectedConversation, selectedConversation,
@ -190,6 +185,7 @@ export class SessionConversation extends React.Component<Props, State> {
showMessageDetails, showMessageDetails,
selectedMessages, selectedMessages,
isRightPanelShowing, isRightPanelShowing,
lightBoxOptions,
} = this.props; } = this.props;
if (!selectedConversation || !messagesProps) { if (!selectedConversation || !messagesProps) {
@ -269,7 +265,7 @@ export class SessionConversation extends React.Component<Props, State> {
<div <div
className={classNames('conversation-item__options-pane', isRightPanelShowing && 'show')} className={classNames('conversation-item__options-pane', isRightPanelShowing && 'show')}
> >
<SessionRightPanelWithDetails {...this.getRightPanelProps()} /> <SessionRightPanelWithDetails />
</div> </div>
</SessionTheme> </SessionTheme>
); );
@ -311,15 +307,6 @@ export class SessionConversation extends React.Component<Props, State> {
}; };
} }
// tslint:disable-next-line: max-func-body-length
public getRightPanelProps() {
return {
onShowLightBox: (lightBoxOptions?: LightBoxOptions) => {
this.setState({ lightBoxOptions });
},
};
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~ MICROPHONE METHODS ~~~~~~~~~~~~ // ~~~~~~~~~~~~ MICROPHONE METHODS ~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -390,7 +377,7 @@ export class SessionConversation extends React.Component<Props, State> {
media: media as any, media: media as any,
attachment, attachment,
}; };
this.setState({ lightBoxOptions }); window.inboxStore?.dispatch(showLightBox(lightBoxOptions));
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -484,14 +471,7 @@ export class SessionConversation extends React.Component<Props, State> {
: 0; : 0;
console.warn('renderLightBox', { media, attachment }); console.warn('renderLightBox', { media, attachment });
return ( return (
<LightboxGallery <LightboxGallery media={media} selectedIndex={selectedIndex} onSave={this.saveAttachment} />
media={media}
close={() => {
this.setState({ lightBoxOptions: undefined });
}}
selectedIndex={selectedIndex}
onSave={this.saveAttachment}
/>
); );
} }

@ -4,23 +4,15 @@ import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionDropdown } from '../SessionDropdown'; import { SessionDropdown } from '../SessionDropdown';
import { MediaGallery } from '../../conversation/media-gallery/MediaGallery'; import { MediaGallery } from '../../conversation/media-gallery/MediaGallery';
import _, { noop } from 'lodash'; import _ from 'lodash';
import { TimerOption } from '../../conversation/ConversationHeader';
import { Constants } from '../../../session'; import { Constants } from '../../../session';
import { import { ConversationAvatar } from '../usingClosedConversationDetails';
ConversationAvatar, import { AttachmentTypeWithPath } from '../../../types/Attachment';
usingClosedConversationDetails, import { useTheme } from 'styled-components';
} from '../usingClosedConversationDetails';
import { AttachmentTypeWithPath, save } from '../../../types/Attachment';
import { DefaultTheme, useTheme, withTheme } from 'styled-components';
import { import {
getMessagesWithFileAttachments, getMessagesWithFileAttachments,
getMessagesWithVisualMediaAttachments, getMessagesWithVisualMediaAttachments,
} from '../../../data/data'; } from '../../../data/data';
import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmentsManager';
import { LightBoxOptions } from './SessionConversation';
import { UserUtils } from '../../../session/utils';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { SpacerLG } from '../../basic/Text'; import { SpacerLG } from '../../basic/Text';
import { import {
deleteMessagesByConvoIdWithConfirmation, deleteMessagesByConvoIdWithConfirmation,
@ -32,30 +24,23 @@ import {
showUpdateGroupMembersByConvoId, showUpdateGroupMembersByConvoId,
showUpdateGroupNameByConvoId, showUpdateGroupNameByConvoId,
} from '../../../interactions/conversationInteractions'; } from '../../../interactions/conversationInteractions';
import { ItemClickEvent } from '../../conversation/media-gallery/types/ItemClickEvent';
import { MediaItemType } from '../../LightboxGallery'; import { MediaItemType } from '../../LightboxGallery';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval'; import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { getTimerOptions } from '../../../state/selectors/timerOptions'; import { getTimerOptions } from '../../../state/selectors/timerOptions';
import { getSelectedConversation, isRightPanelShowing } from '../../../state/selectors/conversations'; import {
getSelectedConversation,
isRightPanelShowing,
} from '../../../state/selectors/conversations';
import { useMembersAvatars } from '../../../hooks/useMembersAvatar'; import { useMembersAvatars } from '../../../hooks/useMembersAvatar';
import { closeRightPanel } from '../../../state/ducks/conversations'; import { closeRightPanel } from '../../../state/ducks/conversations';
type Props = {
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void;
};
async function getMediaGalleryProps( async function getMediaGalleryProps(
conversationId: string, conversationId: string
medias: Array<MediaItemType>,
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void
): Promise<{ ): Promise<{
documents: Array<MediaItemType>; documents: Array<MediaItemType>;
media: Array<MediaItemType>; media: Array<MediaItemType>;
onItemClick: any;
}> { }> {
// We fetch more documents than media as they dont require to be loaded // We fetch more documents than media as they dont require to be loaded
// into memory right away. Revisit this once we have infinite scrolling: // into memory right away. Revisit this once we have infinite scrolling:
@ -118,61 +103,9 @@ async function getMediaGalleryProps(
}; };
}); });
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);
};
const onItemClick = (event: ItemClickEvent) => {
if (!event) {
console.warn('no event');
return;
}
const { mediaItem, type } = event;
switch (type) {
case 'documents': {
void saveAttachment({
messageSender: mediaItem.messageSender,
messageTimestamp: mediaItem.messageTimestamp,
attachment: mediaItem.attachment,
});
break;
}
case 'media': {
const lightBoxOptions: LightBoxOptions = {
media: medias,
attachment: mediaItem.attachment,
};
onShowLightBox(lightBoxOptions);
break;
}
default:
throw new TypeError(`Unknown attachment type: '${type}'`);
}
};
return { return {
media, media,
documents: _.compact(documents), // remove null documents: _.compact(documents), // remove null
onItemClick,
}; };
} }
@ -239,31 +172,30 @@ const HeaderItem = () => {
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity
// tslint:disable: max-func-body-length // tslint:disable: max-func-body-length
export const SessionRightPanelWithDetails = (props: Props) => { export const SessionRightPanelWithDetails = () => {
const [documents, setDocuments] = useState<Array<MediaItemType>>([]); const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
const [media, setMedia] = useState<Array<MediaItemType>>([]); const [media, setMedia] = useState<Array<MediaItemType>>([]);
const [onItemClick, setOnItemClick] = useState<any>(undefined);
const selectedConversation = useSelector(getSelectedConversation); const selectedConversation = useSelector(getSelectedConversation);
const isShowing = useSelector(isRightPanelShowing); const isShowing = useSelector(isRightPanelShowing);
console.warn('props', props);
useEffect(() => { useEffect(() => {
let isRunning = true; let isRunning = true;
if (isShowing && selectedConversation) { if (isShowing && selectedConversation) {
void getMediaGalleryProps(selectedConversation.id, media, props.onShowLightBox).then( void getMediaGalleryProps(selectedConversation.id).then(results => {
results => { console.warn('results2', results);
console.warn('results2', results);
if (isRunning) { if (isRunning) {
if (!_.isEqual(documents, results.documents)) {
setDocuments(results.documents); setDocuments(results.documents);
}
if (!_.isEqual(media, results.media)) {
setMedia(results.media); setMedia(results.media);
setOnItemClick(results.onItemClick);
} }
} }
); });
} }
return () => { return () => {
@ -274,16 +206,10 @@ export const SessionRightPanelWithDetails = (props: Props) => {
useInterval(async () => { useInterval(async () => {
if (isShowing && selectedConversation) { if (isShowing && selectedConversation) {
const results = await getMediaGalleryProps( const results = await getMediaGalleryProps(selectedConversation.id);
selectedConversation.id,
media,
props.onShowLightBox
);
console.warn('results', results);
if (results.documents.length !== documents.length || results.media.length !== media.length) { if (results.documents.length !== documents.length || results.media.length !== media.length) {
setDocuments(results.documents); setDocuments(results.documents);
setMedia(results.media); setMedia(results.media);
setOnItemClick(results.onItemClick);
} }
} }
}, 10000); }, 10000);
@ -319,8 +245,8 @@ export const SessionRightPanelWithDetails = (props: Props) => {
const disappearingMessagesOptions = timerOptions.map(option => { const disappearingMessagesOptions = timerOptions.map(option => {
return { return {
content: option.name, content: option.name,
onClick: async () => { onClick: () => {
await setDisappearingMessagesByConvoId(id, option.value); void setDisappearingMessagesByConvoId(id, option.value);
}, },
}; };
}); });
@ -337,7 +263,6 @@ export const SessionRightPanelWithDetails = (props: Props) => {
: () => { : () => {
showLeaveGroupByConvoId(id); showLeaveGroupByConvoId(id);
}; };
console.warn('onItemClick', onItemClick);
return ( return (
<div className="group-settings"> <div className="group-settings">
<HeaderItem /> <HeaderItem />
@ -404,7 +329,7 @@ export const SessionRightPanelWithDetails = (props: Props) => {
/> />
)} )}
<MediaGallery documents={documents} media={media} onItemClick={onItemClick} /> <MediaGallery documents={documents} media={media} />
{isGroup && ( {isGroup && (
// tslint:disable-next-line: use-simple-attributes // tslint:disable-next-line: use-simple-attributes
<SessionButton <SessionButton

@ -15,6 +15,7 @@ import {
PropsForDataExtractionNotification, PropsForDataExtractionNotification,
} from '../../models/messageType'; } from '../../models/messageType';
import { NotificationForConvoOption } from '../../components/conversation/ConversationHeader'; import { NotificationForConvoOption } from '../../components/conversation/ConversationHeader';
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
export type MessageModelProps = { export type MessageModelProps = {
propsForMessage: PropsForMessage; propsForMessage: PropsForMessage;
@ -233,6 +234,7 @@ export type ConversationsStateType = {
messageDetailProps: MessagePropsDetails | undefined; messageDetailProps: MessagePropsDetails | undefined;
showRightPanel: boolean; showRightPanel: boolean;
selectedMessageIds: Array<string>; selectedMessageIds: Array<string>;
lightBox?: LightBoxOptions;
}; };
async function getMessages( async function getMessages(
@ -708,6 +710,13 @@ const conversationsSlice = createSlice({
state.messages = []; state.messages = [];
return state; return state;
}, },
showLightBox(
state: ConversationsStateType,
action: PayloadAction<LightBoxOptions | undefined>
) {
state.lightBox = action.payload;
return state;
},
}, },
extraReducers: (builder: any) => { extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed // Add reducers for additional action types here, and handle loading state as needed
@ -752,4 +761,5 @@ export const {
addMessageIdToSelection, addMessageIdToSelection,
resetSelectedMessageIds, resetSelectedMessageIds,
toggleSelectedMessageId, toggleSelectedMessageId,
showLightBox,
} = actions; } = actions;

@ -17,6 +17,7 @@ import {
ConversationHeaderProps, ConversationHeaderProps,
ConversationHeaderTitleProps, ConversationHeaderTitleProps,
} from '../../components/conversation/ConversationHeader'; } from '../../components/conversation/ConversationHeader';
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations; export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -283,3 +284,8 @@ export const getSelectedMessageIds = createSelector(
getConversations, getConversations,
(state: ConversationsStateType): Array<string> => state.selectedMessageIds (state: ConversationsStateType): Array<string> => state.selectedMessageIds
); );
export const getLightBoxOptions = createSelector(
getConversations,
(state: ConversationsStateType): LightBoxOptions | undefined => state.lightBox
);

@ -4,6 +4,7 @@ import { SessionConversation } from '../../components/session/conversation/Sessi
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getTheme } from '../selectors/theme'; import { getTheme } from '../selectors/theme';
import { import {
getLightBoxOptions,
getMessagesOfSelectedConversation, getMessagesOfSelectedConversation,
getSelectedConversation, getSelectedConversation,
getSelectedConversationKey, getSelectedConversationKey,
@ -23,6 +24,7 @@ const mapStateToProps = (state: StateType) => {
showMessageDetails: isMessageDetailView(state), showMessageDetails: isMessageDetailView(state),
isRightPanelShowing: isRightPanelShowing(state), isRightPanelShowing: isRightPanelShowing(state),
selectedMessages: getSelectedMessageIds(state), selectedMessages: getSelectedMessageIds(state),
lightBoxOptions: getLightBoxOptions(state),
}; };
}; };

Loading…
Cancel
Save