From ff21d4efa85315fa4dca065973bd764d4c8a718c Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 1 Jul 2021 17:36:34 +0100 Subject: [PATCH] Add permissions / roles settings. --- external/lang | 2 +- package.json | 2 +- src/components/ui/Tip.tsx | 28 +++++-- src/pages/settings/ChannelSettings.tsx | 24 +++++- src/pages/settings/channel/Overview.tsx | 10 ++- src/pages/settings/channel/Permissions.tsx | 91 ++++++++++++++++++++++ src/pages/settings/panes/Feedback.tsx | 10 ++- src/pages/settings/panes/Profile.tsx | 18 +++-- src/pages/settings/server/Bans.tsx | 2 + src/pages/settings/server/Members.tsx | 35 ++++++++- src/pages/settings/server/Overview.tsx | 8 +- src/pages/settings/server/Roles.tsx | 41 ++++++++-- yarn.lock | 8 +- 13 files changed, 239 insertions(+), 40 deletions(-) create mode 100644 src/pages/settings/channel/Permissions.tsx diff --git a/external/lang b/external/lang index 0dc9e46b..588b882a 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 0dc9e46b376621b8af99784a7134fb8648cc3701 +Subproject commit 588b882a3acca644c2f65b67cd341c88fd97989d diff --git a/package.json b/package.json index d22d5150..a8960994 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "react-router-dom": "^5.2.0", "react-scroll": "^1.8.2", "redux": "^4.1.0", - "revolt.js": "4.3.3-alpha.4", + "revolt.js": "4.3.3-alpha.6", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx index 910bb7f1..489572b9 100644 --- a/src/components/ui/Tip.tsx +++ b/src/components/ui/Tip.tsx @@ -1,8 +1,13 @@ -import styled from "styled-components"; -import { InfoCircle } from "@styled-icons/boxicons-regular"; import { Children } from "../../types/Preact"; +import styled, { css } from "styled-components"; +import { InfoCircle } from "@styled-icons/boxicons-regular"; -export const TipBase = styled.div` +interface Props { + warning?: boolean + error?: boolean +} + +export const TipBase = styled.div` display: flex; padding: 12px; overflow: hidden; @@ -24,11 +29,24 @@ export const TipBase = styled.div` flex-shrink: 0; margin-inline-end: 10px; } + + ${ props => props.warning && css` + color: var(--warning); + border: 2px solid var(--warning); + background: var(--secondary-header); + ` } + + ${ props => props.error && css` + color: var(--error); + border: 2px solid var(--error); + background: var(--secondary-header); + ` } `; -export default function Tip(props: { children: Children }) { +export default function Tip(props: Props & { children: Children }) { + const { children, ...tipProps } = props; return ( - + {props.children} diff --git a/src/pages/settings/ChannelSettings.tsx b/src/pages/settings/ChannelSettings.tsx index 4039779e..aed5ca69 100644 --- a/src/pages/settings/ChannelSettings.tsx +++ b/src/pages/settings/ChannelSettings.tsx @@ -1,12 +1,13 @@ import { Text } from "preact-i18n"; -import { ListUl } from "@styled-icons/boxicons-regular"; import Category from "../../components/ui/Category"; import { GenericSettings } from "./GenericSettings"; import { getChannelName } from "../../context/revoltjs/util"; import { Route, useHistory, useParams } from "react-router-dom"; +import { ListCheck, ListUl } from "@styled-icons/boxicons-regular"; import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks"; -import { Overview } from "./channel/Overview"; +import Overview from "./channel/Overview"; +import Permissions from "./channel/Permissions"; export default function ChannelSettings() { const { channel: cid } = useParams<{ channel: string; }>(); @@ -17,10 +18,17 @@ export default function ChannelSettings() { const history = useHistory(); function switchPage(to?: string) { + let base_url; + switch (channel?.channel_type) { + case 'TextChannel': + case 'VoiceChannel': base_url = `/server/${channel.server}/channel/${cid}/settings`; break; + default: base_url = `/channel/${cid}/settings`; + } + if (to) { - history.replace(`/channel/${cid}/settings/${to}`); + history.replace(`${base_url}/${to}`); } else { - history.replace(`/channel/${cid}/settings`); + history.replace(base_url); } } @@ -32,9 +40,17 @@ export default function ChannelSettings() { id: 'overview', icon: , title: + }, + { + id: 'permissions', + icon: , + title: } ]} children={[ + , + , + ]} category="channel_pages" diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx index 60e8cce2..f41f123b 100644 --- a/src/pages/settings/channel/Overview.tsx +++ b/src/pages/settings/channel/Overview.tsx @@ -12,7 +12,7 @@ interface Props { channel: Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel; } -export function Overview({ channel }: Props) { +export default function Overview({ channel }: Props) { const client = useContext(AppContext); const [name, setName] = useState(channel.name); @@ -81,9 +81,11 @@ export function Overview({ channel }: Props) { if (!changed) setChanged(true) }} /> - +

+ +

); } diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx new file mode 100644 index 00000000..7fc8c2e6 --- /dev/null +++ b/src/pages/settings/channel/Permissions.tsx @@ -0,0 +1,91 @@ +import Tip from "../../../components/ui/Tip"; +import Button from "../../../components/ui/Button"; +import { Channels } from "revolt.js/dist/api/objects"; +import Checkbox from "../../../components/ui/Checkbox"; +import { useServer } from "../../../context/revoltjs/hooks"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { ChannelPermission } from "revolt.js/dist/api/permissions"; +import { AppContext } from "../../../context/revoltjs/RevoltClient"; + +// ! FIXME: export from revolt.js +const DEFAULT_PERMISSION_DM = ChannelPermission.View + + ChannelPermission.SendMessage + + ChannelPermission.ManageChannel + + ChannelPermission.VoiceCall + + ChannelPermission.InviteOthers + + ChannelPermission.EmbedLinks + + ChannelPermission.UploadFiles; + +interface Props { + channel: Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel; +} + +// ! FIXME: bad code :) +export default function Permissions({ channel }: Props) { + const [ selected, setSelected ] = useState('default'); + const client = useContext(AppContext); + + type R = { name: string, permissions: number }; + let roles: { [key: string]: R } = {}; + if (channel.channel_type !== 'Group') { + const server = useServer(channel.server); + const a = server?.roles ?? {}; + for (let b of Object.keys(a)) { + roles[b] = { + name: a[b].name, + permissions: a[b].permissions[1] + }; + } + } + + const keys = [ 'default', ...Object.keys(roles) ]; + + const defaultRole = { name: 'Default', permissions: (channel.channel_type === 'Group' ? channel.permissions : channel.default_permissions) ?? DEFAULT_PERMISSION_DM }; + const selectedRole = selected === 'default' ? defaultRole : roles[selected]; + + if (!selectedRole) { + useEffect(() => setSelected('default'), [ ]); + return null; + } + + const [ p, setPerm ] = useState(selectedRole.permissions >>> 0); + + useEffect(() => { + setPerm(selectedRole.permissions >>> 0); + }, [ selected, selectedRole.permissions ]); + + return ( +
+ This section is under construction. +

select role

+ { selected } + { keys + .map(id => { + let role: R = id === 'default' ? defaultRole : roles[id]; + + return ( + selected && setSelected(id)}> + { role.name } + + ) + }) + } +

