import {AbstractModal} from "tc-shared/ui/react-elements/modal/Definitions"; import React, {useContext} from "react"; import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n"; import {IpcRegistryDescription, Registry} from "tc-events"; import { CurrentAvatarState, ModalAvatarUploadEvents, ModalAvatarUploadVariables } from "tc-shared/ui/modal/avatar-upload/Definitions"; import {UiVariableConsumer} from "tc-shared/ui/utils/Variable"; import {createIpcUiVariableConsumer, IpcVariableDescriptor} from "tc-shared/ui/utils/IpcVariable"; import {Button} from "tc-shared/ui/react-elements/Button"; import {requestFile} from "tc-shared/file/Utils"; import {network} from "tc-shared/ui/frames/chat"; import {LoadingDots} from "tc-shared/ui/react-elements/LoadingDots"; import {getOwnAvatarStorage} from "tc-shared/file/OwnAvatarStorage"; import {createErrorModal} from "tc-shared/ui/elements/Modal"; import {joinClassList, useDependentState} from "tc-shared/ui/react-elements/Helper"; import kDefaultAvatarUrl from "../../../../img/style/avatar.png"; import byteSizeToString = network.binarySizeToString; const ServerUniqueIdContext = React.createContext(undefined); const EventContext = React.createContext>(undefined); const VariablesContext = React.createContext>(undefined); const CurrentAvatarContext = React.createContext(undefined); const cssStyle = require("./Renderer.scss"); const AvatarFileName = React.memo(() => { const currentAvatar = useContext(CurrentAvatarContext); switch (currentAvatar.status) { case "loading": return (
Loading avatar
); case "server": case "unset": return (
No avatar selected
); case "available": case "exceeds-max-size": return (
{currentAvatar.fileName}
); default: throw "invalid avatar state"; } }); const SizeLimitRenderer = React.memo((props: { byteSize: number }) => { if(props.byteSize === -1) { return unlimited; } else { return {byteSizeToString(props.byteSize)}; } }); const MaxAvatarSize = React.memo(() => { const variables = useContext(VariablesContext); const currentAvatar = useContext(CurrentAvatarContext); const maxSize = variables.useReadOnly("maxAvatarSize", undefined); if(maxSize.status === "loading") { return (
Maximal avatar size: loading
) } else if(currentAvatar.status === "loading" ||currentAvatar.status === "unset" || currentAvatar.status === "server") { return (
); } else if(currentAvatar.status === "available") { return (
{byteSizeToString(currentAvatar.fileSize)}
); } else if(currentAvatar.status === "exceeds-max-size") { return (
{byteSizeToString(currentAvatar.fileSize)} {maxSize.value === -1 ? unlimited : byteSizeToString(maxSize.value)}
); } else { throw "invalid avatar state"; } }) const AvatarSelect = React.memo(() => { const currentAvatar = useContext(CurrentAvatarContext); const events = useContext(EventContext); return (
) }); const AvatarPreview = React.memo((props: { size: "client-info" | "chat" }) => { const currentAvatar = useContext(CurrentAvatarContext); let sizeClass; let name; switch (props.size) { case "client-info": sizeClass = cssStyle.sizeChatInfo; name = Client info; break; case "chat": sizeClass = cssStyle.sizeChat; name = Chat Avatar; break; } let imageUrl; if(currentAvatar.status === "available" || currentAvatar.status === "exceeds-max-size") { imageUrl = currentAvatar.resourceUrl; } else if(currentAvatar.status === "server") { imageUrl = currentAvatar.resourceUrl; } else { imageUrl = kDefaultAvatarUrl; } const [ boundClass, setBoundClass ] = useDependentState(() => undefined, [ imageUrl ]); return (
{""} { const imageElement = event.currentTarget; if(imageElement.naturalHeight > imageElement.naturalWidth) { setBoundClass(cssStyle.heightBound); } else { setBoundClass(cssStyle.widthBound); } }} />
{name}
) }); const PreviewTitle = React.memo(() => { const currentAvatar = useContext(CurrentAvatarContext); if(currentAvatar.status === "server") { return Current Avatar; } else { return Preview; } }); const AvatarPreviewContainer = React.memo(() => (
)); const Buttons = React.memo(() => { const events = useContext(EventContext); const currentAvatar = useContext(CurrentAvatarContext); let enableReset, enableUpload; switch (currentAvatar.status) { case "exceeds-max-size": enableReset = currentAvatar.serverHasAvatar; enableUpload = false; break; case "loading": case "unset": enableReset = false; enableUpload = false; break; case "available": enableReset = true; enableUpload = true; break; case "server": enableReset = true; enableUpload = false; break; } return (
) }); const CurrentAvatarProvider = React.memo((props: { children }) => { const variables = useContext(VariablesContext); const avatar = variables.useReadOnly("currentAvatar", undefined, { status: "loading" }); return ( {props.children} ); }); class ModalAvatarUpload extends AbstractModal { private readonly serverUniqueId: string; private readonly events: Registry; private readonly variables: UiVariableConsumer; constructor(events: IpcRegistryDescription, variables: IpcVariableDescriptor, serverUniqueId: string) { super(); this.serverUniqueId = serverUniqueId; this.events = Registry.fromIpcDescription(events); this.variables = createIpcUiVariableConsumer(variables); this.events.on("notify_avatar_load_error", event => { createErrorModal(tr("Failed to load avatar"), event.error).open(); }); this.events.on("action_open_select", async () => { this.events.fire("action_file_cache_loading"); const files = await requestFile({ multiple: false, accept: ".svg, .png, .jpg, .jpeg, gif" }); if(files.length !== 1) { this.events.fire("action_file_cache_loading_finished", { success: false }); return; } const result = await getOwnAvatarStorage().updateAvatar(serverUniqueId, "uploading", files[0]); let succeeded; if(result.status === "success") { succeeded = true; } else if(result.status === "error") { createErrorModal(tr("Failed to load avatar"), tra("Failed to load avatar: {}", result.reason)).open(); succeeded = false; } else if(result.status === "cache-unavailable") { createErrorModal(tr("Failed to load avatar"), tra("Failed to load avatar:{:br:}Own avatar cach unavailable.")).open(); succeeded = false; } else { succeeded = false; } this.events.fire("action_file_cache_loading_finished", { success: succeeded }); }); } protected onDestroy() { super.onDestroy(); this.events.destroy(); this.variables.destroy(); } renderBody(): React.ReactElement { return (
); } renderTitle(): string | React.ReactElement { return Avatar upload; } } export default ModalAvatarUpload;