Allowing to view the own clients avatar

master
WolverinDEV 2021-03-24 17:48:01 +01:00
parent 8d25697e98
commit dbb8039f64
4 changed files with 133 additions and 56 deletions

View File

@ -1,6 +1,7 @@
# Changelog: # Changelog:
* **24.03.21** * **24.03.21**
- Improved the avatar upload modal (now way more intuitive) - Improved the avatar upload modal (now way more intuitive)
- Fixed a bug which cause client avatars to be stuck within the loading screen
* **23.03.21** * **23.03.21**
- Made the permission editor popoutable - Made the permission editor popoutable

View File

@ -206,6 +206,11 @@ $bot_thumbnail_height: 9em;
&:hover { &:hover {
opacity: .75; opacity: .75;
} }
&.disabled {
opacity: 0!important;
pointer-events: none!important;
}
} }
&.editable { &.editable {

View File

@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import {useContext, useEffect, useState} from "react"; import {useContext, useEffect, useRef, useState} from "react";
import { import {
ClientCountryInfo, ClientCountryInfo,
ClientForumInfo, ClientForumInfo,
@ -25,31 +25,60 @@ import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
import {getIconManager} from "tc-shared/file/Icons"; import {getIconManager} from "tc-shared/file/Icons";
import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon"; import {RemoteIconRenderer} from "tc-shared/ui/react-elements/Icon";
import {CountryCode} from "tc-shared/ui/react-elements/CountryCode"; import {CountryCode} from "tc-shared/ui/react-elements/CountryCode";
import {getKeyBoard, SpecialKey} from "tc-shared/PPTListener";
const cssStyle = require("./ClientInfoRenderer.scss"); const cssStyle = require("./ClientInfoRenderer.scss");
const EventsContext = React.createContext<Registry<ClientInfoEvents>>(undefined); const EventsContext = React.createContext<Registry<ClientInfoEvents>>(undefined);
const ClientContext = React.createContext<OptionalClientInfoInfo>(undefined); const ClientContext = React.createContext<OptionalClientInfoInfo>(undefined);
const Avatar = React.memo(() => { const EditOverlay = React.memo(() => {
const events = useContext(EventsContext); const events = useContext(EventsContext);
const refContainer = useRef<HTMLDivElement>();
useEffect(() => {
const keyboard = getKeyBoard();
return keyboard.registerHook({
keyShift: true,
callbackPress: () => {
refContainer.current?.classList.add(cssStyle.disabled);
},
callbackRelease: () => {
refContainer.current?.classList.remove(cssStyle.disabled);
}
});
}, []);
return (
<div
ref={refContainer}
className={cssStyle.edit}
onClick={() => events.fire("action_edit_avatar")}
>
<ClientIconRenderer icon={ClientIcon.AvatarUpload} className={cssStyle.icon} />
</div>
);
});
const Avatar = React.memo(() => {
const client = useContext(ClientContext); const client = useContext(ClientContext);
let avatar: "loading" | ClientAvatar; let avatar: "loading" | ClientAvatar;
if(client.type === "none") { if(client.type === "none") {
avatar = "loading"; avatar = "loading";
} else { } else {
avatar = getGlobalAvatarManagerFactory().getManager(client.handlerId).resolveClientAvatar({ id: client.clientId, clientUniqueId: client.clientUniqueId, database_id: client.clientDatabaseId }); avatar = getGlobalAvatarManagerFactory().getManager(client.handlerId)
.resolveClientAvatar({ id: client.clientId, clientUniqueId: client.clientUniqueId, database_id: client.clientDatabaseId });
} }
return ( return (
<div className={cssStyle.containerAvatar + " " + (client.type === "self" ? cssStyle.editable : undefined)}> <div className={cssStyle.containerAvatar + " " + (client.type === "self" ? cssStyle.editable : undefined)}>
<div className={cssStyle.avatar}> <div className={cssStyle.avatar}>
<AvatarRenderer avatar={avatar} className={cssStyle.avatarImage + " " + (avatar === "loading" ? cssStyle.loading : "")} /> <AvatarRenderer avatar={avatar} className={cssStyle.avatarImage + " " + (avatar === "loading" ? cssStyle.loading : "")} key={avatar === "loading" ? "loading" : avatar.clientAvatarId} />
</div>
<div className={cssStyle.edit} onClick={() => events.fire("action_edit_avatar")}>
<ClientIconRenderer icon={ClientIcon.AvatarUpload} className={cssStyle.icon} />
</div> </div>
<EditOverlay />
</div> </div>
) )
}); });

View File

@ -1,77 +1,119 @@
import * as React from "react"; import * as React from "react";
import {useEffect, useState} from "react"; import {useState} from "react";
import {ClientAvatar, kDefaultAvatarImage, kLoadingAvatarImage} from "tc-shared/file/Avatars"; import {ClientAvatar, kDefaultAvatarImage, kLoadingAvatarImage} from "tc-shared/file/Avatars";
import * as image_preview from "tc-shared/ui/frames/ImagePreview"; import {useTr} from "tc-shared/ui/react-elements/Helper";
import {showImagePreview} from "tc-shared/ui/frames/ImagePreview";
const ImageStyle = { height: "100%", width: "100%", cursor: "pointer" }; const ImageStyle = { height: "100%", width: "100%", cursor: "pointer" };
export const AvatarRenderer = React.memo((props: { avatar: ClientAvatar | "loading" | "default", className?: string, alt?: string }) => {
let [ revision, setRevision ] = useState(0); const AvatarLoadingImage = React.memo((props: { alt?: string }) => (
<img
draggable={false}
alt={typeof props.alt === "string" ? props.alt : tr("loading")}
title={useTr("loading avatar")}
src={kLoadingAvatarImage}
style={ImageStyle}
/>
));
const AvatarDefaultImage = React.memo((props: { className?: string, alt?: string }) => (
<img
draggable={false}
src={kDefaultAvatarImage}
alt={typeof props.alt === "string" ? props.alt : tr("default avatar")}
color={props.className}
onClick={event => {
if(event.isDefaultPrevented()) {
return;
}
event.preventDefault();
showImagePreview(kDefaultAvatarImage, undefined);
}}
/>
));
const ClientAvatarRenderer = React.memo((props: { avatar: ClientAvatar, className?: string, alt?: string }) => {
const [ , setRevision ] = useState(0);
let image; let image;
let avatar: ClientAvatar; switch (props.avatar.getState()) {
if(props.avatar === "loading") { case "unset":
image = <img draggable={false} src={kLoadingAvatarImage} alt={tr("loading")}/>; image = (
} else if(props.avatar === "default") { <AvatarDefaultImage key={"default"} />
image = <img draggable={false} src={kDefaultAvatarImage} alt={tr("default avatar")} />; );
} else { break;
const imageUrl = props.avatar.getAvatarUrl();
switch (props.avatar.getState()) {
case "unset":
image = <img
key={"default"}
title={tr("default avatar")}
alt={typeof props.alt === "string" ? props.alt : tr("default avatar")}
src={props.avatar.getAvatarUrl()}
style={ImageStyle}
onClick={event => {
if(event.isDefaultPrevented())
return;
event.preventDefault(); case "loading":
image_preview.showImagePreview(imageUrl, undefined); image = (
}} <AvatarLoadingImage key={"loading"} />
draggable={false} );
/>; break;
break;
case "loaded": case "loaded":
image = <img const imageUrl = props.avatar.getAvatarUrl();
image = (
<img
key={"user-" + props.avatar.getAvatarHash()} key={"user-" + props.avatar.getAvatarHash()}
alt={typeof props.alt === "string" ? props.alt : tr("user avatar")} alt={typeof props.alt === "string" ? props.alt : tr("user avatar")}
title={tr("user avatar")} title={tr("user avatar")}
src={imageUrl} src={imageUrl}
style={ImageStyle} style={ImageStyle}
onClick={event => { onClick={event => {
if(event.isDefaultPrevented()) if(event.isDefaultPrevented()) {
return; return;
}
event.preventDefault(); event.preventDefault();
image_preview.showImagePreview(imageUrl, undefined); showImagePreview(imageUrl, undefined);
}} }}
draggable={false} draggable={false}
/>; />
break; );
break;
case "errored": case "errored":
image = <img draggable={false} key={"error"} alt={typeof props.alt === "string" ? props.alt : tr("error")} title={tr("avatar failed to load:\n") + props.avatar.getLoadError()} src={imageUrl} style={ImageStyle} />; image = (
break; <img
draggable={false}
key={"error"}
alt={typeof props.alt === "string" ? props.alt : tr("error")}
title={tr("avatar failed to load:\n") + props.avatar.getLoadError()}
style={ImageStyle}
/>
);
break;
case "loading": case undefined:
image = <img draggable={false} key={"loading"} alt={typeof props.alt === "string" ? props.alt : tr("loading")} title={tr("loading avatar")} src={kLoadingAvatarImage} style={ImageStyle} />; break;
break;
case undefined:
break;
}
avatar = props.avatar;
} }
useEffect(() => avatar && avatar.events.on("avatar_state_changed", () => setRevision(revision + 1)), [ props.avatar ]); props.avatar.events.reactUse("avatar_state_changed", () => {
setRevision(performance.now());
}, undefined, []);
return image;
});
export const AvatarRenderer = React.memo((props: { avatar: ClientAvatar | "loading" | "default", className?: string, alt?: string }) => {
let body;
if(props.avatar === "loading") {
body = (
<AvatarLoadingImage key={"loading"} {...props} />
);
} else if(props.avatar === "default") {
body = (
<AvatarDefaultImage key={"default"} {...props} />
);
} else if(props.avatar instanceof ClientAvatar) {
body = (
<ClientAvatarRenderer key={"user-avatar"} avatar={props.avatar} className={props.className} alt={props.alt} />
);
}
return ( return (
<div className={props.className} style={{ overflow: "hidden" }}> <div className={props.className} style={{ overflow: "hidden" }}>
{image} {body}
</div> </div>
) );
}); });