mirror of
https://github.com/balzack/databag.git
synced 2025-02-15 04:59:16 +00:00
added profile image editing
This commit is contained in:
parent
4ea23d0353
commit
e3de0a43b4
@ -1,15 +1,40 @@
|
|||||||
import { Checkbox } from 'antd';
|
import { useRef } from 'react';
|
||||||
import { ProfileWrapper } from './Profile.styled';
|
import { Modal, Button, Checkbox } from 'antd';
|
||||||
|
import { ProfileWrapper, EditImageFooter } from './Profile.styled';
|
||||||
import { useProfile } from './useProfile.hook';
|
import { useProfile } from './useProfile.hook';
|
||||||
|
import { ProfileImage } from './profileImage/ProfileImage';
|
||||||
import { Logo } from 'logo/Logo';
|
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 }) {
|
export function Profile({ closeProfile }) {
|
||||||
|
|
||||||
const { state, actions } = useProfile();
|
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 = (
|
const Image = (
|
||||||
<div class="logo">
|
<div class="logo" onClick={actions.setEditProfileImage}>
|
||||||
<Logo url={state.url} width={'100%'} radius={8} />
|
<Logo url={state.url} width={'100%'} radius={8} />
|
||||||
<div class="edit">
|
<div class="edit">
|
||||||
<EditOutlined />
|
<EditOutlined />
|
||||||
@ -40,6 +65,17 @@ export function Profile({ closeProfile }) {
|
|||||||
</div>
|
</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 (
|
return (
|
||||||
<ProfileWrapper>
|
<ProfileWrapper>
|
||||||
{ state.init && state.display === 'xlarge' && (
|
{ state.init && state.display === 'xlarge' && (
|
||||||
@ -72,9 +108,19 @@ export function Profile({ closeProfile }) {
|
|||||||
<LockOutlined />
|
<LockOutlined />
|
||||||
<div class="label">Change Login</div>
|
<div class="label">Change Login</div>
|
||||||
</div>
|
</div>
|
||||||
|
{ state.display === 'small' && (
|
||||||
|
<div class="logout">
|
||||||
|
<LogoutOutlined />
|
||||||
|
<div class="label">Logout</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter}
|
||||||
|
onCancel={actions.clearEditProfileImage}>
|
||||||
|
<ProfileImage state={state} actions={actions} />
|
||||||
|
</Modal>
|
||||||
</ProfileWrapper>
|
</ProfileWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -46,20 +46,25 @@ export const ProfileWrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 20vw;
|
width: 20vw;
|
||||||
margin-right: 32px;
|
margin-right: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .edit {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.edit {
|
.edit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
color: ${Colors.link};
|
color: ${Colors.link};
|
||||||
background-color: ${Colors.mask};
|
background-color: ${Colors.white};
|
||||||
cursor: pointer;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,22 +123,27 @@ export const ProfileWrapper = styled.div`
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 80%;
|
width: 60%;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .edit {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.edit {
|
.edit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
color: ${Colors.link};
|
color: ${Colors.link};
|
||||||
background-color: ${Colors.mask};
|
background-color: ${Colors.white};
|
||||||
cursor: pointer;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +190,7 @@ export const ProfileWrapper = styled.div`
|
|||||||
.section {
|
.section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: ${Colors.grey};
|
color: ${Colors.grey};
|
||||||
padding-top: 32px;
|
padding-top: 24px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -209,5 +219,31 @@ export const ProfileWrapper = styled.div`
|
|||||||
padding-left: 8px;
|
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 { ProfileContext } from 'context/ProfileContext';
|
||||||
import { AppContext } from 'context/AppContext';
|
import { AppContext } from 'context/AppContext';
|
||||||
import { ViewportContext } from 'context/ViewportContext';
|
import { ViewportContext } from 'context/ViewportContext';
|
||||||
|
import avatar from 'images/avatar.png';
|
||||||
|
|
||||||
export function useProfile() {
|
export function useProfile() {
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
init: false,
|
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 app = useContext(AppContext);
|
||||||
const viewport = useContext(ViewportContext);
|
const viewport = useContext(ViewportContext);
|
||||||
const profile = useContext(ProfileContext);
|
const profile = useContext(ProfileContext);
|
||||||
@ -21,7 +27,8 @@ export function useProfile() {
|
|||||||
if (profile.state.init) {
|
if (profile.state.init) {
|
||||||
const { node, name, handle, location, description, image } = profile.state.profile;
|
const { node, name, handle, location, description, image } = profile.state.profile;
|
||||||
let url = !image ? null : profile.actions.profileImageUrl();
|
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]);
|
}, [profile]);
|
||||||
|
|
||||||
@ -31,6 +38,57 @@ export function useProfile() {
|
|||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
logout: app.actions.logout,
|
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 };
|
return { state, actions };
|
||||||
|
Loading…
Reference in New Issue
Block a user