refactor: avatar uploader

This commit is contained in:
Tristan Yang
2022-12-16 21:22:55 +08:00
parent e8cefda0c2
commit af231fc1d5
14 changed files with 49 additions and 39 deletions
+15 -16
View File
@@ -1,17 +1,16 @@
import { useState, useEffect, memo, SyntheticEvent, FC } from "react"; import { useState, useEffect, memo, SyntheticEvent, FC } from "react";
import { getInitials, getInitialsAvatar } from "../utils"; import { getInitials, getInitialsAvatar } from "../utils";
interface Props { interface Props extends Partial<HTMLImageElement> {
className?: string; // className?: string;
alt?: string; // alt?: string;
url?: string; // src?: string;
name?: string; // name?: string;
type?: "user" | "channel"; type?: "user" | "channel";
} }
const Avatar: FC<Props> = ({ url = "", name = "Deleted User", type = "user", ...rest }) => { const Avatar: FC<Props> = ({ src = "", name = "Deleted User", type = "user", ...rest }) => {
const [src, setSrc] = useState(""); const [url, setUrl] = useState("");
const handleError = (err: SyntheticEvent<HTMLImageElement>) => { const handleError = (err: SyntheticEvent<HTMLImageElement>) => {
console.error("load avatar error", err); console.error("load avatar error", err);
const tmp = getInitialsAvatar({ const tmp = getInitialsAvatar({
@@ -19,26 +18,26 @@ const Avatar: FC<Props> = ({ url = "", name = "Deleted User", type = "user", ...
background: type == "channel" ? "#EAECF0" : undefined, background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined foreground: type == "channel" ? "#475467" : undefined
}); });
setSrc(tmp); setUrl(tmp);
}; };
useEffect(() => { useEffect(() => {
if (!url) { if (!src) {
const tmp = getInitialsAvatar({ const tmp = getInitialsAvatar({
initials: getInitials(name), initials: getInitials(name),
background: type == "channel" ? "#EAECF0" : undefined, background: type == "channel" ? "#EAECF0" : undefined,
foreground: type == "channel" ? "#475467" : undefined foreground: type == "channel" ? "#475467" : undefined
}); });
setSrc(tmp); setUrl(tmp);
} else { } else {
setSrc(url); setUrl(src);
} }
}, [url, name]); }, [src, name]);
if (!src) return null; if (!url) return null;
return <img src={src} onError={handleError} {...rest} />; return <img src={url} onError={handleError} {...rest} />;
}; };
export default memo(Avatar, (prev, next) => { export default memo(Avatar, (prev, next) => {
return prev.url == next.url; return prev.src == next.src;
}); });
+17 -4
View File
@@ -53,23 +53,32 @@ const StyledWrapper = styled.div`
} }
} }
.icon { .icon {
display: none;
width: 28px; width: 28px;
height: 28px; height: 28px;
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
} }
&:hover .icon{
display: block;
}
`; `;
type UID = number;
interface Props { interface Props {
uid?: UID,
className?: string,
url?: string; url?: string;
name?: string; name?: string;
type?: "user" | "channel"; type?: "user" | "channel";
disabled?: boolean; disabled?: boolean;
uploadImage: (file: File) => void; uploadImage: (param: File | { uid: number, file: File }) => void;
} }
const AvatarUploader: FC<Props> = ({ const AvatarUploader: FC<Props> = ({
uid,
className = "",
url = "", url = "",
name = "", name = "",
type = "user", type = "user",
@@ -83,14 +92,18 @@ const AvatarUploader: FC<Props> = ({
if (!evt.target.files) return; if (!evt.target.files) return;
const [file] = Array.from(evt.target.files); const [file] = Array.from(evt.target.files);
setUploading(true); setUploading(true);
await uploadImage(file); if (uid) {
await uploadImage({ uid, file });
} else {
await uploadImage(file);
}
setUploading(false); setUploading(false);
}; };
return ( return (
<StyledWrapper> <StyledWrapper className={className}>
<div className="avatar"> <div className="avatar">
<Avatar type={type} url={url} name={name} /> <Avatar type={type} src={url} name={name} className={className} />
{!disabled && ( {!disabled && (
<> <>
<div className="tip">{uploading ? t("status.uploading") : t("action.change_avatar")}</div> <div className="tip">{uploading ? t("status.uploading") : t("action.change_avatar")}</div>
+1 -1
View File
@@ -69,7 +69,7 @@ const Channel: FC<Props> = ({ interactive = true, id, compact = false, avatarSiz
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`} className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
> >
<div className="avatar"> <div className="avatar">
<Avatar type="channel" url={icon} name={"#"} alt="avatar" /> <Avatar type="channel" src={icon} name={"#"} alt="avatar" />
</div> </div>
{!compact && ( {!compact && (
<div className="name"> <div className="name">
@@ -33,7 +33,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
<StyledMsg className="archive" key={idx}> <StyledMsg className="archive" key={idx}>
{user && ( {user && (
<div className="avatar"> <div className="avatar">
<Avatar url={user.avatar} name={user.name} /> <Avatar src={user.avatar} name={user.name} />
</div> </div>
)} )}
<div className="details"> <div className="details">
@@ -63,7 +63,7 @@ const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
<StyledMsg className="archive" key={idx}> <StyledMsg className="archive" key={idx}>
{user && ( {user && (
<div className="avatar"> <div className="avatar">
<Avatar url={user.avatar} name={user.name} /> <Avatar src={user.avatar} name={user.name} />
</div> </div>
)} )}
<div className="details"> <div className="details">
@@ -19,7 +19,7 @@ const PreviewMessage: FC<Props> = ({ mid = 0 }) => {
return ( return (
<StyledWrapper className={`preview`}> <StyledWrapper className={`preview`}>
<div className="avatar"> <div className="avatar">
<Avatar url={avatar} name={name} /> <Avatar src={avatar} name={name} />
</div> </div>
<div className="details"> <div className="details">
<div className="up"> <div className="up">
+1 -1
View File
@@ -177,7 +177,7 @@ const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
onClick={interactive ? handleClick : undefined} onClick={interactive ? handleClick : undefined}
> >
<div className="user"> <div className="user">
<Avatar className="avatar" url={currUser.avatar} name={currUser.name} /> <Avatar className="avatar" src={currUser.avatar} name={currUser.name} />
<span className="name">{currUser.name}</span> <span className="name">{currUser.name}</span>
</div> </div>
<div className="content">{renderContent(data)}</div> <div className="content">{renderContent(data)}</div>
+5 -7
View File
@@ -91,9 +91,8 @@ const Message: FC<IProps> = ({
onContextMenu={readOnly ? undefined : handleContextMenuEvent} onContextMenu={readOnly ? undefined : handleContextMenuEvent}
data-msg-mid={mid} data-msg-mid={mid}
ref={inviewRef} ref={inviewRef}
className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${ className={`message ${readOnly ? "readonly" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
contextMenuVisible ? "contextVisible" : "" } `}
} `}
> >
<Tippy <Tippy
key={_key} key={_key}
@@ -105,7 +104,7 @@ const Message: FC<IProps> = ({
content={<Profile uid={fromUid || 0} type="card" cid={context == "user" ? 0 : contextId} />} content={<Profile uid={fromUid || 0} type="card" cid={context == "user" ? 0 : contextId} />}
> >
<div className="avatar" data-uid={fromUid} ref={avatarRef}> <div className="avatar" data-uid={fromUid} ref={avatarRef}>
<Avatar url={currUser?.avatar} name={currUser?.name} /> <Avatar src={currUser?.avatar} name={currUser?.name} />
</div> </div>
</Tippy> </Tippy>
<ContextMenu <ContextMenu
@@ -118,9 +117,8 @@ const Message: FC<IProps> = ({
> >
<div <div
className="details" className="details"
data-pin-tip={`pinned by ${ data-pin-tip={`pinned by ${pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : ""
pinInfo?.created_by ? usersData[pinInfo.created_by]?.name : "" }`}
}`}
> >
<div className="up"> <div className="up">
<span className="name">{currUser?.name || "Deleted User"}</span> <span className="name">{currUser?.name || "Deleted User"}</span>
+1 -1
View File
@@ -47,7 +47,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
return ( return (
<StyledWrapper className={type}> <StyledWrapper className={type}>
<Avatar className="rounded-full w-20 h-20 object-cover" url={avatar} name={name} /> <Avatar className="rounded-full w-20 h-20 object-cover" src={avatar} name={name} />
<h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2> <h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2>
<span className="text-sm text-[#98a2b3] select-text">{email}</span> <span className="text-sm text-[#98a2b3] select-text">{email}</span>
{/* <p className="intro">{introduction}</p> */} {/* <p className="intro">{introduction}</p> */}
+2 -2
View File
@@ -58,7 +58,7 @@ const User: FC<Props> = ({
onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined} onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined}
> >
<div className="avatar"> <div className="avatar">
<Avatar url={curr.avatar} name={curr.name} alt="avatar" /> <Avatar src={curr.avatar} name={curr.name} alt="avatar" />
<div className={`status ${online ? "online" : "offline"}`}></div> <div className={`status ${online ? "online" : "offline"}`}></div>
</div> </div>
{!compact && <span className="name" title={curr?.name}>{curr?.name}</span>} {!compact && <span className="name" title={curr?.name}>{curr?.name}</span>}
@@ -88,7 +88,7 @@ const User: FC<Props> = ({
onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined} onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined}
> >
<div className="avatar"> <div className="avatar">
<Avatar url={curr.avatar} name={curr.name} alt="avatar" /> <Avatar src={curr.avatar} name={curr.name} alt="avatar" />
<div className={`status ${online ? "online" : "offline"}`}></div> <div className={`status ${online ? "online" : "offline"}`}></div>
</div> </div>
{!compact && <span className="name" title={curr?.name}>{curr?.name}</span>} {!compact && <span className="name" title={curr?.name}>{curr?.name}</span>}
+1 -1
View File
@@ -41,7 +41,7 @@ const Session: FC<IProps> = ({ id, mid }) => {
<li className="session"> <li className="session">
<NavLink className={`nav`} to={`/chat/channel/${id}`}> <NavLink className={`nav`} to={`/chat/channel/${id}`}>
<div className="icon"> <div className="icon">
<Avatar className="icon" type="channel" name={name} url={icon} /> <Avatar className="icon" type="channel" name={name} src={icon} />
</div> </div>
<div className="details"> <div className="details">
<div className="up"> <div className="up">
+1 -1
View File
@@ -117,7 +117,7 @@ const Session: FC<IProps> = ({
{type == "user" ? ( {type == "user" ? (
<User avatarSize={40} compact interactive={false} uid={id} /> <User avatarSize={40} compact interactive={false} uid={id} />
) : ( ) : (
<Avatar className="icon" type="channel" name={name} url={icon} /> <Avatar className="icon" type="channel" name={name} src={icon} />
)} )}
</div> </div>
<div className="details"> <div className="details">
+1 -1
View File
@@ -31,7 +31,7 @@ const User: FC<Props> = ({ uid }) => {
<StyledWrapper> <StyledWrapper>
<NavLink to={`/setting?nav=my_account&f=${pathname}`}> <NavLink to={`/setting?nav=my_account&f=${pathname}`}>
<div className="avatar"> <div className="avatar">
<Avatar url={user.avatar} name={user.name} /> <Avatar src={user.avatar} name={user.name} />
</div> </div>
</NavLink> </NavLink>
</StyledWrapper> </StyledWrapper>
+1 -1
View File
@@ -93,7 +93,7 @@ export default function Filter({ filter, updateFilter }) {
onClick={toggleFilterVisible.bind(null, { from: true })} onClick={toggleFilterVisible.bind(null, { from: true })}
> >
{from && ( {from && (
<Avatar className="avatar" name={userMap[from].name} url={userMap[from].avatar} /> <Avatar className="avatar" name={userMap[from].name} src={userMap[from].avatar} />
)} )}
<span className="txt">{t("from")} {from && userMap[from].name}</span> <span className="txt">{t("from")} {from && userMap[from].name}</span>
<ArrowDown className="arrow" /> <ArrowDown className="arrow" />