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 { 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<string | null>;
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<string | null>(_newAvatarObjectUrl);
const [loading, setLoading] = useState(false);
const closeDialog = () => {
dispatch(updateDisplayPictureModel(null));
};
return (
<SessionWrapperModal
title={window.i18n('setDisplayPicture')}
onClose={onClickClose}
onClose={closeDialog}
showHeader={true}
showExitIcon={true}
>
<div className="avatar-center">
<div className="avatar-center-inner">
<Avatar size={AvatarSize.XL} pubkey={UserUtils.getOurPubKeyStrFromCache()} />
</div>
<div
className="avatar-center"
onClick={async () => {
const updatedAvatarObjectUrl = await avatarAction();
if (updatedAvatarObjectUrl) {
setNewAvatarObjectUrl(updatedAvatarObjectUrl);
}
}}
>
<StyledAvatarContainer className="avatar-center-inner">
<ProfileAvatar
newAvatarObjectUrl={newAvatarObjectUrl}
oldAvatarPath={oldAvatarPath}
profileName={profileName}
ourId={ourId}
/>
</StyledAvatarContainer>
</div>
<SpacerLG />
<SessionSpinner loading={loading} />
<div className="session-modal__button-group">
<SessionButton
text={window.i18n('upload')}
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
text={window.i18n('remove')}
buttonColor={SessionButtonColor.Danger}
buttonType={SessionButtonType.Simple}
onClick={() => {}}
onClick={() => {
removeAction();
}}
disabled={!oldAvatarPath}
/>
</div>
</SessionWrapperModal>

@ -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 (
<Avatar
@ -102,14 +102,12 @@ const ProfileAvatar = (props: ProfileAvatarProps): ReactElement => {
};
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 (
<div className="avatar-center">
@ -123,10 +121,7 @@ const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
<div
className="image-upload-section"
role="button"
onClick={async () => {
// void fireInputEvent();
dispatch(updateDisplayPictureModel({}));
}}
onClick={onClick}
data-testid="image-upload-section"
/>
<div
@ -146,6 +141,8 @@ const ProfileHeader = (props: ProfileHeaderProps): ReactElement => {
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<HTMLInputElement>) => {
@ -252,7 +265,7 @@ export const EditProfileDialog = (): ReactElement => {
oldAvatarPath={oldAvatarPath}
profileName={profileName}
ourId={ourId}
fireInputEvent={fireInputEvent}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name-uneditable">
@ -275,7 +288,7 @@ export const EditProfileDialog = (): ReactElement => {
oldAvatarPath={oldAvatarPath}
profileName={profileName}
ourId={ourId}
fireInputEvent={fireInputEvent}
onClick={handleProfileHeaderClick}
setMode={setMode}
/>
<div className="profile-name">

@ -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;

Loading…
Cancel
Save