Add VoiceChannel support.

This commit is contained in:
Paul 2021-06-23 13:52:16 +01:00
parent 5df1c463a3
commit babb53c794
12 changed files with 123 additions and 59 deletions

2
external/lang vendored

@ -1 +1 @@
Subproject commit 332cc2d7125b9cfb26ce211a9cb0fbf29301946c
Subproject commit f3d13c09b6fa2f28f027ce32643caffadbb63cf1

View file

@ -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",

View file

@ -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,15 +15,21 @@ 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) {
if (target?.channel_type === 'VoiceChannel') {
return (
<Volume2 size={size} />
)
} else {
return (
<Hash size={size} />
)
}
}
}
return (
// ! fixme: replace fallback with <picture /> + <source />

View file

@ -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}

View file

@ -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) {

View file

@ -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";

View file

@ -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}

View file

@ -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;
}
}

View file

@ -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 (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;
}

View file

@ -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 (
<>
let id = channel._id;
return <>
<ChannelHeader channel={channel} toggleSidebar={() => setMembers(!showMembers)} />
<ChannelMain>
<ChannelContent>
<MessageArea id={id} />
<TypingIndicator id={channel._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() {

View file

@ -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) {

View file

@ -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"