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