stoat-for-desktop/components/app/menus/UserContextMenu.tsx

450 lines
13 KiB
TypeScript

import { JSX, Match, Show, Switch } from "solid-js";
import { Trans } from "@lingui-solid/solid/macro";
import { useNavigate } from "@solidjs/router";
import { Channel, Message, ServerMember, User } from "stoat.js";
import { useClient } from "@revolt/client";
import { useModals } from "@revolt/modal";
import { useSmartParams } from "@revolt/routing";
import { useState } from "@revolt/state";
import { Slider, Text } from "@revolt/ui";
import MdAddCircleOutline from "@material-design-icons/svg/outlined/add_circle_outline.svg?component-solid";
import MdAdminPanelSettings from "@material-design-icons/svg/outlined/admin_panel_settings.svg?component-solid";
import MdAlternateEmail from "@material-design-icons/svg/outlined/alternate_email.svg?component-solid";
import MdAssignmentInd from "@material-design-icons/svg/outlined/assignment_ind.svg?component-solid";
import MdBadge from "@material-design-icons/svg/outlined/badge.svg?component-solid";
import MdBlock from "@material-design-icons/svg/outlined/block.svg?component-solid";
import MdCancel from "@material-design-icons/svg/outlined/cancel.svg?component-solid";
import MdChat from "@material-design-icons/svg/outlined/chat.svg?component-solid";
import MdClose from "@material-design-icons/svg/outlined/close.svg?component-solid";
import MdDoNotDisturbOn from "@material-design-icons/svg/outlined/do_not_disturb_on.svg?component-solid";
import MdFace from "@material-design-icons/svg/outlined/face.svg?component-solid";
import MdMicOff from "@material-design-icons/svg/outlined/mic_off.svg?component-solid";
import MdPersonAddAlt from "@material-design-icons/svg/outlined/person_add_alt.svg?component-solid";
import MdPersonRemove from "@material-design-icons/svg/outlined/person_remove.svg?component-solid";
import MdReport from "@material-design-icons/svg/outlined/report.svg?component-solid";
import MdChecked from "@material-symbols/svg-400/outlined/check_box.svg?component-solid";
import MdUnchecked from "@material-symbols/svg-400/outlined/check_box_outline_blank.svg?component-solid";
import {
ContextMenu,
ContextMenuButton,
ContextMenuDivider,
} from "./ContextMenu";
import { NotificationContextMenu } from "./shared/NotificationContextMenu";
/**
* Context menu for users
*/
export function UserContextMenu(props: {
user: User;
channel?: Channel;
member?: ServerMember;
contextMessage?: Message;
inVoice?: boolean;
}) {
// TODO: if we take serverId instead, we could dynamically fetch server member here
// same for the floating menu I guess?
const state = useState();
const client = useClient();
const navigate = useNavigate();
const { openModal } = useModals();
// server context
const params = useSmartParams();
/**
* Open direct message channel
*/
function openDm() {
props.user.openDM().then((channel) => navigate(channel.url));
}
/**
* Delete channel
*/
function closeDm() {
openModal({
type: "delete_channel",
channel: props.channel!,
});
}
/**
* Mention the user
*/
function mention() {
if (!state.draft._setNodeReplacement) return;
state.draft._setNodeReplacement([props.user.toString()]);
}
/**
* Edit server identity for user
*/
function editIdentity() {
openModal({
type: "server_identity",
member: props.member!,
});
}
/**
* Report the user
*/
function reportUser() {
openModal({
type: "report_content",
target: props.user!,
client: client(),
contextMessage: props.contextMessage,
});
}
/**
* Edit this user's roles
*/
function editRoles() {
openModal({
type: "user_profile_roles",
member: props.member!,
});
}
/**
* Kick the member
*/
function kickMember() {
openModal({
type: "kick_member",
member: props.member!,
});
}
/**
* Ban the member
*/
function banMember() {
openModal({
type: "ban_member",
member: props.member!,
});
}
/**
* Ban the user
*/
function banUser() {
openModal({
type: "ban_non_member",
user: props.user!,
server: client().servers.get(params().serverId!)!,
});
}
/**
* Add friend
*/
function addFriend() {
props.user.addFriend();
}
/**
* Remove friend
*/
function removeFriend() {
props.user.removeFriend();
}
/**
* Block user
*/
function blockUser() {
props.user.blockUser();
}
/**
* Unblock user
*/
function unblockUser() {
props.user.unblockUser();
}
/**
* Open user in Stoat Admin Panel
*/
function openAdminPanel() {
window.open(
`https://old-admin.stoatinternal.com/panel/inspect/user/${props.user.id}`,
"_blank",
);
}
/**
* Copy user id to clipboard
*/
function copyId() {
navigator.clipboard.writeText(props.user.id);
}
return (
<ContextMenu class="UserContextMenu">
<Show when={props.inVoice && !props.user.self}>
<ContextMenuButton
onMouseDown={(e) => e.stopImmediatePropagation()}
onClick={(e) => e.stopImmediatePropagation()}
>
<Text class="label">
<Trans>Volume</Trans>
</Text>
<Slider
min={0}
max={3}
step={0.1}
value={state.voice.getUserVolume(props.user.id)}
onInput={(event) =>
state.voice.setUserVolume(
props.user.id,
event.currentTarget.value,
)
}
labelFormatter={(label) => (label * 100).toFixed(0) + "%"}
/>
</ContextMenuButton>
<ContextMenuButton
icon={MdMicOff}
onClick={() =>
state.voice.setUserMuted(
props.user.id,
!state.voice.getUserMuted(props.user.id),
)
}
actionSymbol={
state.voice.getUserMuted(props.user.id) ? MdChecked : MdUnchecked
}
>
<Trans>Mute</Trans>
</ContextMenuButton>
<ContextMenuDivider />
</Show>
<Show when={props.channel?.type === "DirectMessage"}>
<ContextMenuButton icon={MdClose} onClick={closeDm}>
<Trans>Close chat</Trans>
</ContextMenuButton>
</Show>
<Show when={props.channel?.type === "TextChannel"}>
<ContextMenuButton icon={MdAlternateEmail} onClick={mention}>
<Trans>Mention</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Friend"}>
<ContextMenuButton icon={MdChat} onClick={openDm}>
<Trans>Message</Trans>
</ContextMenuButton>
</Show>
<Show
when={
props.user.relationship === "Friend" ||
(props.channel &&
(props.channel.type === "DirectMessage" ||
props.channel.type === "TextChannel"))
}
>
<ContextMenuDivider />
</Show>
<Show when={props.channel?.type === "DirectMessage"}>
<NotificationContextMenu channel={props.channel!} />
<ContextMenuDivider />
</Show>
<Show
when={
props.member &&
(props.user.self
? props.member!.server!.havePermission("ChangeNickname") ||
props.member!.server!.havePermission("ChangeAvatar")
: (props.member!.server!.havePermission("ManageNicknames") ||
props.member!.server!.havePermission("RemoveAvatars")) &&
props.member!.inferiorTo(props.member!.server!.member!))
}
>
<ContextMenuButton icon={MdFace} onClick={editIdentity}>
<Switch fallback={<Trans>Edit identity</Trans>}>
<Match when={props.user.self}>
<Trans>Edit your identity</Trans>
</Match>
</Switch>
</ContextMenuButton>
</Show>
<Show when={props.member}>
<Show
when={
props.member?.server?.owner?.self ||
(props.member?.server?.havePermission("AssignRoles") &&
props.member.inferiorTo(props.member.server.member!))
}
>
<ContextMenuButton icon={MdAssignmentInd} onClick={editRoles}>
<Trans>Edit roles</Trans>
</ContextMenuButton>
</Show>
{/** TODO: #287 timeout users */}
<Show
when={
!props.user.self &&
props.member?.server?.havePermission("KickMembers") &&
props.member.inferiorTo(props.member.server.member!)
}
>
<ContextMenuButton
icon={MdPersonRemove}
onClick={kickMember}
destructive
>
<Trans>Kick member</Trans>
</ContextMenuButton>
</Show>
<Show
when={
!props.user.self &&
props.member?.server?.havePermission("BanMembers") &&
props.member.inferiorTo(props.member.server.member!)
}
>
<ContextMenuButton
icon={MdDoNotDisturbOn}
onClick={banMember}
destructive
>
<Trans>Ban member</Trans>
</ContextMenuButton>
</Show>
</Show>
<Show
when={
!props.user.self &&
props.member?.server?.havePermission("BanMembers") &&
params().serverId &&
!props.member
}
>
<ContextMenuButton
icon={MdDoNotDisturbOn}
onClick={banUser}
destructive
>
<Trans>Ban user</Trans>
</ContextMenuButton>
</Show>
<Show when={!props.user.self}>
<ContextMenuButton icon={MdReport} onClick={reportUser} destructive>
<Trans>Report user</Trans>
</ContextMenuButton>
{/* TODO: #286 show profile / message */}
<Show when={props.user.relationship === "None" && !props.user.bot}>
<ContextMenuButton icon={MdPersonAddAlt} onClick={addFriend}>
<Trans>Add friend</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Friend"}>
<ContextMenuButton icon={MdPersonRemove} onClick={removeFriend}>
<Trans>Remove friend</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Incoming"}>
<ContextMenuButton icon={MdPersonAddAlt} onClick={addFriend}>
<Trans>Accept friend request</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Incoming"}>
<ContextMenuButton icon={MdCancel} onClick={removeFriend}>
<Trans>Reject friend request</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Outgoing"}>
<ContextMenuButton icon={MdCancel} onClick={removeFriend}>
<Trans>Cancel friend request</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship !== "Blocked"}>
<ContextMenuButton icon={MdBlock} onClick={blockUser}>
<Trans>Block user</Trans>
</ContextMenuButton>
</Show>
<Show when={props.user.relationship === "Blocked"}>
<ContextMenuButton icon={MdAddCircleOutline} onClick={unblockUser}>
<Trans>Unblock user</Trans>
</ContextMenuButton>
</Show>
</Show>
<Show
when={
state.settings.getValue("advanced:admin_panel") ||
state.settings.getValue("advanced:copy_id")
}
>
<ContextMenuDivider />
</Show>
<Show when={state.settings.getValue("advanced:admin_panel")}>
<ContextMenuButton icon={MdAdminPanelSettings} onClick={openAdminPanel}>
<Trans>Admin Panel</Trans>
</ContextMenuButton>
</Show>
<Show when={state.settings.getValue("advanced:copy_id")}>
<ContextMenuButton icon={MdBadge} onClick={copyId}>
<Trans>Copy user ID</Trans>
</ContextMenuButton>
</Show>
</ContextMenu>
);
}
/**
* Provide floating user menus on this element
* @param user User
* @param member Server Member
*/
export function floatingUserMenus(
user: User,
member?: ServerMember,
contextMessage?: Message,
): JSX.Directives["floating"] & object {
return {
userCard: {
user,
member,
// we could use message to display masquerade info in user card
},
/**
* Build user context menu
*/
contextMenu() {
return (
<UserContextMenu
user={user}
member={member}
contextMessage={contextMessage}
channel={contextMessage?.channel}
/>
);
},
};
}
export function floatingUserMenusFromMessage(message: Message) {
return message.author
? floatingUserMenus(message.author!, message.member, message)
: {}; // TODO: webhook menu
}