diff --git a/ts/components/dialog/DisplayPictureModal.tsx b/ts/components/dialog/DisplayPictureModal.tsx index 4da383a0a..67f2e0ff3 100644 --- a/ts/components/dialog/DisplayPictureModal.tsx +++ b/ts/components/dialog/DisplayPictureModal.tsx @@ -1,48 +1,121 @@ -import React, { ReactElement } from 'react'; +import React, { useState } from 'react'; import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; -import { Avatar, AvatarSize } from '../avatar/Avatar'; import { SpacerLG } from '../basic/Text'; -import { UserUtils } from '../../session/utils'; import { useDispatch } from 'react-redux'; import { updateDisplayPictureModel } from '../../state/ducks/modalDialog'; +import { ProfileAvatar, ProfileAvatarProps } from './EditProfileDialog'; +import styled from 'styled-components'; +import { uploadOurAvatar } from '../../interactions/conversationInteractions'; +import { ToastUtils } from '../../session/utils'; +import { SessionSpinner } from '../basic/SessionSpinner'; -type Props = {}; +const StyledAvatarContainer = styled.div` + cursor: pointer; +`; -export const DisplayPictureModal = (props: Props): ReactElement => { - const {} = props; +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 type DisplayPictureModalProps = ProfileAvatarProps & { + avatarAction: () => Promise; + removeAction: () => void; +}; + +export const DisplayPictureModal = (props: DisplayPictureModalProps) => { const dispatch = useDispatch(); - const onClickClose = () => { + if (!props) { + return null; + } + + const { + newAvatarObjectUrl: _newAvatarObjectUrl, + oldAvatarPath, + profileName, + ourId, + avatarAction, + removeAction, + } = props; + + const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState(_newAvatarObjectUrl); + const [loading, setLoading] = useState(false); + + const closeDialog = () => { dispatch(updateDisplayPictureModel(null)); }; return ( -
-
- -
+
{ + const updatedAvatarObjectUrl = await avatarAction(); + if (updatedAvatarObjectUrl) { + setNewAvatarObjectUrl(updatedAvatarObjectUrl); + } + }} + > + + +
+
{}} + onClick={async () => { + setLoading(true); + if (newAvatarObjectUrl === _newAvatarObjectUrl) { + window.log.debug(`Avatar Object URL has not changed!`); + return; + } + + await uploadProfileAvatar(newAvatarObjectUrl); + setLoading(false); + closeDialog(); + }} + disabled={_newAvatarObjectUrl === newAvatarObjectUrl} /> {}} + onClick={() => { + removeAction(); + }} + disabled={!oldAvatarPath} />
diff --git a/ts/components/dialog/EditProfileDialog.tsx b/ts/components/dialog/EditProfileDialog.tsx index a076ca746..94a0c45a5 100644 --- a/ts/components/dialog/EditProfileDialog.tsx +++ b/ts/components/dialog/EditProfileDialog.tsx @@ -82,14 +82,14 @@ const commitProfileEdits = async (newName: string, scaledAvatarUrl: string | nul await SyncUtils.forceSyncConfigurationNowIfNeeded(true); }; -type ProfileAvatarProps = { +export type ProfileAvatarProps = { newAvatarObjectUrl: string | null; oldAvatarPath: string | null; profileName: string | undefined; ourId: string; }; -const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => { +export const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => { const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId } = props; return ( { }; type ProfileHeaderProps = ProfileAvatarProps & { - fireInputEvent: () => void; + onClick: () => void; setMode: (mode: ProfileDialogModes) => void; }; const ProfileHeader = (props: ProfileHeaderProps): ReactElement => { - const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId, fireInputEvent, setMode } = props; - - const dispatch = useDispatch(); + const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId, onClick, setMode } = props; return (
@@ -123,10 +121,7 @@ const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
{ - // void fireInputEvent(); - dispatch(updateDisplayPictureModel({})); - }} + onClick={onClick} data-testid="image-upload-section" />
{ type ProfileDialogModes = 'default' | 'edit' | 'qr'; export const EditProfileDialog = (): ReactElement => { + const dispatch = useDispatch(); + const _profileName = useOurConversationUsername() || ''; const [profileName, setProfileName] = useState(_profileName); const [updatedProfileName, setUpdateProfileName] = useState(profileName); @@ -223,6 +220,22 @@ export const EditProfileDialog = (): ReactElement => { setNewAvatarObjectUrl(scaledAvatarUrl); setMode('edit'); } + + return scaledAvatarUrl; + }; + + const handleProfileHeaderClick = () => { + closeDialog(); + dispatch( + updateDisplayPictureModel({ + newAvatarObjectUrl, + oldAvatarPath, + profileName, + ourId, + avatarAction: fireInputEvent, + removeAction: () => {}, + }) + ); }; const onNameEdited = (event: ChangeEvent) => { @@ -252,7 +265,7 @@ export const EditProfileDialog = (): ReactElement => { oldAvatarPath={oldAvatarPath} profileName={profileName} ourId={ourId} - fireInputEvent={fireInputEvent} + onClick={handleProfileHeaderClick} setMode={setMode} />
@@ -275,7 +288,7 @@ export const EditProfileDialog = (): ReactElement => { oldAvatarPath={oldAvatarPath} profileName={profileName} ourId={ourId} - fireInputEvent={fireInputEvent} + onClick={handleProfileHeaderClick} setMode={setMode} />
diff --git a/ts/state/ducks/modalDialog.tsx b/ts/state/ducks/modalDialog.tsx index 53ba355d6..238bf54ac 100644 --- a/ts/state/ducks/modalDialog.tsx +++ b/ts/state/ducks/modalDialog.tsx @@ -1,6 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm'; import { PasswordAction } from '../../components/dialog/SessionPasswordDialog'; +import { DisplayPictureModalProps } from '../../components/dialog/DisplayPictureModal'; export type BanType = 'ban' | 'unban'; export type ConfirmModalState = SessionConfirmDialogProps | null; @@ -34,7 +35,7 @@ export type ReactModalsState = { messageId: string; } | null; -export type DisplayPictureModalState = {} | null; +export type DisplayPictureModalState = DisplayPictureModalProps | null; export type ModalState = { confirmModal: ConfirmModalState;