diff --git a/net/web/src/AppContext/fetchUtil.js b/net/web/src/AppContext/fetchUtil.js index d7afd771..2266d1ab 100644 --- a/net/web/src/AppContext/fetchUtil.js +++ b/net/web/src/AppContext/fetchUtil.js @@ -8,8 +8,8 @@ function checkResponse(response) { } } -export function getProfileImageUrl(token) { - return '/profile/image?agent=' + token +export function getProfileImageUrl(token, revision) { + return '/profile/image?agent=' + token + "&revision=" + revision } async function fetchWithTimeout(url, options) { @@ -61,6 +61,12 @@ export async function setProfileData(token, name, location, description) { return await profile.json() } +export async function setProfileImage(token, image) { + let profile = await fetchWithTimeout('/profile/image?agent=' + token, { method: 'PUT', body: JSON.stringify(image), timeout: FETCH_TIMEOUT }); + checkResponse(profile) + return await profile.json() +} + export async function getGroups(token, revision) { let param = "?agent=" + token if (revision != null) { diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 90bcdd76..73c46c58 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -1,5 +1,5 @@ import { useEffect, useState, useRef } from 'react'; -import { setProfileData, getProfileImageUrl, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; +import { setProfileImage, setProfileData, getProfileImageUrl, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; async function updateProfile(token, updateData) { let profile = await getProfile(token); @@ -71,10 +71,13 @@ export function useAppContext() { appLogout(updateState, clearWebsocket); resetData(); }, - setProfile: async (name, location, description) => { + setProfileData: async (name, location, description) => { await setProfileData(state.token, name, location, description); }, - profileImageUrl: () => getProfileImageUrl(state.token) + setProfileImage: async (image) => { + await setProfileImage(state.token, image); + }, + profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision) } const adminActions = { diff --git a/net/web/src/User/Profile/Profile.jsx b/net/web/src/User/Profile/Profile.jsx index a2a8c61e..ca9e9186 100644 --- a/net/web/src/User/Profile/Profile.jsx +++ b/net/web/src/User/Profile/Profile.jsx @@ -45,11 +45,17 @@ export function Profile(props) { } const onProfileSave = async () => { - if (await actions.setModalProfile()) { + if (await actions.setProfileData()) { setInfoVisible(false); } } + const onImageSave = async () => { + if (await actions.setProfileImage()) { + setLogoVisible(false); + } + } + const onSelectImage = () => { imageFile.current.click(); }; @@ -64,12 +70,12 @@ export function Profile(props) { const Footer = ( - selected(e)} style={{display: 'none'}}/> + selected(e)} style={{display: 'none'}}/>
- +
); @@ -88,7 +94,7 @@ export function Profile(props) {
setInfoVisible(true)}> - detail: + details:
@@ -99,7 +105,7 @@ export function Profile(props) {
- onProfileSave()} onCancel={() => setInfoVisible(false)}> diff --git a/net/web/src/User/Profile/Profile.styled.js b/net/web/src/User/Profile/Profile.styled.js index 9f7e1a6e..2a26544d 100644 --- a/net/web/src/User/Profile/Profile.styled.js +++ b/net/web/src/User/Profile/Profile.styled.js @@ -84,6 +84,10 @@ export const ProfileWrapper = styled.div` position: absolute; padding-right: 8px; cursor: pointer; + background: #f6f5ed; + padding-left: 8px; + border-radius: 4px; + border: 1px solid #dddddd; } .detailedit { @@ -98,7 +102,7 @@ export const ProfileWrapper = styled.div` .label { padding-right: 8px; - font-size: 1.2em; + font-size: 1em; font-weight: bold; color: #888888; } diff --git a/net/web/src/User/Profile/ProfileImage/ProfileImage.jsx b/net/web/src/User/Profile/ProfileImage/ProfileImage.jsx index 99337443..1e0bcd43 100644 --- a/net/web/src/User/Profile/ProfileImage/ProfileImage.jsx +++ b/net/web/src/User/Profile/ProfileImage/ProfileImage.jsx @@ -1,15 +1,15 @@ import React, { useState, useCallback } from 'react'; import Cropper from 'react-easy-crop' import { UserOutlined } from '@ant-design/icons'; -import { ProfileImageWrapper, ProfileDefaultImage } from './ProfileImage.styled'; +import { ProfileSpin, ProfileImageWrapper, ProfileDefaultImage } from './ProfileImage.styled'; export function ProfileImage({ state, actions }) { const [crop, setCrop] = useState({ x: 0, y: 0 }) const [zoom, setZoom] = useState(1) - const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => { - console.log("crop complete"); + const onCropComplete = useCallback((area, crop) => { + actions.setModalCrop(crop.width, crop.height, crop.x, crop.y) }); const Logo = () => { @@ -19,15 +19,12 @@ export function ProfileImage({ state, actions }) { return <> } - const onSelect = () => { - console.log("ON SELECT"); - } - return ( - onSelect()} image={state.modalImage} crop={crop} zoom={zoom} aspect={1} + + ) } diff --git a/net/web/src/User/Profile/ProfileImage/ProfileImage.styled.js b/net/web/src/User/Profile/ProfileImage/ProfileImage.styled.js index 416a02a0..046a154d 100644 --- a/net/web/src/User/Profile/ProfileImage/ProfileImage.styled.js +++ b/net/web/src/User/Profile/ProfileImage/ProfileImage.styled.js @@ -1,3 +1,4 @@ +import { Spin } from 'antd'; import styled from 'styled-components'; export const ProfileImageWrapper = styled.div` @@ -23,3 +24,8 @@ export const ProfileDefaultImage = styled.div` cursor: pointer; `; +export const ProfileSpin = styled(Spin)` + position: absolute; + x-index: 10; +`; + diff --git a/net/web/src/User/Profile/useProfile.hook.js b/net/web/src/User/Profile/useProfile.hook.js index b99a501c..5ca62637 100644 --- a/net/web/src/User/Profile/useProfile.hook.js +++ b/net/web/src/User/Profile/useProfile.hook.js @@ -15,6 +15,7 @@ export function useProfile() { modalLocation: '', modalDescription: '', modalImage: null, + crop: { w :0, h: 0, x: 0, y: 0 } }); const navigate = useNavigate(); @@ -40,12 +41,15 @@ export function useProfile() { setModalImage: (value) => { updateState({ modalImage: value }); }, - setModalProfile: async () => { + setModalCrop: (w, h, x, y) => { + updateState({ crop: { w: w, h: h, x: x, y: y } }); + }, + setProfileData: async () => { let set = false if(!state.modalBusy) { updateState({ modalBusy: true }); try { - await app.actions.setProfile(state.modalName, state.modalLocation, state.modalDescription); + await app.actions.setProfileData(state.modalName, state.modalLocation, state.modalDescription); set = true } catch (err) { @@ -55,6 +59,39 @@ export function useProfile() { } return set }, + setProfileImage: async () => { + let set = false + if(!state.modalBusy) { + updateState({ modalBusy: 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 = state.crop.w; + canvas.height = state.crop.h; + context.drawImage(img, state.crop.x, state.crop.y, state.crop.w, state.crop.h, + 0, 0, state.crop.w, state.crop.h); + resolve(canvas.toDataURL()); + } + img.onerror = reject; + img.src = state.modalImage; + }); + }; + let dataUrl = await processImg(); + let data = dataUrl.split(",")[1]; + await app.actions.setProfileImage(data); + set = true + } + catch (err) { + window.alert(err); + } + updateState({ modalBusy: false }); + } + return set; + }, }; useEffect(() => { diff --git a/net/web/src/User/SideBar/Identity/Identity.jsx b/net/web/src/User/SideBar/Identity/Identity.jsx index 14831b9a..bf1abd3c 100644 --- a/net/web/src/User/SideBar/Identity/Identity.jsx +++ b/net/web/src/User/SideBar/Identity/Identity.jsx @@ -10,6 +10,7 @@ export function Identity() { const { state, actions } = useIdentity() const Logo = () => { +console.log(state); if (state.imageUrl != null) { if (state.imageUrl === '') { return