added profile image editing

This commit is contained in:
Roland Osborne 2022-08-17 14:57:08 -07:00
parent 4ea23d0353
commit e3de0a43b4
5 changed files with 186 additions and 13 deletions

View File

@ -1,15 +1,40 @@
import { Checkbox } from 'antd';
import { ProfileWrapper } from './Profile.styled';
import { useRef } from 'react';
import { Modal, Button, Checkbox } from 'antd';
import { ProfileWrapper, EditImageFooter } from './Profile.styled';
import { useProfile } from './useProfile.hook';
import { ProfileImage } from './profileImage/ProfileImage';
import { Logo } from 'logo/Logo';
import { DatabaseOutlined, LockOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { LogoutOutlined, DatabaseOutlined, LockOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
export function Profile({ closeProfile }) {
const { state, actions } = useProfile();
const imageFile = useRef(null);
const selected = (e) => {
var reader = new FileReader();
reader.onload = () => {
actions.setEditImage(reader.result);
}
reader.readAsDataURL(e.target.files[0]);
}
const saveImage = async () => {
try {
await actions.setProfileImage();
actions.clearEditProfileImage();
}
catch(err) {
console.log(err);
Modal.error({
title: 'Failed to Save',
content: 'Please try again.',
});
}
}
const Image = (
<div class="logo">
<div class="logo" onClick={actions.setEditProfileImage}>
<Logo url={state.url} width={'100%'} radius={8} />
<div class="edit">
<EditOutlined />
@ -40,6 +65,17 @@ export function Profile({ closeProfile }) {
</div>
);
const editImageFooter = (
<EditImageFooter>
<input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selected(e)} style={{display: 'none'}}/>
<div class="select">
<Button key="select" class="select" onClick={() => imageFile.current.click()}>Select Image</Button>
</div>
<Button key="back" onClick={actions.clearEditProfileImage}>Cancel</Button>
<Button key="save" type="primary" onClick={saveImage}>Save</Button>
</EditImageFooter>
);
return (
<ProfileWrapper>
{ state.init && state.display === 'xlarge' && (
@ -72,9 +108,19 @@ export function Profile({ closeProfile }) {
<LockOutlined />
<div class="label">Change Login</div>
</div>
{ state.display === 'small' && (
<div class="logout">
<LogoutOutlined />
<div class="label">Logout</div>
</div>
)}
</div>
</div>
)}
<Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter}
onCancel={actions.clearEditProfileImage}>
<ProfileImage state={state} actions={actions} />
</Modal>
</ProfileWrapper>
);
}

View File

@ -46,20 +46,25 @@ export const ProfileWrapper = styled.div`
position: relative;
width: 20vw;
margin-right: 32px;
cursor: pointer;
&:hover .edit {
opacity: 1;
}
.edit {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
border-radius: 8px;
width: 24px;
height: 24px;
bottom: 0;
right: 0;
color: ${Colors.link};
background-color: ${Colors.mask};
cursor: pointer;
background-color: ${Colors.white};
opacity: 0.7;
}
}
@ -118,22 +123,27 @@ export const ProfileWrapper = styled.div`
.logo {
position: relative;
width: 80%;
width: 60%;
margin-bottom: 16px;
cursor: pointer;
&:hover .edit {
opacity: 1;
}
.edit {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
border-radius: 8px;
width: 24px;
height: 24px;
bottom: 0;
right: 0;
color: ${Colors.link};
background-color: ${Colors.mask};
cursor: pointer;
background-color: ${Colors.white};
opacity: 0.7;
}
}
@ -180,7 +190,7 @@ export const ProfileWrapper = styled.div`
.section {
width: 100%;
color: ${Colors.grey};
padding-top: 32px;
padding-top: 24px;
font-size: 12px;
display: flex;
justify-content: center;
@ -209,5 +219,31 @@ export const ProfileWrapper = styled.div`
padding-left: 8px;
}
}
.logout {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
color: ${Colors.white};
background-color: ${Colors.primary};
margin-top: 8px;
padding: 8px;
border-radius: 4px;
.label {
padding-left: 8px;
}
}
}
`
export const EditImageFooter = styled.div`
width: 100%;
display: flex;
.select {
display: flex;
flex-grow: 1;
}
`

View File

@ -0,0 +1,22 @@
import avatar from 'images/avatar.png';
import { useState, useCallback } from 'react';
import Cropper from 'react-easy-crop'
import { ProfileImageWrapper } from './ProfileImage.styled';
export function ProfileImage({ state, actions }) {
const [crop, setCrop] = useState({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const onCropComplete = useCallback((area, crop) => {
actions.setEditImageCrop(crop.width, crop.height, crop.x, crop.y)
});
return (
<ProfileImageWrapper>
<Cropper image={state.editImage} crop={crop} zoom={zoom} aspect={1}
onCropChange={setCrop} onCropComplete={onCropComplete} onZoomChange={setZoom} />
</ProfileImageWrapper>
)
}

View File

@ -0,0 +1,11 @@
import styled from 'styled-components';
export const ProfileImageWrapper = styled.div`
position: relative;
height: 256px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
`;

View File

@ -2,13 +2,19 @@ import { useState, useEffect, useContext } from 'react';
import { ProfileContext } from 'context/ProfileContext';
import { AppContext } from 'context/AppContext';
import { ViewportContext } from 'context/ViewportContext';
import avatar from 'images/avatar.png';
export function useProfile() {
const [state, setState] = useState({
init: false,
editProfileImage: false,
editImage: null,
crop: { w: 0, h: 0, x: 0, y: 0 },
busy: false,
});
const IMAGE_DIM = 256;
const app = useContext(AppContext);
const viewport = useContext(ViewportContext);
const profile = useContext(ProfileContext);
@ -21,7 +27,8 @@ export function useProfile() {
if (profile.state.init) {
const { node, name, handle, location, description, image } = profile.state.profile;
let url = !image ? null : profile.actions.profileImageUrl();
updateState({ init: true, name, node, handle, url, location, description });
let editImage = !image ? avatar : url;
updateState({ init: true, name, node, handle, url, editImage, location, description });
}
}, [profile]);
@ -31,6 +38,57 @@ export function useProfile() {
const actions = {
logout: app.actions.logout,
setEditImage: (value) => {
updateState({ editImage: value });
},
setEditProfileImage: () => {
updateState({ editProfileImage: true });
},
clearEditProfileImage: () => {
updateState({ editProfileImage: false });
},
setEditImageCrop: (w, h, x, y) => {
updateState({ crop: { w, h, x, y }});
},
setProfileImage: async () => {
console.log("CHECK1");
if(!state.busy) {
updateState({ busy: true });
try {
const processImg = () => {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => {
var canvas = document.createElement("canvas");
var context = canvas.getContext('2d');
canvas.width = IMAGE_DIM;
canvas.height = IMAGE_DIM;
context.imageSmoothingQuality = "medium";
context.drawImage(img, state.crop.x, state.crop.y, state.crop.w, state.crop.h,
0, 0, IMAGE_DIM, IMAGE_DIM);
resolve(canvas.toDataURL());
}
img.onerror = reject;
img.src = state.editImage;
});
};
let dataUrl = await processImg();
let data = dataUrl.split(",")[1];
console.log("CHECK2");
await profile.actions.setProfileImage(data);
console.log("CHECK3");
updateState({ busy: false });
}
catch (err) {
console.log(err);
updateState({ busy: false });
throw new Error('failed to save profile image');
}
}
else {
throw new Error('save in progress');
}
},
};
return { state, actions };