channel per??issions

+ { Object.keys(ChannelPermission) + .map(perm => { + let value = ChannelPermission[perm as keyof typeof ChannelPermission]; + if (value & DEFAULT_PERMISSION_DM) { + return ( + 0} onChange={c => setPerm(c ? (p | value) : (p ^ value))}> + { perm } + + ) + } + }) + } + +
+ ); +} diff --git a/src/pages/settings/panes/Feedback.tsx b/src/pages/settings/panes/Feedback.tsx index 8aeeafe2..66d21cd6 100644 --- a/src/pages/settings/panes/Feedback.tsx +++ b/src/pages/settings/panes/Feedback.tsx @@ -69,7 +69,7 @@ export function Feedback() { } setChecked("__other_option__")}> setDescription(ev.currentTarget.value)} /> - +

+ +

); } diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index 148f1b47..39902c0c 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -113,14 +113,16 @@ export function Profile() { intl.dictionary )} /> - +

+ +

); } diff --git a/src/pages/settings/server/Bans.tsx b/src/pages/settings/server/Bans.tsx index 7d8a1590..5ec7c0f0 100644 --- a/src/pages/settings/server/Bans.tsx +++ b/src/pages/settings/server/Bans.tsx @@ -1,3 +1,4 @@ +import Tip from "../../../components/ui/Tip"; import { Servers } from "revolt.js/dist/api/objects"; import { useContext, useEffect, useState } from "preact/hooks"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; @@ -17,6 +18,7 @@ export function Bans({ server }: Props) { return (
+ This section is under construction. { bans?.map(x =>
{x._id.user}: {x.reason ?? 'no reason'}
) }
); diff --git a/src/pages/settings/server/Members.tsx b/src/pages/settings/server/Members.tsx index d4fb3c83..b569e71a 100644 --- a/src/pages/settings/server/Members.tsx +++ b/src/pages/settings/server/Members.tsx @@ -1,13 +1,17 @@ import { useEffect, useState } from "preact/hooks"; import { Servers } from "revolt.js/dist/api/objects"; +import Checkbox from "../../../components/ui/Checkbox"; +import Tip from "../../../components/ui/Tip"; import { useForceUpdate, useUsers } from "../../../context/revoltjs/hooks"; interface Props { server: Servers.Server; } +// ! FIXME: bad code :) export function Members({ server }: Props) { const [members, setMembers] = useState(undefined); + const ctx = useForceUpdate(); const users = useUsers(members?.map(x => x._id.user) ?? [], ctx); @@ -18,7 +22,36 @@ export function Members({ server }: Props) { return (
- { members && members.length > 0 && users?.map(x => x &&
@{x.username}
) } + This section is under construction. + { members && members.length > 0 && users?.map(x => x &&
+
+
+
+ + @{x.username} + { server.roles && Object.keys(server.roles).map(id => { + let role = server.roles?.[id]!; + let member = members.find(y => x._id === y._id.user)!; + + return ( + { + let roles = (member.roles ?? []).filter(z => z !== id); + if (selected) roles.push(id); + + ctx.client.servers.members.editMember(server._id, x._id, { roles }); + setMembers( + [ + ...members.filter(e => e._id.user !== x._id), + { + ...member, + roles + } + ] + ); + }}>{ role.name } + ) + }) } +
) }
); } diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index d6d0c284..fe530276 100644 --- a/src/pages/settings/server/Overview.tsx +++ b/src/pages/settings/server/Overview.tsx @@ -76,9 +76,11 @@ export function Overview({ server }: Props) { if (!changed) setChanged(true) }} /> - +

