import { For, Match, Show, Switch, createSignal } from "solid-js"; import { useLingui } from "@lingui-solid/solid/macro"; import { API, Channel, DEFAULT_PERMISSION_DIRECT_MESSAGE, Server, } from "stoat.js"; import { css } from "styled-system/css"; import { styled } from "styled-system/jsx"; import { Button, Checkbox2, OverrideSwitch, Row, Text } from "@revolt/ui"; type Props = | { type: "server_default"; context: Server } | { type: "server_role"; context: Server; roleId: string } | { type: "channel_default"; context: Channel } | { type: "channel_role"; context: Channel; roleId: string } | { type: "group"; context: Channel }; type Context = API.Channel["channel_type"] | "Server"; /** * Generic editor for any channel permissions */ export function ChannelPermissionsEditor(props: Props) { const { t } = useLingui(); const context: Context = // eslint-disable-next-line solid/reactivity props.context instanceof Server ? "Server" : props.context.type; /** * Current permission value, normalised to [allow, deny] * @returns [allow, deny] BigInts */ function currentValue() { switch (props.type) { case "server_default": return [BigInt(props.context.defaultPermissions), BigInt(0)]; case "server_role": return [ BigInt(props.context.roles?.get(props.roleId)?.permissions.a || 0), BigInt(props.context.roles?.get(props.roleId)?.permissions.d || 0), ]; case "channel_default": return [ BigInt(props.context.defaultPermissions?.a || 0), BigInt(props.context.defaultPermissions?.d || 0), ]; case "channel_role": return [ BigInt(props.context.rolePermissions?.[props.roleId]?.a || 0), BigInt(props.context.rolePermissions?.[props.roleId]?.d || 0), ]; case "group": return [ BigInt( props.context.permissions ?? DEFAULT_PERMISSION_DIRECT_MESSAGE, ), BigInt(0), ]; } } /** * Current edited values */ const [value, setValue] = createSignal(currentValue()); /** * Whether there is a pending save */ function unsavedChanges() { const [a1, a2] = currentValue(), [b1, b2] = value(); return a1 !== b1 || a2 !== b2; } /** * Reset to the current value */ function reset() { setValue(currentValue()); } /** * Commit changes * @todo mutator */ function save() { switch (props.type) { case "server_default": props.context.setPermissions(undefined, Number(value()[0])); break; case "server_role": props.context.setPermissions(props.roleId, { allow: Number(value()[0]), deny: Number(value()[1]), }); break; case "channel_default": props.context.setPermissions(undefined, { allow: Number(value()[0]), deny: Number(value()[1]), }); break; case "channel_role": props.context.setPermissions(props.roleId, { allow: Number(value()[0]), deny: Number(value()[1]), }); break; case "group": props.context.setPermissions(undefined, Number(value()[0])); break; } } const Permissions: { heading?: string; key: string; value: bigint; title: string; description: Partial>; }[] = [ { heading: t`Admin`, key: "ManageChannel", value: 1n ** 0n, title: t`Manage Channel`, description: { Group: t`Edit group name and description`, Any: t`Edit and delete channel`, }, }, { key: "ManageServer", value: 2n ** 1n, title: t`Manage Server`, description: { Server: t`Edit the server's information and settings`, }, }, { key: "ManagePermissions", value: 2n ** 2n, title: t`Manage Permissions`, description: { Group: t`Whether other users can edit these settings`, TextChannel: t`Edit channel-specific role and default permissions`, Server: t`Edit any permissions on the server`, }, }, { key: "ManageRole", value: 2n ** 3n, title: t`Manage Roles`, description: { Server: t`Create and edit server roles`, }, }, { key: "ManageCustomisation", value: 2n ** 4n, title: t`Manage Customisation`, description: { Server: t`Create server emoji`, }, }, { heading: t`Members`, key: "KickMembers", value: 2n ** 6n, title: t`Kick Members`, description: { Server: t`Kick lower-ranking members from the server`, }, }, { key: "BanMembers", value: 2n ** 7n, title: t`Ban Members`, description: { Server: t`Ban lower-ranking members from the server`, }, }, { key: "TimeoutMembers", value: 2n ** 8n, title: t`Timeout Members`, description: { Server: t`Temporarily prevent lower-ranking members from interacting`, }, }, { key: "AssignRoles", value: 2n ** 9n, title: t`Assign Roles`, description: { Server: t`Assign lower-ranked roles to lower-ranking members`, }, }, { key: "ChangeNickname", value: 2n ** 10n, title: t`Change Nickname`, description: { Server: t`Change own nickname`, }, }, { key: "ManageNicknames", value: 2n ** 11n, title: t`Manage Nicknames`, description: { Server: t`Change other members' nicknames`, }, }, { key: "ChangeAvavar", value: 2n ** 12n, title: t`Change Avatar`, description: { Server: t`Change own avatar`, }, }, { key: "RemoveAvatars", value: 2n ** 13n, title: t`Remove Avatars`, description: { Server: t`Remove other members' avatars`, }, }, { heading: t`Channels`, key: "ViewChannel", value: 2n ** 20n, title: t`View Channel`, description: { TextChannel: t`Able to access this channel`, Server: t`Able to access channels on this server`, }, }, { key: "ReadMessageHistory", value: 2n ** 21n, title: t`Read Message History`, description: { TextChannel: t`Read past messages sent in channel`, Server: t`Read past messages sent in channels`, }, }, { key: "SendMessage", value: 2n ** 22n, title: t`Send Messages`, description: { Group: t`Send messages in channel`, TextChannel: t`Send messages in channel`, Server: t`Send messages in channels`, }, }, { key: "ManageMessages", value: 2n ** 23n, title: t`Manage Messages`, description: { Group: t`Delete and pin messages sent by other members`, TextChannel: t`Delete and pin messages sent by other members`, Server: t`Delete and pin messages sent by other members`, }, }, { key: "ManageWebhooks", value: 2n ** 24n, title: t`Manage Webhooks`, description: { Group: t`Create and edit webhooks`, TextChannel: t`Create and edit webhooks`, Server: t`Create and edit webhooks`, }, }, { key: "InviteOthers", value: 2n ** 25n, title: t`Invite Others`, description: { Group: t`Add new members to the group`, Any: t`Create invites for others to use`, }, }, { heading: t`Messaging`, key: "SendEmbeds", value: 2n ** 26n, title: t`Send Embeds`, description: { Any: t`Send embedded content such as link embeds or custom embeds`, }, }, { key: "UploadFiles", value: 2n ** 27n, title: t`Upload Files`, description: { Any: t`Send attachments to chat`, }, }, { key: "Masquerade", value: 2n ** 28n, title: t`Masquerade`, description: { Any: t`Allow members to change name and avatar per-message`, }, }, { key: "React", value: 2n ** 29n, title: t`React`, description: { Any: t`React to messages with emoji`, }, }, { heading: t`Voice`, key: "Connect", value: 2n ** 30n, title: t`Connect`, description: { TextChannel: t`Connect to voice channel`, Server: t`Connect to voice channel`, }, }, { key: "Speak", value: 2n ** 31n, title: t`Speak`, description: { TextChannel: t`Able to speak in voice call`, Server: t`Able to speak in voice call`, }, }, { key: "Video", value: 2n ** 32n, title: t`Video`, description: { TextChannel: t`Share camera or screen in voice call`, Server: t`Share camera or screen in voice call`, }, }, { key: "MuteMembers", value: 2n ** 33n, title: t`Mute Members`, description: { TextChannel: t`Mute lower-ranking members in voice call`, Server: t`Mute lower-ranking members in voice call`, }, }, { key: "DeafenMembers", value: 2n ** 34n, title: t`Deafen Members`, description: { TextChannel: t`Deafen lower-ranking members in voice call`, Server: t`Deafen lower-ranking members in voice call`, }, }, { key: "MoveMembers", value: 2n ** 35n, title: t`Move Members`, description: { TextChannel: t`Move members between voice channels`, Server: t`Move members between voice channels`, }, }, { key: "Listen", value: 2n ** 36n, title: t`Listen`, description: { TextChannel: t`Hear other people and see their video`, Server: t`Hear other people and see their video`, }, }, { heading: t`Mentions`, key: "MentionEveryone", value: 2n ** 37n, title: t`Mention Everyone`, description: { Any: t`Mention everyone and online members inside the server`, }, }, { key: "MentionRoles", value: 2n ** 38n, title: t`Mention Roles`, description: { Any: t`Mention specific roles`, }, }, ]; /** * Find description for this permission in context * If null, don't show this permission entry * @param entry Entry * @returns Description or null */ function description(entry: (typeof Permissions)[number]) { const desc = entry.description; return desc[context] ?? desc.Any; } return (
{(entry) => ( {entry.heading} setValue((v) => [v[0] ^ BigInt(entry.value), v[1]]) } havePermission={ (props.context.permission & entry.value) === entry.value } /> } > { let allow = value()[0] & ~entry.value; let deny = value()[1] & ~entry.value; if (target === "allow") allow |= entry.value; if (target === "deny") deny |= entry.value; setValue([allow, deny]); }} havePermission={ (props.context.permission & entry.value) === entry.value } /> )}
); } const StickyPanel = styled("div", { base: { position: "sticky", width: "fit-content", padding: "var(--gap-md)", bottom: "var(--gap-lg)", borderRadius: "var(--borderRadius-xl)", background: "var(--md-sys-color-surface)", }, }); function ChannelPermissionToggle(props: { key: string; title: string; description: string; value: boolean; onChange: (value: boolean) => void; havePermission: boolean; }) { return ( props.onChange(event.currentTarget.checked)} disabled={!props.havePermission} >
{props.title} {props.description}
); } function ChannelPermissionOverride(props: { key: string; title: string; description: string; value: "allow" | "deny" | "neutral"; onChange: (value: "allow" | "deny" | "neutral") => void; havePermission: boolean; }) { return (
{props.title} {props.description}
); }