This commit is contained in:
LordElias 2024-09-16 12:28:53 +00:00 committed by GitHub
commit e6a9e2ac24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 249 additions and 57 deletions

View file

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

View file

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

View file

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

View file

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

View 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>
);

View 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>
);

View file

@ -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]),"
}
}
],
});