From babb53c7940cb8505b1a0fc65d63ed0152e4f532 Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Wed, 23 Jun 2021 13:52:16 +0100 Subject: [PATCH] Add VoiceChannel support. --- external/lang | 2 +- package.json | 2 +- src/components/common/ChannelIcon.tsx | 18 ++++--- src/components/common/messaging/Message.tsx | 3 +- src/components/navigation/left/common.ts | 3 +- src/context/intermediate/Intermediate.tsx | 6 +-- src/context/intermediate/modals/Input.tsx | 21 +------- src/context/intermediate/modals/Prompt.tsx | 59 ++++++++++++++++++++- src/lib/ContextMenus.tsx | 15 +++--- src/pages/channels/Channel.tsx | 43 ++++++++++----- src/pages/settings/channel/Overview.tsx | 2 +- yarn.lock | 8 +-- 12 files changed, 123 insertions(+), 59 deletions(-) diff --git a/external/lang b/external/lang index 332cc2d..f3d13c0 160000 --- a/external/lang +++ b/external/lang @@ -1 +1 @@ -Subproject commit 332cc2d7125b9cfb26ce211a9cb0fbf29301946c +Subproject commit f3d13c09b6fa2f28f027ce32643caffadbb63cf1 diff --git a/package.json b/package.json index bce943b..a1a8deb 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "react-scroll": "^1.8.2", "react-tippy": "^1.4.0", "redux": "^4.1.0", - "revolt.js": "4.3.1-alpha.0", + "revolt.js": "4.3.2", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/components/common/ChannelIcon.tsx b/src/components/common/ChannelIcon.tsx index a793462..fb4d1a5 100644 --- a/src/components/common/ChannelIcon.tsx +++ b/src/components/common/ChannelIcon.tsx @@ -1,10 +1,10 @@ import { useContext } from "preact/hooks"; -import { Hash } from "@styled-icons/feather"; import { Channels } from "revolt.js/dist/api/objects"; +import { Hash, Volume2 } from "@styled-icons/feather"; import { ImageIconBase, IconBaseProps } from "./IconBase"; import { AppContext } from "../../context/revoltjs/RevoltClient"; -interface Props extends IconBaseProps<Channels.GroupChannel | Channels.TextChannel> { +interface Props extends IconBaseProps<Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel> { isServerChannel?: boolean; } @@ -15,13 +15,19 @@ export default function ChannelIcon(props: Props & Omit<JSX.HTMLAttributes<HTMLI const { size, target, attachment, isServerChannel: server, animate, children, as, ...imgProps } = props; const iconURL = client.generateFileURL(target?.icon ?? attachment, { max_side: 256 }, animate); - const isServerChannel = server || target?.channel_type === 'TextChannel'; + const isServerChannel = server || (target && (target.channel_type === 'TextChannel' || target.channel_type === 'VoiceChannel')); if (typeof iconURL === 'undefined') { if (isServerChannel) { - return ( - <Hash size={size} /> - ) + if (target?.channel_type === 'VoiceChannel') { + return ( + <Volume2 size={size} /> + ) + } else { + return ( + <Hash size={size} /> + ) + } } } diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index 362c087..28ee957 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -22,13 +22,14 @@ interface Props { head?: boolean } -export default function Message({ attachContext, message, contrast, content: replacement, head, queued }: Props) { +export default function Message({ attachContext, message, contrast, content: replacement, head: preferHead, queued }: Props) { // TODO: Can improve re-renders here by providing a list // TODO: of dependencies. We only need to update on u/avatar. const user = useUser(message.author); const client = useContext(AppContext); const content = message.content as string; + const head = (message.replies && message.replies.length > 0) || preferHead; return ( <MessageBase id={message._id} head={head} diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts index 12d8f9b..2a681d1 100644 --- a/src/components/navigation/left/common.ts +++ b/src/components/navigation/left/common.ts @@ -16,7 +16,8 @@ export function useUnreads({ channel, unreads, dispatcher }: UnreadProps, contex function checkUnread(target?: Channel) { if (!target) return; if (target._id !== channel._id) return; - if (target?.channel_type === "SavedMessages") return; + if (target.channel_type === "SavedMessages" || + target.channel_type === "VoiceChannel") return; const unread = unreads[channel._id]?.last_id; if (target.last_message) { diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 67b5f01..f64acce 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -26,11 +26,11 @@ export type Screen = { type: "delete_message", target: Channels.Message } | { type: "create_invite", target: Channels.TextChannel | Channels.GroupChannel } | { type: "kick_member", target: Servers.Server, user: string } | - { type: "ban_member", target: Servers.Server, user: string } + { type: "ban_member", target: Servers.Server, user: string } | + { type: "create_channel", target: Servers.Server } )) | ({ id: "special_input" } & ( - { type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } | - { type: "create_channel", server: string } + { type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } )) | { id: "_input"; diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx index 3d28785..b589737 100644 --- a/src/context/intermediate/modals/Input.tsx +++ b/src/context/intermediate/modals/Input.tsx @@ -65,8 +65,7 @@ export function InputModal({ } type SpecialProps = { onClose: () => void } & ( - { type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } | - { type: "create_channel", server: string } + { type: "create_group" | "create_server" | "set_custom_status" | "add_friend" } ) export function SpecialInputModal(props: SpecialProps) { @@ -110,24 +109,6 @@ export function SpecialInputModal(props: SpecialProps) { }} />; } - case "create_channel": { - return <InputModal - onClose={onClose} - question={<Text id="app.context_menu.create_channel" />} - field={<Text id="app.main.servers.channel_name" />} - callback={async name => { - const channel = await client.servers.createChannel( - props.server, - { - name, - nonce: ulid() - } - ); - - history.push(`/server/${props.server}/channel/${channel._id}`); - }} - />; - } case "set_custom_status": { return <InputModal onClose={onClose} diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index 1a48e62..78048d8 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -1,5 +1,8 @@ +import { ulid } from "ulid"; import { Text } from "preact-i18n"; import styles from './Prompt.module.scss'; +import { useHistory } from "react-router-dom"; +import Radio from "../../../components/ui/Radio"; import { Children } from "../../../types/Preact"; import { useIntermediate } from "../Intermediate"; import InputBox from "../../../components/ui/InputBox"; @@ -44,7 +47,8 @@ type SpecialProps = { onClose: () => void } & ( { type: "delete_message", target: Channels.Message } | { type: "create_invite", target: Channels.TextChannel | Channels.GroupChannel } | { type: "kick_member", target: Servers.Server, user: string } | - { type: "ban_member", target: Servers.Server, user: string } + { type: "ban_member", target: Servers.Server, user: string } | + { type: "create_channel", target: Servers.Server } ) export function SpecialPromptModal(props: SpecialProps) { @@ -263,6 +267,59 @@ export function SpecialPromptModal(props: SpecialProps) { /> ) } + case 'create_channel': { + const [ name, setName ] = useState(''); + const [ type, setType ] = useState<'Text' | 'Voice'>('Text'); + const history = useHistory(); + + return ( + <PromptModal + onClose={onClose} + question={<Text id="app.context_menu.create_channel" />} + actions={[ + { + confirmation: true, + contrast: true, + text: <Text id="app.special.modals.actions.create" />, + onClick: async () => { + setProcessing(true); + + try { + const channel = await client.servers.createChannel( + props.target._id, + { + type, + name, + nonce: ulid() + } + ); + + history.push(`/server/${props.target._id}/channel/${channel._id}`); + onClose(); + } catch (err) { + setError(takeError(err)); + setProcessing(false); + } + } + }, + { text: <Text id="app.special.modals.actions.cancel" />, onClick: onClose } + ]} + content={<> + <Overline block type="subtle"><Text id="app.main.servers.channel_type" /></Overline> + <Radio checked={type === 'Text'} onSelect={() => setType('Text')}> + <Text id="app.main.servers.text_channel" /></Radio> + <Radio checked={type === 'Voice'} onSelect={() => setType('Voice')}> + <Text id="app.main.servers.voice_channel" /></Radio> + <Overline block type="subtle"><Text id="app.main.servers.channel_name" /></Overline> + <InputBox + value={name} + onChange={e => setName(e.currentTarget.value)} /> + </>} + disabled={processing} + error={error} + /> + ) + } default: return null; } } diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 73e5403..d5c8467 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -60,7 +60,7 @@ type Action = | { action: "set_presence"; presence: Users.Presence } | { action: "set_status" } | { action: "clear_status" } - | { action: "create_channel"; server: string } + | { action: "create_channel"; target: Servers.Server } | { action: "create_invite"; target: Channels.GroupChannel | Channels.TextChannel } | { action: "leave_group"; target: Channels.GroupChannel } | { action: "delete_channel"; target: Channels.TextChannel } @@ -92,7 +92,8 @@ function ContextMenus(props: WithDispatcher) { break; case "mark_as_read": { - if (data.channel.channel_type === 'SavedMessages') return; + if (data.channel.channel_type === 'SavedMessages' || + data.channel.channel_type === 'VoiceChannel') return; let message = data.channel.channel_type === 'TextChannel' ? data.channel.last_message : data.channel.last_message._id; props.dispatcher({ @@ -280,14 +281,13 @@ function ContextMenus(props: WithDispatcher) { case "delete_channel": case "delete_server": case "delete_message": + case "create_channel": // @ts-expect-error case "create_invite": openScreen({ id: "special_prompt", type: data.action, target: data.target }); break; case "ban_member": case "kick_member": openScreen({ id: "special_prompt", type: data.action, target: data.target, user: data.user }); break; - case "create_channel": openScreen({ id: "special_input", type: "create_channel", server: data.server }); break; - case "open_channel_settings": history.push(`/channel/${data.id}/settings`); break; case "open_server_channel_settings": history.push(`/server/${data.server}/channel/${data.id}/settings`); break; case "open_server_settings": history.push(`/server/${data.id}/settings`); break; @@ -341,9 +341,12 @@ function ContextMenus(props: WithDispatcher) { } if (server_list) { + let server = useServer(server_list, forceUpdate); let permissions = useServerPermission(server_list, forceUpdate); - if (permissions & ServerPermission.ManageChannels) generateAction({ action: 'create_channel', server: server_list }); - if (permissions & ServerPermission.ManageServer) generateAction({ action: 'open_server_settings', id: server_list }); + if (server) { + if (permissions & ServerPermission.ManageChannels) generateAction({ action: 'create_channel', target: server }); + if (permissions & ServerPermission.ManageServer) generateAction({ action: 'open_server_settings', id: server_list }); + } return elements; } diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index 8d32b3b..70a7d02 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -10,6 +10,7 @@ import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks"; import MemberSidebar from "../../components/navigation/right/MemberSidebar"; import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom"; import TypingIndicator from "../../components/common/messaging/bars/TypingIndicator"; +import { Channel } from "revolt.js"; const ChannelMain = styled.div` flex-grow: 1; @@ -31,22 +32,36 @@ export function Channel({ id }: { id: string }) { const channel = useChannel(id, ctx); if (!channel) return null; + + if (channel.channel_type === 'VoiceChannel') { + return <VoiceChannel channel={channel} />; + } else { + return <TextChannel channel={channel} />; + } +} + +function TextChannel({ channel }: { channel: Channel }) { const [ showMembers, setMembers ] = useState(true); - return ( - <> - <ChannelHeader channel={channel} toggleSidebar={() => setMembers(!showMembers)} /> - <ChannelMain> - <ChannelContent> - <MessageArea id={id} /> - <TypingIndicator id={channel._id} /> - <JumpToBottom id={id} /> - <MessageBox channel={channel} /> - </ChannelContent> - { !isTouchscreenDevice && showMembers && <MemberSidebar channel={channel} /> } - </ChannelMain> - </> - ) + let id = channel._id; + return <> + <ChannelHeader channel={channel} toggleSidebar={() => setMembers(!showMembers)} /> + <ChannelMain> + <ChannelContent> + <MessageArea id={id} /> + <TypingIndicator id={id} /> + <JumpToBottom id={id} /> + <MessageBox channel={channel} /> + </ChannelContent> + { !isTouchscreenDevice && showMembers && <MemberSidebar channel={channel} /> } + </ChannelMain> + </>; +} + +function VoiceChannel({ channel }: { channel: Channel }) { + return <> + <ChannelHeader channel={channel} /> + </>; } export default function() { diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx index 30c36b9..60e8cce 100644 --- a/src/pages/settings/channel/Overview.tsx +++ b/src/pages/settings/channel/Overview.tsx @@ -9,7 +9,7 @@ import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { FileUploader } from "../../../context/revoltjs/FileUploads"; interface Props { - channel: Channels.GroupChannel | Channels.TextChannel; + channel: Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel; } export function Overview({ channel }: Props) { diff --git a/yarn.lock b/yarn.lock index 18f56c4..2ec6929 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3344,10 +3344,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.1-alpha.0: - version "4.3.1-alpha.0" - resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.1-alpha.0.tgz#21abb0706852468a0b7991a80d81093f547d25f3" - integrity sha512-YwDdDgioVYeBYkgZtgtXM37//96WmT18XVPJ7cBJzDQ3GWUKKPrw4VFjmi9FSh0ksfgfkSIrA7/hqmztZWbnVw== +revolt.js@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.2.tgz#2e613ff1d918d77266e9c777e226bfbddd5a9b87" + integrity sha512-JyD3fRaory3Rhy/sAWcvHjLb/CluJRZap2Di2ZFFf9uiRJBgLNlClS/3RkBLAcQqx4KVx7Ua3WbKq1/dU6x7dQ== dependencies: "@insertish/mutable" "1.1.0" axios "^0.19.2" -- GitLab