more webapp profile refactor

This commit is contained in:
Roland Osborne 2023-01-23 12:20:15 -08:00
parent 5078538a4b
commit 91c5a88096
7 changed files with 71 additions and 89 deletions

View File

@ -1,12 +1,11 @@
import { useRef } from 'react'; import { useRef, useCallback } from 'react';
import { Space, Modal, Button } from 'antd'; import { Space, Modal, Input, Button } from 'antd';
import { ProfileWrapper, EditFooter } from './Profile.styled'; import { ProfileWrapper, ProfileDetailsWrapper, ProfileImageWrapper, EditFooter } from './Profile.styled';
import { useProfile } from './useProfile.hook'; import { useProfile } from './useProfile.hook';
import { ProfileImage } from './profileImage/ProfileImage';
import { ProfileDetails } from './profileDetails/ProfileDetails';
import { Logo } from 'logo/Logo'; import { Logo } from 'logo/Logo';
import { AccountAccess } from './accountAccess/AccountAccess'; import { AccountAccess } from './accountAccess/AccountAccess';
import { LogoutOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { LogoutOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
import Cropper from 'react-easy-crop';
export function Profile({ closeProfile }) { export function Profile({ closeProfile }) {
@ -65,7 +64,7 @@ export function Profile({ closeProfile }) {
<EditFooter> <EditFooter>
<input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selected(e)} style={{display: 'none'}}/> <input type='file' id='file' accept="image/*" ref={imageFile} onChange={e => selected(e)} style={{display: 'none'}}/>
<div className="select"> <div className="select">
<Button key="select" className="select" onClick={() => imageFile.current.click()}>Select Image</Button> <Button key="select" className="pic" onClick={() => imageFile.current.click()}>Select Image</Button>
</div> </div>
<Button key="back" onClick={actions.clearEditProfileImage}>Cancel</Button> <Button key="back" onClick={actions.clearEditProfileImage}>Cancel</Button>
<Button key="save" type="primary" onClick={saveImage} loading={state.busy}>Save</Button> <Button key="save" type="primary" onClick={saveImage} loading={state.busy}>Save</Button>
@ -80,6 +79,11 @@ export function Profile({ closeProfile }) {
</EditFooter> </EditFooter>
); );
const onCropComplete = useCallback((area, crop) => {
actions.setEditImageCrop(crop.width, crop.height, crop.x, crop.y)
// eslint-disable-next-line
}, []);
return ( return (
<ProfileWrapper> <ProfileWrapper>
{ modalContext } { modalContext }
@ -152,11 +156,31 @@ export function Profile({ closeProfile }) {
)} )}
<Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter} <Modal title="Profile Image" centered visible={state.editProfileImage} footer={editImageFooter}
onCancel={actions.clearEditProfileImage}> onCancel={actions.clearEditProfileImage}>
<ProfileImage state={state} actions={actions} />
<ProfileImageWrapper>
<Cropper image={state.editImage} crop={state.crop} zoom={state.zoom} aspect={1}
onCropChange={actions.setCrop} onCropComplete={onCropComplete} onZoomChange={actions.setZoom} />
</ProfileImageWrapper>
</Modal> </Modal>
<Modal title="Profile Details" centered visible={state.editProfileDetails} footer={editDetailsFooter} <Modal title="Profile Details" centered visible={state.editProfileDetails} footer={editDetailsFooter}
onCancel={actions.clearEditProfileDetails}> onCancel={actions.clearEditProfileDetails}>
<ProfileDetails state={state} actions={actions} />
<ProfileDetailsWrapper>
<div class="info">
<Input placeholder="Name" spellCheck="false" onChange={(e) => actions.setEditName(e.target.value)}
defaultValue={state.editName} autocapitalize="word" />
</div>
<div class="info">
<Input placeholder="Location" spellCheck="false" onChange={(e) => actions.setEditLocation(e.target.value)}
defaultValue={state.editLocation} autocapitalize="word" />
</div>
<div class="info">
<Input.TextArea placeholder="Description" onChange={(e) => actions.setEditDescription(e.target.value)}
spellCheck="false" defaultValue={state.editDescription} autoSize={{ minRows: 2, maxRows: 6 }} />
</div>
</ProfileDetailsWrapper>
</Modal> </Modal>
</ProfileWrapper> </ProfileWrapper>
); );

View File

@ -204,4 +204,24 @@ export const EditFooter = styled.div`
flex-grow: 1; flex-grow: 1;
} }
` `
export const ProfileDetailsWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.info {
width: 100%;
padding: 8px;
}
`;
export const ProfileImageWrapper = styled.div`
position: relative;
height: 256px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
`;

View File