+ +

diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 47e82023..5d530c1d 100644 --- a/src/pages/settings/server/Roles.tsx +++ b/src/pages/settings/server/Roles.tsx @@ -1,15 +1,20 @@ -import { useEffect, useState } from "preact/hooks"; +import Tip from "../../../components/ui/Tip"; import Button from "../../../components/ui/Button"; import { Servers } from "revolt.js/dist/api/objects"; +import InputBox from "../../../components/ui/InputBox"; import Checkbox from "../../../components/ui/Checkbox"; +import { useContext, useEffect, useState } from "preact/hooks"; +import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { ChannelPermission, ServerPermission } from "revolt.js/dist/api/permissions"; interface Props { server: Servers.Server; } +// ! FIXME: bad code :) export function Roles({ server }: Props) { const [ selected, setSelected ] = useState('default'); + const client = useContext(AppContext); const roles = server.roles ?? {}; const keys = [ 'default', ...Object.keys(roles) ]; @@ -21,10 +26,26 @@ export function Roles({ server }: Props) { useEffect(() => setSelected('default'), [ ]); return null; } + + const [ p, setPerm ] = useState([ + selectedRole.permissions[0] >>> 0, + selectedRole.permissions[1] >>> 0, + ]); + + useEffect(() => { + setPerm([ + selectedRole.permissions[0] >>> 0, + selectedRole.permissions[1] >>> 0, + ]); + }, [ selected, selectedRole.permissions ]); + + const [ name, setName ] = useState(''); return (
+ This section is under construction.

select role

+ { selected } { keys .map(id => { let role: Servers.Role = id === 'default' ? defaultRole : roles[id]; @@ -36,14 +57,21 @@ export function Roles({ server }: Props) { ) }) } - -

server permmissions

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

serverm permmissions

{ Object.keys(ServerPermission) .map(perm => { let value = ServerPermission[perm as keyof typeof ServerPermission]; return ( - >> 0) & value) > 0} onChange={() => {}}> + 0} onChange={c => setPerm([ c ? (p[0] | value) : (p[0] ^ value), p[1] ])}> { perm } ) @@ -55,12 +83,15 @@ export function Roles({ server }: Props) { let value = ChannelPermission[perm as keyof typeof ChannelPermission]; return ( - >> 0) & value) > 0} onChange={() => {}}> + >> 0) & value) > 0} onChange={c => setPerm([ p[0], c ? (p[1] | value) : (p[1] ^ value) ])}> { perm } ) }) } +
); } diff --git a/yarn.lock b/yarn.lock index b3a3e352..1a95434b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3420,10 +3420,10 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -revolt.js@4.3.3-alpha.4: - version "4.3.3-alpha.4" - resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.4.tgz#7d393c9016a9d89151b1c091f6b02bc193b3a553" - integrity sha512-d6SGjRKFDlWG5fEveaVf0DRGb8d0RW8iv1E0kEG0W3R138KdeWCK8zUU0H+ykUdd5OjS7ESBKaEcwSP2BXMRSA== +revolt.js@4.3.3-alpha.6: + version "4.3.3-alpha.6" + resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.6.tgz#054e685a5c0dac2c7ae3e2aa454d1965218cb2b0" + integrity sha512-u1/xf+YSQr8DbKsO0raym+F05R75bqYadrPWaIie3m2s2p7ZWeamHlfWIKJlmDO5AL+Lg3xoZWoLwuRHrD1K/Q== dependencies: "@insertish/mutable" "1.1.0" axios "^0.19.2"