feat: added button state logic and migrated avatar upload logic from EditProfileDialog

show loading spinner while avatar is loading
pull/2765/head
William Grant 2 years ago
parent ebeaec2080
commit 88587a203d

@ -1,48 +1,121 @@
import React, { ReactElement } from 'react'; import React, { useState } from 'react';
import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SpacerLG } from '../basic/Text'; import { SpacerLG } from '../basic/Text';
import { UserUtils } from '../../session/utils';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { updateDisplayPictureModel } from '../../state/ducks/modalDialog'; 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 uploadProfileAvatar = async (scaledAvatarUrl: string | null) => {
const {} = props; 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<string | null>;
removeAction: () => void;
};
export const DisplayPictureModal = (props: DisplayPictureModalProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const onClickClose = () => { if (!props) {
return null;
}
const {
newAvatarObjectUrl: _newAvatarObjectUrl,
oldAvatarPath,
profileName,
ourId,
avatarAction,
removeAction,
} = props;
const [newAvatarObjectUrl, setNewAvatarObjectUrl] = useState<string | null>(_newAvatarObjectUrl);
const [loading, setLoading] = useState(false);
const closeDialog = () => {
dispatch(updateDisplayPictureModel(null)); dispatch(updateDisplayPictureModel(null));
}; };
return ( return (
<SessionWrapperModal <SessionWrapperModal
title={window.i18n('setDisplayPicture')} title={window.i18n('setDisplayPicture')}
onClose={onClickClose} onClose={closeDialog}
showHeader={true} showHeader={true}
showExitIcon={true} showExitIcon={true}
> >
<div className="avatar-center"> <div
<div className="avatar-center-inner"> className="avatar-center"
<Avatar size={AvatarSize.XL} pubkey={UserUtils.getOurPubKeyStrFromCache()} /> onClick={async () => {
</div> const updatedAvatarObjectUrl = await avatarAction();
if (updatedAvatarObjectUrl) {
setNewAvatarObjectUrl(updatedAvatarObjectUrl);
}
}}
>
<StyledAvatarContainer className="avatar-center-inner">
<ProfileAvatar
newAvatarObjectUrl={newAvatarObjectUrl}
oldAvatarPath={oldAvatarPath}
profileName={profileName}
ourId={ourId}
/>
</StyledAvatarContainer>
</div> </div>
<SpacerLG /> <SpacerLG />
<SessionSpinner loading={loading} />
<div className="session-modal__button-group"> <div className="session-modal__button-group">
<SessionButton <SessionButton
text={window.i18n('upload')} text={window.i18n('upload')}
buttonType={SessionButtonType.Simple} buttonType={SessionButtonType.Simple}
onClick={() => {}} 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}
/> />
<SessionButton <SessionButton
text={window.i18n('remove')} text={window.i18n('remove')}
buttonColor={SessionButtonColor.Danger} buttonColor={SessionButtonColor.Danger}
buttonType={SessionButtonType.Simple} buttonType={SessionButtonType.Simple}
onClick={() => {}} onClick={() => {
removeAction();
}}
disabled={!oldAvatarPath}
/> />
</div> </div>
</SessionWrapperModal> </SessionWrapperModal>

@ -82,14 +82,14 @@ const commitProfileEdits = async (newName: string, scaledAvatarUrl: string | nul
await SyncUtils.forceSyncConfigurationNowIfNeeded(true); await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}; };
type ProfileAvatarProps = { export type ProfileAvatarProps = {
newAvatarObjectUrl: string | null; newAvatarObjectUrl: string | null;
oldAvatarPath: string | null; oldAvatarPath: string | null;
profileName: string | undefined; profileName: string | undefined;
ourId: string; ourId: string;
}; };
const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => { export const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => {
const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId } = props; const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId } = props;
return ( return (
<Avatar <Avatar
@ -102,14 +102,12 @@ const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => {
}; };
type ProfileHeaderProps = ProfileAvatarProps & { type ProfileHeaderProps = ProfileAvatarProps & {
fireInputEvent: () => void; onClick: () => void;
setMode: (mode: ProfileDialogModes) => void; setMode: (mode: ProfileDialogModes) => void;
}; };
const ProfileHeader = (props: ProfileHeaderProps): ReactElement => { const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId, fireInputEvent, setMode } = props; const { newAvatarObjectUrl, oldAvatarPath, profileName, ourId, onClick, setMode } = props;
const dispatch = useDispatch();
return ( return (
<div className="avatar-center"> <div className="avatar-center">
@ -123,10 +121,7 @@ const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
<div <div
className="image-upload-section" className="image-upload-section"
role="button" role="button"
onClick={async () => { onClick={onClick}
// void fireInputEvent();
dispatch(updateDisplayPictureModel({}));
}}
data-testid="image-upload-section" data-testid="image-upload-section"
/> />
<div <div
@ -146,6 +141,8 @@ const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
type ProfileDialogModes = 'default' | 'edit' | 'qr'; type ProfileDialogModes = 'default' | 'edit' | 'qr';
export const EditProfileDialog = (): ReactElement => { export const EditProfileDialog = (): ReactElement => {
const dispatch = useDispatch();
const _profileName = useOurConversationUsername() || ''; const _profileName = useOurConversationUsername() || '';
const [profileName, setProfileName] = useState(_profileName); const [profileName, setProfileName] = useState(_profileName);
const [updatedProfileName, setUpdateProfileName] = useState(profileName); const [updatedProfileName, setUpdateProfileName] = useState(profileName);
@ -223,6 +220,22 @@ export const EditProfileDialog = (): ReactElement => {
setNewAvatarObjectUrl(scaledAvatarUrl); setNewAvatarObjectUrl(scaledAvatarUrl);
setMode('edit'); setMode('edit');
} }
return scaledAvatarUrl;
};
const handleProfileHeaderClick = () => {
closeDialog();
dispatch(
updateDisplayPictureModel({
newAvatarObjectUrl,
oldAvatarPath,
profileName,
ourId,
avatarAction: fireInputEvent,
removeAction: () => {},
})
);
}; };
const onNameEdited = (event: ChangeEvent<HTMLInputElement>) => { const onNameEdited = (event: ChangeEvent<HTMLInputElement>) => {
@ -252,7 +265,7 @@ export const EditProfileDialog = (): ReactElement => {
oldAvatarPath={oldAvatarPath} oldAvatarPath={oldAvatarPath}
profileName={profileName} profileName={profileName}
ourId={ourId} ourId={ourId}
fireInputEvent={fireInputEvent} onClick={handleProfileHeaderClick}
setMode={setMode} setMode={setMode}
/> />
<div className="profile-name-uneditable"> <div className="profile-name-uneditable">
@ -275,7 +288,7 @@ export const EditProfileDialog = (): ReactElement => {
oldAvatarPath={oldAvatarPath} oldAvatarPath={oldAvatarPath}
profileName={profileName} profileName={profileName}
ourId={ourId} ourId={ourId}
fireInputEvent={fireInputEvent} onClick={handleProfileHeaderClick}
setMode={setMode} setMode={setMode}
/> />
<div className="profile-name"> <div className="profile-name">

@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm'; import { SessionConfirmDialogProps } from '../../components/dialog/SessionConfirm';
import { PasswordAction } from '../../components/dialog/SessionPasswordDialog'; import { PasswordAction } from '../../components/dialog/SessionPasswordDialog';
import { DisplayPictureModalProps } from '../../components/dialog/DisplayPictureModal';
export type BanType = 'ban' | 'unban'; export type BanType = 'ban' | 'unban';
export type ConfirmModalState = SessionConfirmDialogProps | null; export type ConfirmModalState = SessionConfirmDialogProps | null;
@ -34,7 +35,7 @@ export type ReactModalsState = {
messageId: string; messageId: string;
} | null; } | null;
export type DisplayPictureModalState = {} | null; export type DisplayPictureModalState = DisplayPictureModalProps | null;
export type ModalState = { export type ModalState = {
confirmModal: ConfirmModalState; confirmModal: ConfirmModalState;

Loading…
Cancel
Save