@ -1,22 +0,0 @@
import { Input } from 'antd';
import { ProfileDetailsWrapper } from './ProfileDetails.styled';
export function ProfileDetails({ state, actions }) {
return (
<ProfileDetailsWrapper>
<div class="info">
<Input placeholder="Name" spellCheck="false" onChange={(e) => actions.setEditName(e.target.value)}
defaultValue={state.editName} autocapitalize="word" />
</div>
<div class="info">
<Input placeholder="Location" spellCheck="false" onChange={(e) => actions.setEditLocation(e.target.value)}
defaultValue={state.editLocation} autocapitalize="word" />
</div>
<div class="info">
<Input.TextArea placeholder="Description" onChange={(e) => actions.setEditDescription(e.target.value)}
spellCheck="false" defaultValue={state.editDescription} autoSize={{ minRows: 2, maxRows: 6 }} />
</div>
</ProfileDetailsWrapper>
);
}

View File

@ -1,14 +0,0 @@
import styled from 'styled-components';
export const ProfileDetailsWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.info {
width: 100%;
padding: 8px;
}
`;

View File

@ -1,22 +0,0 @@
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)
// eslint-disable-next-line
}, []);
return (
<ProfileImageWrapper>
<Cropper image={state.editImage} crop={crop} zoom={zoom} aspect={1}
onCropChange={setCrop} onCropComplete={onCropComplete} onZoomChange={setZoom} />
</ProfileImageWrapper>
)
}

View File

@ -1,11 +0,0 @@
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

@ -7,9 +7,6 @@ import avatar from 'images/avatar.png';
export function useProfile() { export function useProfile() {
const [state, setState] = useState({ const [state, setState] = useState({
init: false,
editProfileImage: false,
editProfileDetails: false,
handle: null, handle: null,
name: null, name: null,
location: null, location: null,
@ -18,10 +15,14 @@ export function useProfile() {
editName: null, editName: null,
editLocation: null, editLocation: null,
editDescription: null, editDescription: null,
crop: { w: 0, h: 0, x: 0, y: 0 }, editProfileImage: false,
editProfileDetails: false,
clip: { w: 0, h: 0, x: 0, y: 0 },
crop: { x: 0, y: 0},
zoom: 1,
busy: false, busy: false,
searchable: null,
checked: true,
}); });
const IMAGE_DIM = 256; const IMAGE_DIM = 256;
@ -35,17 +36,17 @@ export function useProfile() {
useEffect(() => { useEffect(() => {
if (profile.state.identity.guid) { if (profile.state.identity.guid) {
const { node, name, handle, location, description, image } = profile.state.identity; const { node, name, handle, location, description, image, imageUrl } = profile.state.identity;
let url = !image ? null : profile.state.imageUrl; let url = !image ? null : profile.state.imageUrl;
let editImage = !image ? avatar : url; let editImage = !image ? avatar : url;
updateState({ init: true, name, location, description, node, handle, url, updateState({ name, location, description, node, handle, url,
editName: name, editLocation: location, editDescription: description, editHandle: handle, editImage }); editName: name, editLocation: location, editDescription: description, editHandle: handle, editImage });
} }
}, [profile]); }, [profile.state]);
useEffect(() => { useEffect(() => {
updateState({ display: viewport.state.display }); updateState({ display: viewport.state.display });
}, [viewport]); }, [viewport.state]);
const actions = { const actions = {
logout: app.actions.logout, logout: app.actions.logout,
@ -65,7 +66,7 @@ export function useProfile() {
updateState({ editProfileDetails: false }); updateState({ editProfileDetails: false });
}, },
setEditImageCrop: (w, h, x, y) => { setEditImageCrop: (w, h, x, y) => {
updateState({ crop: { w, h, x, y }}); updateState({ clip: { w, h, x, y }});
}, },
setEditName: (editName) => { setEditName: (editName) => {
updateState({ editName }); updateState({ editName });
@ -76,6 +77,12 @@ export function useProfile() {
setEditDescription: (editDescription) => { setEditDescription: (editDescription) => {
updateState({ editDescription }); updateState({ editDescription });
}, },
setCrop: (crop) => {
updateState({ crop });
},
setZoom: (zoom) => {
updateState({ zoom });
},
setProfileImage: async () => { setProfileImage: async () => {
if(!state.busy) { if(!state.busy) {
updateState({ busy: true }); updateState({ busy: true });
@ -89,7 +96,7 @@ export function useProfile() {
canvas.width = IMAGE_DIM; canvas.width = IMAGE_DIM;
canvas.height = IMAGE_DIM; canvas.height = IMAGE_DIM;
context.imageSmoothingQuality = "medium"; context.imageSmoothingQuality = "medium";
context.drawImage(img, state.crop.x, state.crop.y, state.crop.w, state.crop.h, context.drawImage(img, state.clip.x, state.clip.y, state.clip.w, state.clip.h,
0, 0, IMAGE_DIM, IMAGE_DIM); 0, 0, IMAGE_DIM, IMAGE_DIM);
resolve(canvas.toDataURL()); resolve(canvas.toDataURL());
} }