diff --git a/src/Avatar.module.css b/src/Avatar.module.css index 52b20a5..7e110d4 100644 --- a/src/Avatar.module.css +++ b/src/Avatar.module.css @@ -56,4 +56,5 @@ width: 90px; height: 90px; border-radius: 90px; + font-size: 48px; } diff --git a/src/UserMenuContainer.jsx b/src/UserMenuContainer.jsx index eecb657..18d52db 100644 --- a/src/UserMenuContainer.jsx +++ b/src/UserMenuContainer.jsx @@ -43,14 +43,7 @@ export function UserMenuContainer({ preventNavigation }) { displayName || (userName ? userName.replace("@", "") : undefined) } /> - {modalState.isOpen && ( - - )} + {modalState.isOpen && } ); } diff --git a/src/icons/Edit.svg b/src/icons/Edit.svg new file mode 100644 index 0000000..b960040 --- /dev/null +++ b/src/icons/Edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/input/AvatarInputField.jsx b/src/input/AvatarInputField.jsx new file mode 100644 index 0000000..1d1569d --- /dev/null +++ b/src/input/AvatarInputField.jsx @@ -0,0 +1,78 @@ +import { useObjectRef } from "@react-aria/utils"; +import React, { useEffect } from "react"; +import { useCallback } from "react"; +import { useState } from "react"; +import { forwardRef } from "react"; +import { Avatar } from "../Avatar"; +import { Button } from "../button"; +import classNames from "classnames"; +import { ReactComponent as EditIcon } from "../icons/Edit.svg"; +import styles from "./AvatarInputField.module.css"; + +export const AvatarInputField = forwardRef( + ( + { id, label, className, avatarUrl, displayName, onRemoveAvatar, ...rest }, + ref + ) => { + const [removed, setRemoved] = useState(false); + const [objUrl, setObjUrl] = useState(null); + + const fileInputRef = useObjectRef(ref); + + useEffect(() => { + const onChange = (e) => { + if (e.target.files.length > 0) { + setObjUrl(URL.createObjectURL(e.target.files[0])); + setRemoved(false); + } else { + setObjUrl(null); + } + }; + + fileInputRef.current.addEventListener("change", onChange); + + return () => { + if (fileInputRef.current) { + fileInputRef.current.removeEventListener("change", onChange); + } + }; + }); + + const onPressRemoveAvatar = useCallback(() => { + setRemoved(true); + onRemoveAvatar(); + }, [onRemoveAvatar]); + + return ( +
+
+ + + +
+ +
+ ); + } +); diff --git a/src/input/AvatarInputField.module.css b/src/input/AvatarInputField.module.css new file mode 100644 index 0000000..53f31cc --- /dev/null +++ b/src/input/AvatarInputField.module.css @@ -0,0 +1,41 @@ +.avatarInputField { + display: flex; + flex-direction: column; + justify-content: center; +} + +.avatarContainer { + position: relative; + margin-bottom: 8px; +} + +.fileInput { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +.fileInput:focus + .fileInputButton { + outline: auto; +} + +.fileInputButton { + position: absolute; + bottom: 11px; + right: -4px; + background-color: var(--bgColor4); + width: 20px; + height: 20px; + border-radius: 10px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.removeButton { + color: #0dbd8b; +} diff --git a/src/profile/ProfileModal.jsx b/src/profile/ProfileModal.jsx index 8207c6c..1f757dd 100644 --- a/src/profile/ProfileModal.jsx +++ b/src/profile/ProfileModal.jsx @@ -3,22 +3,25 @@ import { Button } from "../button"; import { useProfile } from "./useProfile"; import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { Modal, ModalContent } from "../Modal"; +import { AvatarInputField } from "../input/AvatarInputField"; +import styles from "./ProfileModal.module.css"; -export function ProfileModal({ - client, - isAuthenticated, - isPasswordlessUser, - ...rest -}) { +export function ProfileModal({ client, ...rest }) { const { onClose } = rest; const { success, error, loading, displayName: initialDisplayName, + avatarUrl, saveProfile, } = useProfile(client); const [displayName, setDisplayName] = useState(initialDisplayName || ""); + const [removeAvatar, setRemoveAvatar] = useState(false); + + const onRemoveAvatar = useCallback(() => { + setRemoveAvatar(true); + }, []); const onChangeDisplayName = useCallback( (e) => { @@ -37,9 +40,10 @@ export function ProfileModal({ saveProfile({ displayName, avatar: avatar && avatar.size > 0 ? avatar : undefined, + removeAvatar: removeAvatar && (!avatar || avatar.size === 0), }); }, - [saveProfile] + [saveProfile, removeAvatar] ); useEffect(() => { @@ -52,6 +56,16 @@ export function ProfileModal({
+ + + - {isAuthenticated && ( - - - - )} {error && ( {error.message} diff --git a/src/profile/ProfileModal.module.css b/src/profile/ProfileModal.module.css index e69de29..6ca1acf 100644 --- a/src/profile/ProfileModal.module.css +++ b/src/profile/ProfileModal.module.css @@ -0,0 +1,3 @@ +.avatarFieldRow { + justify-content: center; +} diff --git a/src/profile/useProfile.js b/src/profile/useProfile.js index 2483774..d2e7834 100644 --- a/src/profile/useProfile.js +++ b/src/profile/useProfile.js @@ -44,7 +44,7 @@ export function useProfile(client) { }, [client]); const saveProfile = useCallback( - async ({ displayName, avatar }) => { + async ({ displayName, avatar, removeAvatar }) => { if (client) { setState((prev) => ({ ...prev, @@ -58,7 +58,9 @@ export function useProfile(client) { let mxcAvatarUrl; - if (avatar) { + if (removeAvatar) { + await client.setAvatarUrl(""); + } else if (avatar) { mxcAvatarUrl = await client.uploadContent(avatar); await client.setAvatarUrl(mxcAvatarUrl); } @@ -66,7 +68,9 @@ export function useProfile(client) { setState((prev) => ({ ...prev, displayName, - avatarUrl: mxcAvatarUrl + avatarUrl: removeAvatar + ? null + : mxcAvatarUrl ? getAvatarUrl(client, mxcAvatarUrl) : prev.avatarUrl, loading: false, diff --git a/src/room/LobbyView.jsx b/src/room/LobbyView.jsx index e63022f..fbeabcd 100644 --- a/src/room/LobbyView.jsx +++ b/src/room/LobbyView.jsx @@ -10,7 +10,6 @@ import { OverflowMenu } from "./OverflowMenu"; import { UserMenuContainer } from "../UserMenuContainer"; import { Body, Link } from "../typography/Typography"; import { Avatar } from "../Avatar"; -import { getAvatarUrl } from "../matrix-utils"; import { useProfile } from "../profile/useProfile"; import useMeasure from "react-use-measure"; import { ResizeObserver } from "@juggle/resize-observer"; @@ -86,7 +85,7 @@ export function LobbyView({ borderRadius: avatarSize, fontSize: Math.round(avatarSize / 2), }} - src={avatarUrl && getAvatarUrl(client, avatarUrl, 96)} + src={avatarUrl} fallback={displayName.slice(0, 1).toUpperCase()} />