mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
added profile image editing
This commit is contained in:
parent
4ea23d0353
commit
e3de0a43b4
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
|
22
net/web/src/session/profile/profileImage/ProfileImage.jsx
Normal file
22
net/web/src/session/profile/profileImage/ProfileImage.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
@ -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;
|
||||
`;
|
||||
|
@ -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 };
|
||||
|
Loading…
Reference in New Issue
Block a user