mirror of
https://github.com/revoltchat/revite.git
synced 2025-01-12 23:41:25 -05:00
Merge branch 'master' of https://github.com/revoltchat/revite
This commit is contained in:
commit
ea2e6ada82
17 changed files with 244 additions and 27 deletions
|
@ -83,7 +83,7 @@ export default function Embed({ embed }: Props) {
|
||||||
className={classNames(styles.embed, styles.website)}
|
className={classNames(styles.embed, styles.website)}
|
||||||
style={{
|
style={{
|
||||||
borderInlineStartColor:
|
borderInlineStartColor:
|
||||||
embed.color ?? "var(--tertiary-background)",
|
embed.colour ?? "var(--tertiary-background)",
|
||||||
width: width + CONTAINER_PADDING,
|
width: width + CONTAINER_PADDING,
|
||||||
}}>
|
}}>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -12,8 +12,9 @@ import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import IconBase, { IconBaseProps } from "../IconBase";
|
import IconBase, { IconBaseProps } from "../IconBase";
|
||||||
import fallback from "../assets/user.png";
|
import fallback from "../assets/user.png";
|
||||||
|
import {VolumeMute} from "@styled-icons/boxicons-solid";
|
||||||
|
|
||||||
type VoiceStatus = "muted";
|
type VoiceStatus = "muted" | "deaf";
|
||||||
interface Props extends IconBaseProps<User> {
|
interface Props extends IconBaseProps<User> {
|
||||||
mask?: string;
|
mask?: string;
|
||||||
status?: boolean;
|
status?: boolean;
|
||||||
|
@ -47,7 +48,7 @@ const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
|
||||||
}
|
}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.status === "muted" &&
|
(props.status === "muted" || props.status === "deaf") &&
|
||||||
css`
|
css`
|
||||||
background: var(--error);
|
background: var(--error);
|
||||||
`}
|
`}
|
||||||
|
@ -125,7 +126,9 @@ export default observer(
|
||||||
{props.voice && (
|
{props.voice && (
|
||||||
<foreignObject x="22" y="22" width="10" height="10">
|
<foreignObject x="22" y="22" width="10" height="10">
|
||||||
<VoiceIndicator status={props.voice}>
|
<VoiceIndicator status={props.voice}>
|
||||||
{props.voice === "muted" && (
|
{props.voice === "deaf" && (
|
||||||
|
<VolumeMute size={6} />
|
||||||
|
) ||props.voice === "muted" && (
|
||||||
<MicrophoneOff size={6} />
|
<MicrophoneOff size={6} />
|
||||||
)}
|
)}
|
||||||
</VoiceIndicator>
|
</VoiceIndicator>
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { generateEmoji } from "../common/Emoji";
|
||||||
|
|
||||||
import { emojiDictionary } from "../../assets/emojis";
|
import { emojiDictionary } from "../../assets/emojis";
|
||||||
import { MarkdownProps } from "./Markdown";
|
import { MarkdownProps } from "./Markdown";
|
||||||
|
import {useIntermediate} from "../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
// TODO: global.d.ts file for defining globals
|
// TODO: global.d.ts file for defining globals
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -32,6 +33,13 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ALLOWED_ORIGINS = [
|
||||||
|
location.hostname,
|
||||||
|
'app.revolt.chat',
|
||||||
|
'nightly.revolt.chat',
|
||||||
|
'local.revolt.chat',
|
||||||
|
];
|
||||||
|
|
||||||
// Handler for code block copy.
|
// Handler for code block copy.
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.copycode = function (element: HTMLDivElement) {
|
window.copycode = function (element: HTMLDivElement) {
|
||||||
|
@ -90,6 +98,8 @@ const RE_CHANNELS = /<#([A-z0-9]{26})>/g;
|
||||||
|
|
||||||
export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
|
const { openScreen } = useIntermediate();
|
||||||
|
|
||||||
if (typeof content === "undefined") return null;
|
if (typeof content === "undefined") return null;
|
||||||
if (content.length === 0) return null;
|
if (content.length === 0) return null;
|
||||||
|
|
||||||
|
@ -172,7 +182,7 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(href, location.href);
|
const url = new URL(href, location.href);
|
||||||
|
|
||||||
if (url.hostname === location.hostname) {
|
if (ALLOWED_ORIGINS.includes(url.hostname)) {
|
||||||
internal = true;
|
internal = true;
|
||||||
element.addEventListener(
|
element.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
|
@ -191,6 +201,13 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
|
|
||||||
if (!internal) {
|
if (!internal) {
|
||||||
element.setAttribute("target", "_blank");
|
element.setAttribute("target", "_blank");
|
||||||
|
element.onclick = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
openScreen({
|
||||||
|
id: "external_link_prompt",
|
||||||
|
link: href
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -240,15 +240,16 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
|
||||||
let homeUnread: "mention" | "unread" | undefined;
|
let homeUnread: "mention" | "unread" | undefined;
|
||||||
let alertCount = 0;
|
let alertCount = 0;
|
||||||
for (const x of channels) {
|
for (const x of channels) {
|
||||||
if (
|
if (x.channel?.channel_type === "Group" && x.unread) {
|
||||||
(x.channel?.channel_type === "DirectMessage"
|
|
||||||
? x.channel?.active
|
|
||||||
: x.channel?.channel_type === "Group") &&
|
|
||||||
x.unread
|
|
||||||
) {
|
|
||||||
homeUnread = "unread";
|
homeUnread = "unread";
|
||||||
alertCount += x.alertCount ?? 0;
|
alertCount += x.alertCount ?? 0;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
x.channel?.channel_type === "DirectMessage" &&
|
||||||
|
x.unread &&
|
||||||
|
x.unread.length > 0
|
||||||
|
)
|
||||||
|
alertCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
alertCount += [...client.users.values()].filter(
|
alertCount += [...client.users.values()].filter(
|
||||||
|
|
|
@ -24,6 +24,7 @@ export type Screen =
|
||||||
| { id: "signed_out" }
|
| { id: "signed_out" }
|
||||||
| { id: "error"; error: string }
|
| { id: "error"; error: string }
|
||||||
| { id: "clipboard"; text: string }
|
| { id: "clipboard"; text: string }
|
||||||
|
| { id: "external_link_prompt"; link: string }
|
||||||
| {
|
| {
|
||||||
id: "_prompt";
|
id: "_prompt";
|
||||||
question: Children;
|
question: Children;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { InputModal } from "./modals/Input";
|
||||||
import { OnboardingModal } from "./modals/Onboarding";
|
import { OnboardingModal } from "./modals/Onboarding";
|
||||||
import { PromptModal } from "./modals/Prompt";
|
import { PromptModal } from "./modals/Prompt";
|
||||||
import { SignedOutModal } from "./modals/SignedOut";
|
import { SignedOutModal } from "./modals/SignedOut";
|
||||||
|
import {ExternalLinkModal} from "./modals/ExternalLinkPrompt";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
screen: Screen;
|
screen: Screen;
|
||||||
|
@ -34,6 +35,8 @@ export default function Modals({ screen, openScreen }: Props) {
|
||||||
return <ClipboardModal onClose={onClose} {...screen} />;
|
return <ClipboardModal onClose={onClose} {...screen} />;
|
||||||
case "onboarding":
|
case "onboarding":
|
||||||
return <OnboardingModal onClose={onClose} {...screen} />;
|
return <OnboardingModal onClose={onClose} {...screen} />;
|
||||||
|
case "external_link_prompt":
|
||||||
|
return <ExternalLinkModal onClose={onClose} {...screen} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
34
src/context/intermediate/modals/ExternalLinkPrompt.tsx
Normal file
34
src/context/intermediate/modals/ExternalLinkPrompt.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import Modal from "../../../components/ui/Modal";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClose: () => void;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExternalLinkModal({ onClose, link }: Props) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={true}
|
||||||
|
onClose={onClose}
|
||||||
|
title={<Text id={"app.special.modals.external_links.title"} />}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
onClick: ()=>{window.open(link, "_blank");},
|
||||||
|
confirmation: true,
|
||||||
|
contrast: true,
|
||||||
|
accent: true,
|
||||||
|
children: "Continue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onClick: onClose,
|
||||||
|
confirmation: false,
|
||||||
|
children: "Cancel",
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Text id={"app.special.modals.external_links.short"} /> <br />
|
||||||
|
<a>{link}</a>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -40,6 +40,8 @@ export default class VoiceClient extends EventEmitter<VoiceEvents> {
|
||||||
sendTransport?: Transport;
|
sendTransport?: Transport;
|
||||||
recvTransport?: Transport;
|
recvTransport?: Transport;
|
||||||
|
|
||||||
|
isDeaf?: boolean;
|
||||||
|
|
||||||
userId?: string;
|
userId?: string;
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
participants: Map<string, VoiceUser>;
|
participants: Map<string, VoiceUser>;
|
||||||
|
@ -54,6 +56,8 @@ export default class VoiceClient extends EventEmitter<VoiceEvents> {
|
||||||
this.participants = new Map();
|
this.participants = new Map();
|
||||||
this.consumers = new Map();
|
this.consumers = new Map();
|
||||||
|
|
||||||
|
this.isDeaf = false;
|
||||||
|
|
||||||
this.signaling.on(
|
this.signaling.on(
|
||||||
"data",
|
"data",
|
||||||
(json) => {
|
(json) => {
|
||||||
|
|
|
@ -143,6 +143,33 @@ class VoiceStateReference {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDeaf() {
|
||||||
|
if(!this.client)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.client.isDeaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
async startDeafen() {
|
||||||
|
if(!this.client)
|
||||||
|
return console.log("No client object"); // ! TODO: let the user know
|
||||||
|
|
||||||
|
this.client.isDeaf = true;
|
||||||
|
|
||||||
|
this.client?.consumers.forEach(consumer => {
|
||||||
|
consumer.audio?.pause();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async stopDeafen() {
|
||||||
|
if(!this.client)
|
||||||
|
return console.log("No client object"); // ! TODO: let the user know
|
||||||
|
|
||||||
|
this.client.isDeaf = false;
|
||||||
|
this.client?.consumers.forEach(consumer => {
|
||||||
|
consumer.audio?.resume();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async startProducing(type: ProduceType) {
|
async startProducing(type: ProduceType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "audio": {
|
case "audio": {
|
||||||
|
@ -152,8 +179,10 @@ class VoiceStateReference {
|
||||||
if (navigator.mediaDevices === undefined)
|
if (navigator.mediaDevices === undefined)
|
||||||
return console.log("No media devices."); // ! TODO: let the user know
|
return console.log("No media devices."); // ! TODO: let the user know
|
||||||
|
|
||||||
|
const mediaDevice = window.localStorage.getItem("audioInputDevice");
|
||||||
|
|
||||||
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: true,
|
audio: mediaDevice?{deviceId: mediaDevice}:true
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.client?.startProduce(
|
await this.client?.startProduce(
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default function MessageEditor({ message, finish }: Props) {
|
||||||
<AutoComplete detached {...autoCompleteProps} />
|
<AutoComplete detached {...autoCompleteProps} />
|
||||||
<TextAreaAutoSize
|
<TextAreaAutoSize
|
||||||
forceFocus
|
forceFocus
|
||||||
maxRows={3}
|
maxRows={10}
|
||||||
value={content}
|
value={content}
|
||||||
maxLength={2000}
|
maxLength={2000}
|
||||||
padding="var(--message-box-padding)"
|
padding="var(--message-box-padding)"
|
||||||
|
|
|
@ -11,6 +11,18 @@ import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import UserIcon from "../../../components/common/user/UserIcon";
|
import UserIcon from "../../../components/common/user/UserIcon";
|
||||||
import Button from "../../../components/ui/Button";
|
import Button from "../../../components/ui/Button";
|
||||||
|
import {
|
||||||
|
Megaphone,
|
||||||
|
Microphone,
|
||||||
|
MicrophoneOff,
|
||||||
|
PhoneOff,
|
||||||
|
Speaker,
|
||||||
|
VolumeFull,
|
||||||
|
VolumeMute
|
||||||
|
} from "@styled-icons/boxicons-solid";
|
||||||
|
import Tooltip from "../../../components/common/Tooltip";
|
||||||
|
import {Hashnode, Speakerdeck, Teamspeak} from "@styled-icons/simple-icons";
|
||||||
|
import VoiceClient from "../../../lib/vortex/VoiceClient";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -89,7 +101,8 @@ export default observer(({ id }: Props) => {
|
||||||
target={user}
|
target={user}
|
||||||
status={false}
|
status={false}
|
||||||
voice={
|
voice={
|
||||||
voiceState.participants!.get(id)
|
client.user?._id === id && voiceState.isDeaf()?"deaf"
|
||||||
|
: voiceState.participants!.get(id)
|
||||||
?.audio
|
?.audio
|
||||||
? undefined
|
? undefined
|
||||||
: "muted"
|
: "muted"
|
||||||
|
@ -115,18 +128,38 @@ export default observer(({ id }: Props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<Button error onClick={voiceState.disconnect}>
|
<Tooltip content={"Leave call"} placement={"bottom"}>
|
||||||
<Text id="app.main.channel.voice.leave" />
|
<Button error onClick={voiceState.disconnect}>
|
||||||
</Button>
|
<PhoneOff width={25} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
{voiceState.isProducing("audio") ? (
|
{voiceState.isProducing("audio") ? (
|
||||||
<Button onClick={() => voiceState.stopProducing("audio")}>
|
<Tooltip content={"Mute microphone"} placement={"bottom"}>
|
||||||
<Text id="app.main.channel.voice.mute" />
|
<Button onClick={() => voiceState.stopProducing("audio")}>
|
||||||
</Button>
|
<Microphone width={25} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => voiceState.startProducing("audio")}>
|
<Tooltip content={"Unmute microphone"} placement={"bottom"}>
|
||||||
<Text id="app.main.channel.voice.unmute" />
|
<Button onClick={() => voiceState.startProducing("audio")}>
|
||||||
</Button>
|
<MicrophoneOff width={25} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{voiceState.isDeaf() ? (
|
||||||
|
<Tooltip content={"Deafen"} placement={"bottom"}>
|
||||||
|
<Button onClick={() => voiceState.stopDeafen()}>
|
||||||
|
<VolumeMute width={25} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
): (
|
||||||
|
<Tooltip content={"Deafen"} placement={"bottom"}>
|
||||||
|
<Button onClick={() => voiceState.startDeafen()}>
|
||||||
|
<VolumeFull width={25} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</VoiceBase>
|
</VoiceBase>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,10 +16,12 @@ function mapMailProvider(email?: string): [string, string] | undefined {
|
||||||
const domain = match[1];
|
const domain = match[1];
|
||||||
switch (domain) {
|
switch (domain) {
|
||||||
case "gmail.com":
|
case "gmail.com":
|
||||||
|
case "googlemail.com":
|
||||||
return ["Gmail", "https://gmail.com"];
|
return ["Gmail", "https://gmail.com"];
|
||||||
case "tuta.io":
|
case "tuta.io":
|
||||||
return ["Tutanota", "https://mail.tutanota.com"];
|
return ["Tutanota", "https://mail.tutanota.com"];
|
||||||
case "outlook.com":
|
case "outlook.com":
|
||||||
|
case "hotmail.com":
|
||||||
return ["Outlook", "https://outlook.live.com"];
|
return ["Outlook", "https://outlook.live.com"];
|
||||||
case "yahoo.com":
|
case "yahoo.com":
|
||||||
return ["Yahoo", "https://mail.yahoo.com"];
|
return ["Yahoo", "https://mail.yahoo.com"];
|
||||||
|
@ -27,11 +29,24 @@ function mapMailProvider(email?: string): [string, string] | undefined {
|
||||||
return ["WP Poczta", "https://poczta.wp.pl"];
|
return ["WP Poczta", "https://poczta.wp.pl"];
|
||||||
case "protonmail.com":
|
case "protonmail.com":
|
||||||
case "protonmail.ch":
|
case "protonmail.ch":
|
||||||
|
case "pm.me":
|
||||||
return ["ProtonMail", "https://mail.protonmail.com"];
|
return ["ProtonMail", "https://mail.protonmail.com"];
|
||||||
case "seznam.cz":
|
case "seznam.cz":
|
||||||
case "email.cz":
|
case "email.cz":
|
||||||
case "post.cz":
|
case "post.cz":
|
||||||
return ["Seznam", "https://email.seznam.cz"];
|
return ["Seznam", "https://email.seznam.cz"];
|
||||||
|
case "zoho.com":
|
||||||
|
return ["Zoho Mail", "https://mail.zoho.com/zm/"];
|
||||||
|
case "aol.com":
|
||||||
|
case "aim.com":
|
||||||
|
return ["AOL Mail", "https://mail.aol.com/"];
|
||||||
|
case "icloud.com":
|
||||||
|
return ["iCloud Mail", "https://mail.aol.com/"];
|
||||||
|
case "mail.com":
|
||||||
|
case "email.com":
|
||||||
|
return ["mail.com", "https://www.mail.com/mail/"];
|
||||||
|
case "yandex.com":
|
||||||
|
return ["Yandex Mail", "https://mail.yandex.com/"];
|
||||||
default:
|
default:
|
||||||
return [domain, `https://${domain}`];
|
return [domain, `https://${domain}`];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
Flask,
|
Flask,
|
||||||
User,
|
User,
|
||||||
Megaphone,
|
Megaphone,
|
||||||
|
Speaker,
|
||||||
} from "@styled-icons/boxicons-solid";
|
} from "@styled-icons/boxicons-solid";
|
||||||
import { Route, Switch, useHistory } from "react-router-dom";
|
import { Route, Switch, useHistory } from "react-router-dom";
|
||||||
import { LIBRARY_VERSION } from "revolt.js";
|
import { LIBRARY_VERSION } from "revolt.js";
|
||||||
|
@ -37,6 +38,7 @@ import { APP_VERSION } from "../../version";
|
||||||
import { GenericSettings } from "./GenericSettings";
|
import { GenericSettings } from "./GenericSettings";
|
||||||
import { Account } from "./panes/Account";
|
import { Account } from "./panes/Account";
|
||||||
import { Appearance } from "./panes/Appearance";
|
import { Appearance } from "./panes/Appearance";
|
||||||
|
import { Audio } from "./panes/Audio";
|
||||||
import { ExperimentsPage } from "./panes/Experiments";
|
import { ExperimentsPage } from "./panes/Experiments";
|
||||||
import { Feedback } from "./panes/Feedback";
|
import { Feedback } from "./panes/Feedback";
|
||||||
import { Languages } from "./panes/Languages";
|
import { Languages } from "./panes/Languages";
|
||||||
|
@ -85,6 +87,12 @@ export default function Settings() {
|
||||||
category: (
|
category: (
|
||||||
<Text id="app.settings.categories.client_settings" />
|
<Text id="app.settings.categories.client_settings" />
|
||||||
),
|
),
|
||||||
|
id: "audio",
|
||||||
|
icon: <Speaker size={20} />,
|
||||||
|
title: <Text id="app.settings.pages.audio.title" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
id: "appearance",
|
id: "appearance",
|
||||||
icon: <Palette size={20} />,
|
icon: <Palette size={20} />,
|
||||||
title: <Text id="app.settings.pages.appearance.title" />,
|
title: <Text id="app.settings.pages.appearance.title" />,
|
||||||
|
@ -141,6 +149,9 @@ export default function Settings() {
|
||||||
<Route path="/settings/appearance">
|
<Route path="/settings/appearance">
|
||||||
<Appearance />
|
<Appearance />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/settings/audio">
|
||||||
|
<Audio />
|
||||||
|
</Route>
|
||||||
<Route path="/settings/notifications">
|
<Route path="/settings/notifications">
|
||||||
<Notifications />
|
<Notifications />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
61
src/pages/settings/panes/Audio.tsx
Normal file
61
src/pages/settings/panes/Audio.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import styles from "./Panes.module.scss";
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import { connectState } from "../../../redux/connector";
|
||||||
|
|
||||||
|
import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState";
|
||||||
|
|
||||||
|
import ComboBox from "../../../components/ui/ComboBox";
|
||||||
|
import {useEffect, useState} from "preact/hooks";
|
||||||
|
|
||||||
|
export function Component() {
|
||||||
|
const [mediaDevices, setMediaDevices] = useState<MediaDeviceInfo[] | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigator
|
||||||
|
.mediaDevices
|
||||||
|
.enumerateDevices()
|
||||||
|
.then( devices => {
|
||||||
|
setMediaDevices(devices)
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.audio.input_device" />
|
||||||
|
</h3>
|
||||||
|
<ComboBox
|
||||||
|
value={window.localStorage.getItem("audioInputDevice") ?? 0}
|
||||||
|
onChange={(e) => changeAudioDevice(e.currentTarget.value, "input")}>
|
||||||
|
{
|
||||||
|
mediaDevices?.filter(device => device.kind === "audioinput").map(device => {
|
||||||
|
return (
|
||||||
|
<option value={device.deviceId} key={device.deviceId}>
|
||||||
|
{device.label}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ComboBox>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeAudioDevice(deviceId: string, deviceType: string) {
|
||||||
|
if(deviceType === "input") {
|
||||||
|
window.localStorage.setItem("audioInputDevice", deviceId)
|
||||||
|
if(voiceState.isProducing("audio")) {
|
||||||
|
voiceState.stopProducing("audio");
|
||||||
|
voiceState.startProducing("audio");
|
||||||
|
}
|
||||||
|
}else if(deviceType === "output") {
|
||||||
|
window.localStorage.setItem("audioOutputDevice", deviceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Audio = connectState(Component, () => {
|
||||||
|
return;
|
||||||
|
});
|
|
@ -152,7 +152,7 @@ export const Overview = observer(({ server }: Props) => {
|
||||||
<Text id="general.disabled" />
|
<Text id="general.disabled" />
|
||||||
</option>
|
</option>
|
||||||
{server.channels
|
{server.channels
|
||||||
.filter((x) => typeof x !== "undefined")
|
.filter((x) => (typeof x !== "undefined" && x.channel_type === "TextChannel"))
|
||||||
.map((channel) => (
|
.map((channel) => (
|
||||||
<option key={channel!._id} value={channel!._id}>
|
<option key={channel!._id} value={channel!._id}>
|
||||||
{getChannelName(channel!, true)}
|
{getChannelName(channel!, true)}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Server } from "revolt.js/dist/maps/Servers";
|
||||||
|
|
||||||
import styles from "./Panes.module.scss";
|
import styles from "./Panes.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
import {useCallback, useContext, useEffect, useMemo, useState} from "preact/hooks";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import InputBox from "../../../components/ui/InputBox";
|
||||||
import Overline from "../../../components/ui/Overline";
|
import Overline from "../../../components/ui/Overline";
|
||||||
|
|
||||||
import ButtonItem from "../../../components/navigation/items/ButtonItem";
|
import ButtonItem from "../../../components/navigation/items/ButtonItem";
|
||||||
|
import {AppContext} from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
server: Server;
|
server: Server;
|
||||||
|
@ -26,6 +27,7 @@ const I32ToU32 = (arr: number[]) => arr.map((x) => x >>> 0);
|
||||||
|
|
||||||
// ! FIXME: bad code :)
|
// ! FIXME: bad code :)
|
||||||
export const Roles = observer(({ server }: Props) => {
|
export const Roles = observer(({ server }: Props) => {
|
||||||
|
const client = useContext(AppContext);
|
||||||
const [role, setRole] = useState("default");
|
const [role, setRole] = useState("default");
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen } = useIntermediate();
|
||||||
const roles = useMemo(() => server.roles ?? {}, [server]);
|
const roles = useMemo(() => server.roles ?? {}, [server]);
|
||||||
|
@ -35,6 +37,8 @@ export const Roles = observer(({ server }: Props) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clientPermissions = client.servers.get(server._id)!.permission;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name: roleName,
|
name: roleName,
|
||||||
colour: roleColour,
|
colour: roleColour,
|
||||||
|
@ -207,6 +211,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setPerm([perm[0] ^ value, perm[1]])
|
setPerm([perm[0] ^ value, perm[1]])
|
||||||
}
|
}
|
||||||
|
disabled={!(clientPermissions & value)}
|
||||||
description={
|
description={
|
||||||
<Text id={`permissions.server.${key}.d`} />
|
<Text id={`permissions.server.${key}.d`} />
|
||||||
}>
|
}>
|
||||||
|
@ -233,7 +238,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
setPerm([perm[0], perm[1] ^ value])
|
setPerm([perm[0], perm[1] ^ value])
|
||||||
}
|
}
|
||||||
disabled={key === "View"}
|
disabled={key === "View" || (!(clientPermissions & value))}
|
||||||
description={
|
description={
|
||||||
<Text id={`permissions.channel.${key}.d`} />
|
<Text id={`permissions.channel.${key}.d`} />
|
||||||
}>
|
}>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export type Experiments = "search";
|
export type Experiments = "search";
|
||||||
export const AVAILABLE_EXPERIMENTS: Experiments[] = ["search"];
|
export const AVAILABLE_EXPERIMENTS: Experiments[] = [];
|
||||||
export const EXPERIMENTS: {
|
export const EXPERIMENTS: {
|
||||||
[key in Experiments]: { title: string; description: string };
|
[key in Experiments]: { title: string; description: string };
|
||||||
} = {
|
} = {
|
||||||
|
|
Loading…
Reference in a new issue