diff --git a/net/web/src/session/profile/Profile.jsx b/net/web/src/session/profile/Profile.jsx
index e64fbc02..e40b54fe 100644
--- a/net/web/src/session/profile/Profile.jsx
+++ b/net/web/src/session/profile/Profile.jsx
@@ -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 = (
-
+
@@ -40,6 +65,17 @@ export function Profile({ closeProfile }) {
);
+ const editImageFooter = (
+
+ selected(e)} style={{display: 'none'}}/>
+
+
+
+
+
+
+ );
+
return (
{ state.init && state.display === 'xlarge' && (
@@ -72,9 +108,19 @@ export function Profile({ closeProfile }) {
Change Login
+ { state.display === 'small' && (
+
+ )}
)}
+
+
+
);
}
diff --git a/net/web/src/session/profile/Profile.styled.js b/net/web/src/session/profile/Profile.styled.js
index 99af33e2..f2fbc30b 100644
--- a/net/web/src/session/profile/Profile.styled.js
+++ b/net/web/src/session/profile/Profile.styled.js
@@ -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;
+ }
+`
+
diff --git a/net/web/src/session/profile/profileImage/ProfileImage.jsx b/net/web/src/session/profile/profileImage/ProfileImage.jsx
new file mode 100644
index 00000000..eeb78f84
--- /dev/null
+++ b/net/web/src/session/profile/profileImage/ProfileImage.jsx
@@ -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 (
+
+
+
+ )
+}
+
diff --git a/net/web/src/session/profile/profileImage/ProfileImage.styled.js b/net/web/src/session/profile/profileImage/ProfileImage.styled.js
new file mode 100644
index 00000000..e59cfce1
--- /dev/null
+++ b/net/web/src/session/profile/profileImage/ProfileImage.styled.js
@@ -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;
+`;
+
diff --git a/net/web/src/session/profile/useProfile.hook.js b/net/web/src/session/profile/useProfile.hook.js
index 5979a719..6e94aa47 100644
--- a/net/web/src/session/profile/useProfile.hook.js
+++ b/net/web/src/session/profile/useProfile.hook.js
@@ -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 };