|  |  |  |  | import React from 'react'; | 
					
						
							|  |  |  |  | import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; | 
					
						
							|  |  |  |  | import { Avatar } from '../Avatar'; | 
					
						
							|  |  |  |  | import { | 
					
						
							|  |  |  |  |   SessionButton, | 
					
						
							|  |  |  |  |   SessionButtonColor, | 
					
						
							|  |  |  |  |   SessionButtonType, | 
					
						
							|  |  |  |  | } from './SessionButton'; | 
					
						
							|  |  |  |  | import { SessionDropdown } from './SessionDropdown'; | 
					
						
							|  |  |  |  | import { MediaGallery } from '../conversation/media-gallery/MediaGallery'; | 
					
						
							|  |  |  |  | import _ from 'lodash'; | 
					
						
							|  |  |  |  | import { TimerOption } from '../conversation/ConversationHeader'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | interface Props { | 
					
						
							|  |  |  |  |   id: string; | 
					
						
							|  |  |  |  |   name: string; | 
					
						
							|  |  |  |  |   memberCount: number; | 
					
						
							|  |  |  |  |   description: string; | 
					
						
							|  |  |  |  |   avatarPath: string; | 
					
						
							|  |  |  |  |   timerOptions: Array<TimerOption>; | 
					
						
							|  |  |  |  |   isPublic: boolean; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   onGoBack: () => void; | 
					
						
							|  |  |  |  |   onInviteFriends: () => void; | 
					
						
							|  |  |  |  |   onLeaveGroup: () => void; | 
					
						
							|  |  |  |  |   onShowLightBox: (options: any) => void; | 
					
						
							|  |  |  |  |   onSetDisappearingMessages: (seconds: number) => void; | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | export class SessionChannelSettings extends React.Component<Props, any> { | 
					
						
							|  |  |  |  |   public constructor(props: Props) { | 
					
						
							|  |  |  |  |     super(props); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     this.state = { | 
					
						
							|  |  |  |  |       documents: Array<any>(), | 
					
						
							|  |  |  |  |       media: Array<any>(), | 
					
						
							|  |  |  |  |       onItemClick: undefined, | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public componentWillMount() { | 
					
						
							|  |  |  |  |     this.getMediaGalleryProps() | 
					
						
							|  |  |  |  |       .then(({ documents, media, onItemClick }) => { | 
					
						
							|  |  |  |  |         this.setState({ | 
					
						
							|  |  |  |  |           documents, | 
					
						
							|  |  |  |  |           media, | 
					
						
							|  |  |  |  |           onItemClick, | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |       }) | 
					
						
							|  |  |  |  |       .ignore(); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public componentDidUpdate() { | 
					
						
							|  |  |  |  |     const mediaScanInterval = 1000; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     setTimeout(() => { | 
					
						
							|  |  |  |  |       this.getMediaGalleryProps() | 
					
						
							|  |  |  |  |         .then(({ documents, media, onItemClick }) => { | 
					
						
							|  |  |  |  |           this.setState({ | 
					
						
							|  |  |  |  |             documents, | 
					
						
							|  |  |  |  |             media, | 
					
						
							|  |  |  |  |             onItemClick, | 
					
						
							|  |  |  |  |           }); | 
					
						
							|  |  |  |  |         }) | 
					
						
							|  |  |  |  |         .ignore(); | 
					
						
							|  |  |  |  |     }, mediaScanInterval); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public async getMediaGalleryProps() { | 
					
						
							|  |  |  |  |     // 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 DEFAULT_MEDIA_FETCH_COUNT = 50; | 
					
						
							|  |  |  |  |     const DEFAULT_DOCUMENTS_FETCH_COUNT = 150; | 
					
						
							|  |  |  |  |     const conversationId = this.props.id; | 
					
						
							|  |  |  |  |     const rawMedia = await window.Signal.Data.getMessagesWithVisualMediaAttachments( | 
					
						
							|  |  |  |  |       conversationId, | 
					
						
							|  |  |  |  |       { | 
					
						
							|  |  |  |  |         limit: DEFAULT_MEDIA_FETCH_COUNT, | 
					
						
							|  |  |  |  |         MessageCollection: window.Whisper.MessageCollection, | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |     const rawDocuments = await window.Signal.Data.getMessagesWithFileAttachments( | 
					
						
							|  |  |  |  |       conversationId, | 
					
						
							|  |  |  |  |       { | 
					
						
							|  |  |  |  |         limit: DEFAULT_DOCUMENTS_FETCH_COUNT, | 
					
						
							|  |  |  |  |         MessageCollection: window.Whisper.MessageCollection, | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // 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 window.Signal.Data.saveMessage(rawMedia[i], { | 
					
						
							|  |  |  |  |           Message: window.Whisper.Message, | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // tslint:disable-next-line: underscore-consistent-invocation
 | 
					
						
							|  |  |  |  |     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> }) => { | 
					
						
							|  |  |  |  |         const attachments = message.attachments || []; | 
					
						
							|  |  |  |  |         const attachment = attachments[0]; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         return { | 
					
						
							|  |  |  |  |           contentType: attachment.contentType, | 
					
						
							|  |  |  |  |           index: 0, | 
					
						
							|  |  |  |  |           attachment, | 
					
						
							|  |  |  |  |           message, | 
					
						
							|  |  |  |  |         }; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     const saveAttachment = async ({ attachment, message }: any = {}) => { | 
					
						
							|  |  |  |  |       const timestamp = message.received_at; | 
					
						
							|  |  |  |  |       window.Signal.Types.Attachment.save({ | 
					
						
							|  |  |  |  |         attachment, | 
					
						
							|  |  |  |  |         document, | 
					
						
							|  |  |  |  |         getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath, | 
					
						
							|  |  |  |  |         timestamp, | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     const onItemClick = async ({ message, attachment, type }: any) => { | 
					
						
							|  |  |  |  |       switch (type) { | 
					
						
							|  |  |  |  |         case 'documents': { | 
					
						
							|  |  |  |  |           saveAttachment({ message, attachment }).ignore(); | 
					
						
							|  |  |  |  |           break; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         case 'media': { | 
					
						
							|  |  |  |  |           const lightBoxOptions = { | 
					
						
							|  |  |  |  |             media, | 
					
						
							|  |  |  |  |             attachment, | 
					
						
							|  |  |  |  |             message, | 
					
						
							|  |  |  |  |           }; | 
					
						
							|  |  |  |  |           this.onShowLightBox(lightBoxOptions); | 
					
						
							|  |  |  |  |           break; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         default: | 
					
						
							|  |  |  |  |           throw new TypeError(`Unknown attachment type: '${type}'`); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     return { | 
					
						
							|  |  |  |  |       media, | 
					
						
							|  |  |  |  |       documents, | 
					
						
							|  |  |  |  |       onItemClick, | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public onShowLightBox(options: any) { | 
					
						
							|  |  |  |  |     this.props.onShowLightBox(options); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   public render() { | 
					
						
							|  |  |  |  |     const { | 
					
						
							|  |  |  |  |       memberCount, | 
					
						
							|  |  |  |  |       name, | 
					
						
							|  |  |  |  |       timerOptions, | 
					
						
							|  |  |  |  |       onLeaveGroup, | 
					
						
							|  |  |  |  |       isPublic, | 
					
						
							|  |  |  |  |     } = this.props; | 
					
						
							|  |  |  |  |     const { documents, media, onItemClick } = this.state; | 
					
						
							|  |  |  |  |     const showMemberCount = !!(memberCount && memberCount > 0); | 
					
						
							|  |  |  |  |     const hasDisappearingMessages = !isPublic; | 
					
						
							|  |  |  |  |     const leaveGroupString = isPublic | 
					
						
							|  |  |  |  |       ? window.i18n('leaveOpenGroup') | 
					
						
							|  |  |  |  |       : window.i18n('leaveClosedGroup'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     const disappearingMessagesOptions = timerOptions.map(option => { | 
					
						
							|  |  |  |  |       return { | 
					
						
							|  |  |  |  |         content: option.name, | 
					
						
							|  |  |  |  |         onClick: () => { | 
					
						
							|  |  |  |  |           this.props.onSetDisappearingMessages(option.value); | 
					
						
							|  |  |  |  |         }, | 
					
						
							|  |  |  |  |       }; | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     return ( | 
					
						
							|  |  |  |  |       <div className="group-settings"> | 
					
						
							|  |  |  |  |         {this.renderHeader()} | 
					
						
							|  |  |  |  |         <h2>{name}</h2> | 
					
						
							|  |  |  |  |         {showMemberCount && ( | 
					
						
							|  |  |  |  |           <> | 
					
						
							|  |  |  |  |             <div className="spacer-lg" /> | 
					
						
							|  |  |  |  |             <div className="text-subtle"> | 
					
						
							|  |  |  |  |               {window.i18n('members', memberCount)} | 
					
						
							|  |  |  |  |             </div> | 
					
						
							|  |  |  |  |             <div className="spacer-lg" /> | 
					
						
							|  |  |  |  |           </> | 
					
						
							|  |  |  |  |         )} | 
					
						
							|  |  |  |  |         <input | 
					
						
							|  |  |  |  |           className="description" | 
					
						
							|  |  |  |  |           placeholder={window.i18n('description')} | 
					
						
							|  |  |  |  |         /> | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         <div className="group-settings-item"> | 
					
						
							|  |  |  |  |           {window.i18n('notifications')} | 
					
						
							|  |  |  |  |         </div> | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         {hasDisappearingMessages && ( | 
					
						
							|  |  |  |  |           <SessionDropdown | 
					
						
							|  |  |  |  |             label={window.i18n('disappearingMessages')} | 
					
						
							|  |  |  |  |             options={disappearingMessagesOptions} | 
					
						
							|  |  |  |  |           /> | 
					
						
							|  |  |  |  |         )} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         <MediaGallery | 
					
						
							|  |  |  |  |           documents={documents} | 
					
						
							|  |  |  |  |           media={media} | 
					
						
							|  |  |  |  |           onItemClick={onItemClick} | 
					
						
							|  |  |  |  |         /> | 
					
						
							|  |  |  |  |         <SessionButton | 
					
						
							|  |  |  |  |           text={leaveGroupString} | 
					
						
							|  |  |  |  |           buttonColor={SessionButtonColor.Danger} | 
					
						
							|  |  |  |  |           buttonType={SessionButtonType.SquareOutline} | 
					
						
							|  |  |  |  |           onClick={onLeaveGroup} | 
					
						
							|  |  |  |  |         /> | 
					
						
							|  |  |  |  |       </div> | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   private renderHeader() { | 
					
						
							|  |  |  |  |     const { id, onGoBack, onInviteFriends, avatarPath } = this.props; | 
					
						
							|  |  |  |  |     const shouldShowInviteFriends = !this.props.isPublic; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     return ( | 
					
						
							|  |  |  |  |       <div className="group-settings-header"> | 
					
						
							|  |  |  |  |         <SessionIconButton | 
					
						
							|  |  |  |  |           iconType={SessionIconType.Chevron} | 
					
						
							|  |  |  |  |           iconSize={SessionIconSize.Medium} | 
					
						
							|  |  |  |  |           iconRotation={270} | 
					
						
							|  |  |  |  |           onClick={onGoBack} | 
					
						
							|  |  |  |  |         /> | 
					
						
							|  |  |  |  |         <Avatar | 
					
						
							|  |  |  |  |           avatarPath={avatarPath} | 
					
						
							|  |  |  |  |           phoneNumber={id} | 
					
						
							|  |  |  |  |           conversationType="group" | 
					
						
							|  |  |  |  |           size={80} | 
					
						
							|  |  |  |  |         /> | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         <div className="invite-friends-container"> | 
					
						
							|  |  |  |  |           {shouldShowInviteFriends && ( | 
					
						
							|  |  |  |  |             <SessionIconButton | 
					
						
							|  |  |  |  |               iconType={SessionIconType.AddUser} | 
					
						
							|  |  |  |  |               iconSize={SessionIconSize.Medium} | 
					
						
							|  |  |  |  |               onClick={onInviteFriends} | 
					
						
							|  |  |  |  |             /> | 
					
						
							|  |  |  |  |           )} | 
					
						
							|  |  |  |  |         </div> | 
					
						
							|  |  |  |  |       </div> | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } |