diff --git a/external/lang b/external/lang index d27cbcf7..30558b79 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit d27cbcf7a8d7663d924ecb67de283b2fea901788 +Subproject commit 30558b7989de3b6b39c360d85db4fc58e146ad5e diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index ffd3fb85..e3540bcd 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -113,6 +113,7 @@ const Message = observer( onContextMenu={userContext} onClick={openProfile} animate={animate} + showServerIdentity /> ) : ( @@ -126,6 +127,7 @@ const Message = observer( user={user} onContextMenu={userContext} onClick={openProfile} + showServerIdentity /> - + diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx index f5f38660..935e6cd4 100644 --- a/src/components/common/user/UserIcon.tsx +++ b/src/components/common/user/UserIcon.tsx @@ -1,5 +1,6 @@ import { MicrophoneOff } from "@styled-icons/boxicons-regular"; import { observer } from "mobx-react-lite"; +import { useParams } from "react-router-dom"; import { Presence } from "revolt-api/types/Users"; import { User } from "revolt.js/dist/maps/Users"; import styled, { css } from "styled-components"; @@ -7,7 +8,7 @@ import styled, { css } from "styled-components"; import { useContext } from "preact/hooks"; import { ThemeContext } from "../../../context/Theme"; -import { AppContext } from "../../../context/revoltjs/RevoltClient"; +import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient"; import IconBase, { IconBaseProps } from "../IconBase"; import fallback from "../assets/user.png"; @@ -17,6 +18,7 @@ interface Props extends IconBaseProps { mask?: string; status?: boolean; voice?: VoiceStatus; + showServerIdentity?: boolean; } export function useStatusColour(user?: User) { @@ -59,7 +61,7 @@ export default observer( keyof Props | "children" | "as" >, ) => { - const client = useContext(AppContext); + const client = useClient(); const { target, @@ -69,11 +71,28 @@ export default observer( animate, mask, hover, + showServerIdentity, ...svgProps } = props; + + let override; + if (target && showServerIdentity) { + const { server } = useParams<{ server?: string }>(); + if (server) { + const member = client.members.getKey({ + server, + user: target._id, + }); + + if (member?.avatar) { + override = member?.avatar; + } + } + } + const iconURL = client.generateFileURL( - target?.avatar ?? attachment, + override ?? target?.avatar ?? attachment, { max_side: 256 }, animate, ) ?? (target ? target.defaultAvatarURL : fallback); diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx index c89bb631..331796f7 100644 --- a/src/components/common/user/UserShort.tsx +++ b/src/components/common/user/UserShort.tsx @@ -11,12 +11,16 @@ import UserIcon from "./UserIcon"; export const Username = observer( ({ user, + showServerIdentity, ...otherProps - }: { user?: User } & JSX.HTMLAttributes) => { + }: { + user?: User; + showServerIdentity?: boolean; + } & JSX.HTMLAttributes) => { let username = user?.username; let color; - if (user) { + if (user && showServerIdentity) { const { server } = useParams<{ server?: string }>(); if (server) { const client = useClient(); diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index 51267801..751a65c5 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -64,10 +64,11 @@ export const UserButton = observer((props: UserProps) => { target={user} size={32} status + showServerIdentity />
- +
{
diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 51be4c1d..ac3f8f47 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -86,6 +86,10 @@ export type Screen = id: "user_picker"; omit?: string[]; callback: (users: string[]) => Promise; + } + | { + id: "server_identity"; + server: Server; }; export const IntermediateContext = createContext({ diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx index fa0f0b7d..199dc349 100644 --- a/src/context/intermediate/Popovers.tsx +++ b/src/context/intermediate/Popovers.tsx @@ -11,6 +11,7 @@ import { ChannelInfo } from "./popovers/ChannelInfo"; import { ImageViewer } from "./popovers/ImageViewer"; import { ModifyAccountModal } from "./popovers/ModifyAccount"; import { PendingRequests } from "./popovers/PendingRequests"; +import { ServerIdentityModal } from "./popovers/ServerIdentityModal"; import { UserPicker } from "./popovers/UserPicker"; import { UserProfile } from "./popovers/UserProfile"; @@ -40,6 +41,8 @@ export default function Popovers() { return ; case "special_input": return ; + case "server_identity": + return ; } return null; diff --git a/src/context/intermediate/popovers/ServerIdentityModal.tsx b/src/context/intermediate/popovers/ServerIdentityModal.tsx new file mode 100644 index 00000000..eb6e449e --- /dev/null +++ b/src/context/intermediate/popovers/ServerIdentityModal.tsx @@ -0,0 +1,71 @@ +import { observer } from "mobx-react-lite"; +import { Server } from "revolt.js/dist/maps/Servers"; + +import { useEffect, useState } from "preact/hooks"; + +import Button from "../../../components/ui/Button"; +import InputBox from "../../../components/ui/InputBox"; +import Modal from "../../../components/ui/Modal"; +import Overline from "../../../components/ui/Overline"; + +import { FileUploader } from "../../revoltjs/FileUploads"; +import { useClient } from "../../revoltjs/RevoltClient"; + +interface Props { + server: Server; + onClose: () => void; +} + +export const ServerIdentityModal = observer(({ server, onClose }: Props) => { + const client = useClient(); + const member = client.members.getKey({ + server: server._id, + user: client.user!._id, + }); + + if (!member) return null; + + const [nickname, setNickname] = useState(""); + useEffect(() => setNickname(member.nickname ?? ""), [member.nickname]); + + return ( + + Nickname +

+ setNickname(e.currentTarget.value)} + /> +

+

+ +

+

+ +

+ Avatar + member.edit({ avatar })} + remove={() => member.edit({ remove: "Avatar" })} + defaultPreview={client.generateFileURL( + member.avatar ?? undefined, + { max_side: 256 }, + true, + )} + previewURL={client.generateFileURL( + member.avatar ?? undefined, + { max_side: 256 }, + true, + )} + /> +
+ ); +}); diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 97a8d416..c3b33e1f 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -114,6 +114,7 @@ type Action = | { action: "close_dm"; target: Channel } | { action: "leave_server"; target: Server } | { action: "delete_server"; target: Server } + | { action: "edit_identity"; target: Server } | { action: "open_notification_options"; channel: Channel } | { action: "open_settings" } | { action: "open_channel_settings"; id: string } @@ -408,6 +409,13 @@ function ContextMenus(props: Props) { } as unknown as Screen); break; + case "edit_identity": + openScreen({ + id: "server_identity", + server: data.target, + }); + break; + case "ban_member": case "kick_member": openScreen({ @@ -850,6 +858,17 @@ function ContextMenus(props: Props) { } if (sid && server) { + if ( + serverPermissions & + ServerPermission.ChangeNickname || + serverPermissions & + ServerPermission.ChangeAvatar + ) + generateAction( + { action: "edit_identity", target: server }, + "edit_identity", + ); + if ( serverPermissions & ServerPermission.ManageServer