mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-19 22:20:34 +00:00
Merge e2326dcbc4
into f27361f017
This commit is contained in:
commit
e6a9e2ac24
7 changed files with 249 additions and 57 deletions
|
@ -0,0 +1,15 @@
|
|||
.vc-uvs-indicator {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.vc-uvs-indicator > svg {
|
||||
fill: dodgerblue;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./VoiceChannelIndicator.css";
|
||||
|
||||
import { NavigationRouter, PermissionsBits, PermissionStore, Toasts, Tooltip } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
import speakerSvg from "./speaker.svg";
|
||||
|
||||
interface VoiceChannelIndicatorProps {
|
||||
tooltipText?: string;
|
||||
channel: Channel;
|
||||
}
|
||||
|
||||
export const VoiceChannelIndicator = ({ tooltipText, channel }: VoiceChannelIndicatorProps) => (
|
||||
<span className="vc-uvs-indicator" onClick={e => {
|
||||
if (PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel)) {
|
||||
NavigationRouter.transitionTo(getChannelPath(channel));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
else
|
||||
Toasts.show({
|
||||
message: "Insufficient permissions to view the channel.",
|
||||
id: "user-voice-show-insufficient-permissions",
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM,
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<Tooltip text={tooltipText}>
|
||||
{(tooltipProps: any) => speakerSvg(tooltipProps)}
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
|
||||
const getChannelPath = (c: Channel) => `/channels/${c.guild_id ?? "@me"}/${c.id}`;
|
|
@ -8,20 +8,15 @@
|
|||
height: unset;
|
||||
}
|
||||
|
||||
.vc-uvs-button-view {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: unset;
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.vc-uvs-header {
|
||||
color: var(--header-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.vc-uvs-modal-margin {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.vc-uvs-modal-margin div {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.vc-uvs-popout-margin-self>[class^="section"] {
|
||||
padding-top: 0;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
|
|
@ -18,44 +18,77 @@
|
|||
|
||||
import "./VoiceChannelSection.css";
|
||||
|
||||
import { Flex } from "@components/Flex";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, Forms, PermissionStore, Toasts } from "@webpack/common";
|
||||
import { Button, Forms, NavigationRouter, PermissionsBits, PermissionStore, React, Toasts } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
import eyeSvg from "./eye.svg";
|
||||
|
||||
const ChannelActions = findByPropsLazy("selectChannel", "selectVoiceChannel");
|
||||
|
||||
const CONNECT = 1n << 20n;
|
||||
|
||||
interface VoiceChannelFieldProps {
|
||||
channel: Channel;
|
||||
label: string;
|
||||
showHeader: boolean;
|
||||
showHeader?: boolean;
|
||||
}
|
||||
|
||||
export const VoiceChannelSection = ({ channel, label, showHeader }: VoiceChannelFieldProps) => (
|
||||
// @TODO The div is supposed to be a UserPopoutSection
|
||||
<div>
|
||||
export const VoiceChannelSection = ({ channel, label, showHeader = false }: VoiceChannelFieldProps) => (
|
||||
<React.Fragment>
|
||||
{showHeader && <Forms.FormTitle className="vc-uvs-header">In a voice channel</Forms.FormTitle>}
|
||||
<Button
|
||||
className="vc-uvs-button"
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
size={Button.Sizes.SMALL}
|
||||
|
||||
onClick={() => {
|
||||
if (PermissionStore.can(CONNECT, channel))
|
||||
ChannelActions.selectVoiceChannel(channel.id);
|
||||
else
|
||||
Toasts.show({
|
||||
message: "Insufficient permissions to enter the channel.",
|
||||
id: "user-voice-show-insufficient-permissions",
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM,
|
||||
}
|
||||
});
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
gap: "0.5em",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
className="vc-uvs-button"
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
size={Button.Sizes.SMALL}
|
||||
|
||||
onClick={() => {
|
||||
if (PermissionStore.can(PermissionsBits.CONNECT, channel))
|
||||
ChannelActions.selectVoiceChannel(channel.id);
|
||||
else
|
||||
Toasts.show({
|
||||
message: "Insufficient permissions to enter the channel.",
|
||||
id: "user-voice-show-insufficient-permissions",
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM,
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
<Button
|
||||
className="vc-uvs-button-view"
|
||||
color={Button.Colors.TRANSPARENT}
|
||||
size={Button.Sizes.SMALL}
|
||||
|
||||
onClick={() => {
|
||||
if (PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel))
|
||||
NavigationRouter.transitionTo(getChannelPath(channel));
|
||||
else
|
||||
Toasts.show({
|
||||
message: "Insufficient permissions to view the channel.",
|
||||
id: "user-voice-show-insufficient-permissions",
|
||||
type: Toasts.Type.FAILURE,
|
||||
options: {
|
||||
position: Toasts.Position.BOTTOM,
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{eyeSvg()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const getChannelPath = (c: Channel) => `/channels/${c.guild_id ?? "@me"}/${c.id}`;
|
||||
|
|
20
src/plugins/userVoiceShow/components/eye.svg.tsx
Normal file
20
src/plugins/userVoiceShow/components/eye.svg.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export default () => (
|
||||
<svg
|
||||
width="20px"
|
||||
height="20px"
|
||||
fill="currentColor"
|
||||
overflow="hidden"
|
||||
|
||||
style={{ verticalAlign: "middle" }}
|
||||
|
||||
viewBox="0 0 1024 1024"
|
||||
>
|
||||
<path d="M512 384a128 128 0 0 0-128 128 128 128 0 0 0 128 128 128 128 0 0 0 128-128 128 128 0 0 0-128-128m0 341.333333a213.333333 213.333333 0 0 1-213.333333-213.333333 213.333333 213.333333 0 0 1 213.333333-213.333333 213.333333 213.333333 0 0 1 213.333333 213.333333 213.333333 213.333333 0 0 1-213.333333 213.333333m0-533.333333C298.666667 192 116.48 324.693333 42.666667 512c73.813333 187.306667 256 320 469.333333 320s395.52-132.693333 469.333333-320c-73.813333-187.306667-256-320-469.333333-320z" />
|
||||
</svg>
|
||||
);
|
21
src/plugins/userVoiceShow/components/speaker.svg.tsx
Normal file
21
src/plugins/userVoiceShow/components/speaker.svg.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export default (props: any) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="17px"
|
||||
height="17px"
|
||||
fill="currentColor"
|
||||
overflow="hidden"
|
||||
|
||||
style={{ verticalAlign: "middle" }}
|
||||
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.92l4.28 4.68a1 1 0 0 0 .74.32H11a1 1 0 0 0 1-1V3ZM15.1 20.75c-.58.14-1.1-.33-1.1-.92v-.03c0-.5.37-.92.85-1.05a7 7 0 0 0 0-13.5A1.11 1.11 0 0 1 14 4.2v-.03c0-.6.52-1.06 1.1-.92a9 9 0 0 1 0 17.5Z"></path><path d="M15.16 16.51c-.57.28-1.16-.2-1.16-.83v-.14c0-.43.28-.8.63-1.02a3 3 0 0 0 0-5.04c-.35-.23-.63-.6-.63-1.02v-.14c0-.63.59-1.1 1.16-.83a5 5 0 0 1 0 9.02Z"></path>
|
||||
</svg>
|
||||
);
|
|
@ -16,17 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import { VoiceChannelIndicator } from "./components/VoiceChannelIndicator";
|
||||
import { VoiceChannelSection } from "./components/VoiceChannelSection";
|
||||
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
const UserModalSection = findByCodeLazy("heading:", "subheading:", "scrollIntoView:");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showInUserProfileModal: {
|
||||
|
@ -34,9 +37,9 @@ const settings = definePluginSettings({
|
|||
description: "Show a user's voice channel in their profile modal",
|
||||
default: true,
|
||||
},
|
||||
showVoiceChannelSectionHeader: {
|
||||
showVoiceChannelIndicator: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: 'Whether to show "IN A VOICE CHANNEL" above the join button',
|
||||
description: "Indicator in the member list wether a user is in a voice channel",
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
@ -44,8 +47,12 @@ const settings = definePluginSettings({
|
|||
interface UserProps {
|
||||
user: User;
|
||||
}
|
||||
interface VoiceChannelFieldProps {
|
||||
user: User;
|
||||
isPopout?: boolean;
|
||||
}
|
||||
|
||||
const VoiceChannelField = ErrorBoundary.wrap(({ user }: UserProps) => {
|
||||
const VoiceChannelField = ErrorBoundary.wrap(({ user, isPopout = false }: VoiceChannelFieldProps) => {
|
||||
const { channelId } = VoiceStateStore.getVoiceStateForUser(user.id) ?? {};
|
||||
if (!channelId) return null;
|
||||
|
||||
|
@ -58,11 +65,19 @@ const VoiceChannelField = ErrorBoundary.wrap(({ user }: UserProps) => {
|
|||
|
||||
const result = `${guild.name} | ${channel.name}`;
|
||||
|
||||
return (
|
||||
// when popout do padding and show header, when in modal no pad and no header
|
||||
return isPopout ? (
|
||||
<div style={{ padding: "4px 16px 8px" }}>
|
||||
<VoiceChannelSection
|
||||
channel={channel}
|
||||
label={result}
|
||||
showHeader
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<VoiceChannelSection
|
||||
channel={channel}
|
||||
label={result}
|
||||
showHeader={settings.store.showVoiceChannelSectionHeader}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -73,28 +88,67 @@ export default definePlugin({
|
|||
authors: [Devs.LordElias],
|
||||
settings,
|
||||
|
||||
start() {
|
||||
addDecorator("uvs-indicator", props => {
|
||||
if (!props.user) return null;
|
||||
|
||||
const { channelId } = VoiceStateStore.getVoiceStateForUser(props.user.id) ?? {};
|
||||
if (!channelId) return null;
|
||||
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (!channel) return null;
|
||||
|
||||
const guild = GuildStore.getGuild(channel.guild_id);
|
||||
|
||||
if (!guild) return null; // When in DM call
|
||||
|
||||
const result = `${guild.name} | ${channel.name}`;
|
||||
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<VoiceChannelIndicator tooltipText={result} channel={channel}></VoiceChannelIndicator>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeDecorator("uvs-indicator");
|
||||
},
|
||||
|
||||
patchModal({ user }: UserProps) {
|
||||
if (!settings.store.showInUserProfileModal)
|
||||
return null;
|
||||
if (!settings.store.showInUserProfileModal) return null;
|
||||
|
||||
return (
|
||||
<div className="vc-uvs-modal-margin">
|
||||
<UserModalSection heading="In a voice channel">
|
||||
<VoiceChannelField user={user} />
|
||||
</div>
|
||||
</UserModalSection>
|
||||
);
|
||||
},
|
||||
|
||||
patchProfilePopout: ({ user }: UserProps) => {
|
||||
const isSelfUser = user.id === UserStore.getCurrentUser().id;
|
||||
patchPopout: ({ user }: UserProps) => {
|
||||
return (
|
||||
<div className={isSelfUser ? "vc-uvs-popout-margin-self" : ""}>
|
||||
<VoiceChannelField user={user} />
|
||||
</div>
|
||||
<VoiceChannelField user={user} isPopout />
|
||||
);
|
||||
},
|
||||
|
||||
patches: [
|
||||
// @TODO Maybe patch UserVoiceShow in simplified profile popout
|
||||
// @TODO Patch new profile modal
|
||||
// in profile popout above message box
|
||||
{
|
||||
find: ":\"BITE_SIZE_POPOUT_RESTRICTED_BLOCKER_PROFILE",
|
||||
replacement: {
|
||||
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,guildId:\i,channelId:\i,onClose:\i}\)\]}\),/,
|
||||
replace: "$self.patchPopout(arguments[0]),$&",
|
||||
}
|
||||
},
|
||||
// in profile modal
|
||||
{
|
||||
find: "action:\"PRESS_APP_CONNECTION\"",
|
||||
replacement: {
|
||||
// match: /scroller,children:\[/, // at very top
|
||||
match: /setLineClamp:!\d}\),/, // after bio
|
||||
replace: "$&$self.patchModal(arguments[0]),"
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue