diff --git a/external/lang b/external/lang index 0dc9e46b376621b8af99784a7134fb8648cc3701..588b882a3acca644c2f65b67cd341c88fd97989d 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 0dc9e46b376621b8af99784a7134fb8648cc3701 +Subproject commit 588b882a3acca644c2f65b67cd341c88fd97989d diff --git a/package.json b/package.json index d22d515093e93ce68c0db8bee05613a019348b11..a89609945ac03a43f1585f36420d845daee1d37d 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 910bb7f13446e125fecd13cf19b82a349f56b1dc..489572b915f688b63a9101c50e8630b12a8d4ed6 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"; + +interface Props { + warning?: boolean + error?: boolean +} -export const TipBase = styled.div` +export const TipBase = styled.div<Props>` 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 ( - <TipBase> + <TipBase {...tipProps}> <InfoCircle size={20} /> <span>{props.children}</span> </TipBase> diff --git a/src/pages/settings/ChannelSettings.tsx b/src/pages/settings/ChannelSettings.tsx index 4039779e4e646ba7c54c9c1ef36f4007f406697a..aed5ca6950b2c6164bb5c516382e3609611064e7 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: <ListUl size={20} />, title: <Text id="app.settings.channel_pages.overview.title" /> + }, + { + id: 'permissions', + icon: <ListCheck size={20} />, + title: <Text id="app.settings.channel_pages.permissions.title" /> } ]} children={[ + <Route path="/server/:server/channel/:channel/settings/permissions"><Permissions channel={channel} /></Route>, + <Route path="/channel/:channel/settings/permissions"><Permissions channel={channel} /></Route>, + <Route path="/"><Overview channel={channel} /></Route> ]} category="channel_pages" diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx index 60e8cce2784f296b2adb934e38b6d5324869035b..f41f123bd45242c04cf6556e68a4b03900d62a70 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) }} /> - <Button onClick={save} contrast disabled={!changed}> - <Text id="app.special.modals.actions.save" /> - </Button> + <p> + <Button onClick={save} contrast disabled={!changed}> + <Text id="app.special.modals.actions.save" /> + </Button> + </p> </div> ); } diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7fc8c2e694790082a7c4cc103d3fc8e02dc9ba38 --- /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 ( + <div> + <Tip warning>This section is under construction.</Tip> + <h2>select role</h2> + { selected } + { keys + .map(id => { + let role: R = id === 'default' ? defaultRole : roles[id]; + + return ( + <Checkbox checked={selected === id} onChange={selected => selected && setSelected(id)}> + { role.name } + </Checkbox> + ) + }) + } + <h2>channel per??issions</h2> + { Object.keys(ChannelPermission) + .map(perm => { + let value = ChannelPermission[perm as keyof typeof ChannelPermission]; + if (value & DEFAULT_PERMISSION_DM) { + return ( + <Checkbox checked={(p & value) > 0} onChange={c => setPerm(c ? (p | value) : (p ^ value))}> + { perm } + </Checkbox> + ) + } + }) + } + <Button contrast onClick={() => { + client.channels.setPermissions(channel._id, selected, p); + }}>click here to save permissions for role</Button> + </div> + ); +} diff --git a/src/pages/settings/panes/Feedback.tsx b/src/pages/settings/panes/Feedback.tsx index 8aeeafe21eed33ce0492dbfca24809abebc0377c..66d21cd692056cb77dde2a911bb783de22b7159d 100644 --- a/src/pages/settings/panes/Feedback.tsx +++ b/src/pages/settings/panes/Feedback.tsx @@ -69,7 +69,7 @@ export function Feedback() { </Radio> } <Radio disabled={state === "sending"} - checked={checked === "__other_option__"} + checked={checked === "__other_option__" && other !== "Revite"} onSelect={() => setChecked("__other_option__")}> <Localizer> <InputBox @@ -96,9 +96,11 @@ export function Feedback() { disabled={state === "sending"} onChange={ev => setDescription(ev.currentTarget.value)} /> - <Button type="submit" contrast> - <Text id="app.settings.pages.feedback.send" /> - </Button> + <p> + <Button type="submit" contrast> + <Text id="app.settings.pages.feedback.send" /> + </Button> + </p> </form> ); } diff --git a/src/pages/settings/panes/Profile.tsx b/src/pages/settings/panes/Profile.tsx index 148f1b4782292f6576d1b4cb1b1aa6bb03b41e3e..39902c0c37451d4cab2885131006cb7c2dc83822 100644 --- a/src/pages/settings/panes/Profile.tsx +++ b/src/pages/settings/panes/Profile.tsx @@ -113,14 +113,16 @@ export function Profile() { intl.dictionary )} /> - <Button contrast - onClick={() => { - setChanged(false); - ctx.client.users.editUser({ profile: { content: profile?.content } }) - }} - disabled={!changed}> - <Text id="app.special.modals.actions.save" /> - </Button> + <p> + <Button contrast + onClick={() => { + setChanged(false); + ctx.client.users.editUser({ profile: { content: profile?.content } }) + }} + disabled={!changed}> + <Text id="app.special.modals.actions.save" /> + </Button> + </p> </div> ); } diff --git a/src/pages/settings/server/Bans.tsx b/src/pages/settings/server/Bans.tsx index 7d8a1590fc7ddceee953ef528a730b37dffd9a19..5ec7c0f0581a3e0eb0e9ba7b2a46053474b0f1d0 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 ( <div> + <Tip warning>This section is under construction.</Tip> { bans?.map(x => <div>{x._id.user}: {x.reason ?? 'no reason'} <button onClick={() => client.servers.unbanUser(server._id, x._id.user)}>unban</button></div>) } </div> ); diff --git a/src/pages/settings/server/Members.tsx b/src/pages/settings/server/Members.tsx index d4fb3c83291bc7a69c1e442d05f7b590f02bbec9..b569e71afcda26451413104da704755282d396aa 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<Servers.Member[] | undefined>(undefined); + const ctx = useForceUpdate(); const users = useUsers(members?.map(x => x._id.user) ?? [], ctx); @@ -18,7 +22,36 @@ export function Members({ server }: Props) { return ( <div> - { members && members.length > 0 && users?.map(x => x && <div>@{x.username}</div>) } + <Tip warning>This section is under construction.</Tip> + { members && members.length > 0 && users?.map(x => x && <div> + <br/> + <br/> + <br/> + + <span>@{x.username}</span> + { server.roles && Object.keys(server.roles).map(id => { + let role = server.roles?.[id]!; + let member = members.find(y => x._id === y._id.user)!; + + return ( + <Checkbox checked={member.roles?.includes(id) ?? false} onChange={selected => { + 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 }</Checkbox> + ) + }) } + </div>) } </div> ); } diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index d6d0c2842233f583ecb5458448149045a23dcb18..fe530276a1736e84f6077ba407b18bacb1254a07 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) }} /> - <Button onClick={save} contrast disabled={!changed}> - <Text id="app.special.modals.actions.save" /> - </Button> + <p> + <Button onClick={save} contrast disabled={!changed}> + <Text id="app.special.modals.actions.save" /> + </Button> + </p> <h3> <Text id="app.main.servers.custom_banner" /> diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx index 47e82023050739831b2891e98012a8b24d29e33d..5d530c1dca3b37544842a67397dbee0f7c1631ba 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 ( <div> + <Tip warning>This section is under construction.</Tip> <h2>select role</h2> + { selected } { keys .map(id => { let role: Servers.Role = id === 'default' ? defaultRole : roles[id]; @@ -36,14 +57,21 @@ export function Roles({ server }: Props) { ) }) } - <Button disabled={selected === 'default'} error onClick={() => {}}>delete role</Button> - <h2>server permmissions</h2> + <Button disabled={selected === 'default'} error onClick={() => { + setSelected('default'); + client.servers.deleteRole(server._id, selected); + }}>delete role</Button><br/> + <InputBox placeholder="role name" value={name} onChange={e => setName(e.currentTarget.value)} /> + <Button contrast onClick={() => { + client.servers.createRole(server._id, name); + }}>create</Button> + <h2>serverm permmissions</h2> { Object.keys(ServerPermission) .map(perm => { let value = ServerPermission[perm as keyof typeof ServerPermission]; return ( - <Checkbox checked={((selectedRole.permissions[0] >>> 0) & value) > 0} onChange={() => {}}> + <Checkbox checked={(p[0] & value) > 0} onChange={c => setPerm([ c ? (p[0] | value) : (p[0] ^ value), p[1] ])}> { perm } </Checkbox> ) @@ -55,12 +83,15 @@ export function Roles({ server }: Props) { let value = ChannelPermission[perm as keyof typeof ChannelPermission]; return ( - <Checkbox checked={((selectedRole.permissions[1] >>> 0) & value) > 0} onChange={() => {}}> + <Checkbox checked={((p[1] >>> 0) & value) > 0} onChange={c => setPerm([ p[0], c ? (p[1] | value) : (p[1] ^ value) ])}> { perm } </Checkbox> ) }) } + <Button contrast onClick={() => { + client.servers.setPermissions(server._id, selected, { server: p[0], channel: p[1] }); + }}>click here to save permissions for role</Button> </div> ); } diff --git a/yarn.lock b/yarn.lock index b3a3e352c05cdda0b09e34b029fa6c15f07d9777..1a95434bc21aa725e1bf33fe3b815d17b8b8622e 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"