You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
| import { useState } from 'react';
 | |
| import { useDispatch } from 'react-redux';
 | |
| import styled from 'styled-components';
 | |
| import { clearOurAvatar, uploadOurAvatar } from '../../interactions/conversationInteractions';
 | |
| import { ToastUtils } from '../../session/utils';
 | |
| import { editProfileModal, updateEditProfilePictureModal } from '../../state/ducks/modalDialog';
 | |
| import type { EditProfilePictureModalProps } from '../../types/ReduxTypes';
 | |
| import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
 | |
| import { SessionWrapperModal } from '../SessionWrapperModal';
 | |
| import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
 | |
| import { SpacerLG } from '../basic/Text';
 | |
| import { SessionIconButton } from '../icon';
 | |
| import { SessionSpinner } from '../loading';
 | |
| import { ProfileAvatar } from './edit-profile/components';
 | |
| 
 | |
| const StyledAvatarContainer = styled.div`
 | |
|   cursor: pointer;
 | |
| `;
 | |
| 
 | |
| const StyledUploadButton = styled.div`
 | |
|   background-color: var(--chat-buttons-background-color);
 | |
|   border-radius: 50%;
 | |
|   overflow: hidden;
 | |
| `;
 | |
| 
 | |
| const UploadImageButton = () => {
 | |
|   return (
 | |
|     <div style={{ position: 'relative' }}>
 | |
|       <StyledUploadButton>
 | |
|         <SessionIconButton iconType="thumbnail" iconSize={80} iconPadding="16px" />
 | |
|       </StyledUploadButton>
 | |
|       <SessionIconButton
 | |
|         iconType="plusFat"
 | |
|         iconSize={23}
 | |
|         iconColor="var(--modal-background-content-color)"
 | |
|         iconPadding="5px"
 | |
|         borderRadius="50%"
 | |
|         backgroundColor="var(--primary-color)"
 | |
|         style={{ position: 'absolute', bottom: 0, right: 0 }}
 | |
|       />
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| const uploadProfileAvatar = async (scaledAvatarUrl: string | null) => {
 | |
|   if (scaledAvatarUrl?.length) {
 | |
|     try {
 | |
|       const blobContent = await (await fetch(scaledAvatarUrl)).blob();
 | |
|       if (!blobContent || !blobContent.size) {
 | |
|         throw new Error('Failed to fetch blob content from scaled avatar');
 | |
|       }
 | |
|       await uploadOurAvatar(await blobContent.arrayBuffer());
 | |
|     } catch (error) {
 | |
|       if (error.message && error.message.length) {
 | |
|         ToastUtils.pushToastError('edit-profile', error.message);
 | |
|       }
 | |
|       window.log.error(
 | |
|         'showEditProfileDialog Error ensuring that image is properly sized:',
 | |
|         error && error.stack ? error.stack : error
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| export const EditProfilePictureModal = (props: EditProfilePictureModalProps) => {
 | |
|   const dispatch = useDispatch();
 | |
| 
 | |
|   const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState<string | null>(props.avatarPath);
 | |
|   const [loading, setLoading] = useState(false);
 | |
| 
 | |
|   if (!props) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   const { avatarPath, profileName, ourId } = props;
 | |
| 
 | |
|   const closeDialog = () => {
 | |
|     dispatch(updateEditProfilePictureModal(null));
 | |
|     dispatch(editProfileModal({}));
 | |
|   };
 | |
| 
 | |
|   const handleAvatarClick = async () => {
 | |
|     const updatedAvatarObjectUrl = await pickFileForAvatar();
 | |
|     if (updatedAvatarObjectUrl) {
 | |
|       setNewAvatarObjectUrl(updatedAvatarObjectUrl);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const handleUpload = async () => {
 | |
|     setLoading(true);
 | |
|     if (newAvatarObjectUrl === avatarPath) {
 | |
|       window.log.debug('Avatar Object URL has not changed!');
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     await uploadProfileAvatar(newAvatarObjectUrl);
 | |
|     setLoading(false);
 | |
|     dispatch(updateEditProfilePictureModal(null));
 | |
|   };
 | |
| 
 | |
|   const handleRemove = async () => {
 | |
|     setLoading(true);
 | |
|     await clearOurAvatar();
 | |
|     setNewAvatarObjectUrl(null);
 | |
|     setLoading(false);
 | |
|     dispatch(updateEditProfilePictureModal(null));
 | |
|   };
 | |
| 
 | |
|   return (
 | |
|     <SessionWrapperModal
 | |
|       title={window.i18n('profileDisplayPictureSet')}
 | |
|       onClose={closeDialog}
 | |
|       showHeader={true}
 | |
|       headerReverse={true}
 | |
|       showExitIcon={true}
 | |
|     >
 | |
|       <div
 | |
|         className="avatar-center"
 | |
|         role="button"
 | |
|         onClick={() => void handleAvatarClick()}
 | |
|         data-testid={'image-upload-click'}
 | |
|       >
 | |
|         <StyledAvatarContainer className="avatar-center-inner">
 | |
|           {newAvatarObjectUrl || avatarPath ? (
 | |
|             <ProfileAvatar
 | |
|               newAvatarObjectUrl={newAvatarObjectUrl}
 | |
|               avatarPath={avatarPath}
 | |
|               profileName={profileName}
 | |
|               ourId={ourId}
 | |
|             />
 | |
|           ) : (
 | |
|             <UploadImageButton />
 | |
|           )}
 | |
|         </StyledAvatarContainer>
 | |
|       </div>
 | |
| 
 | |
|       {loading ? (
 | |
|         <SessionSpinner loading={loading} />
 | |
|       ) : (
 | |
|         <>
 | |
|           <SpacerLG />
 | |
|           <div className="session-modal__button-group">
 | |
|             <SessionButton
 | |
|               text={window.i18n('save')}
 | |
|               buttonType={SessionButtonType.Simple}
 | |
|               onClick={handleUpload}
 | |
|               disabled={newAvatarObjectUrl === avatarPath}
 | |
|               dataTestId="save-button-profile-update"
 | |
|             />
 | |
|             <SessionButton
 | |
|               text={window.i18n('remove')}
 | |
|               buttonColor={SessionButtonColor.Danger}
 | |
|               buttonType={SessionButtonType.Simple}
 | |
|               onClick={handleRemove}
 | |
|               disabled={!avatarPath}
 | |
|             />
 | |
|           </div>
 | |
|         </>
 | |
|       )}
 | |
|     </SessionWrapperModal>
 | |
|   );
 | |
| };
 |