Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin): VCPanelSettings #3105

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/plugins/vcPanelSettings/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# VCPanelSettings

Shows voice/video settings directly in the "voice connected" panel.

![Plugin screenshot](https://github.com/user-attachments/assets/e63fef06-8ea9-491b-a855-0bdf82729b6c)

The settings that are shown are:
- Input device
- Input volume
- Output volume
- Output device
- Camera
251 changes: 251 additions & 0 deletions src/plugins/vcPanelSettings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import "./style.css";

import { definePluginSettings } from "@api/Settings";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { identity } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, Forms, Select, Slider, Text, useEffect, useState } from "@webpack/common";
import { Settings } from "Vencord";

const configModule = findByPropsLazy("getOutputVolume");

function OutputVolumeComponent() {
const [outputVolume, setOutputVolume] = useState(configModule.getOutputVolume());

useEffect(() => {
const listener = () => setOutputVolume(configModule.getOutputVolume());
FluxDispatcher.subscribe("AUDIO_SET_OUTPUT_VOLUME", listener);
});

return (
<>
{Settings.plugins.VCPanelSettings.showOutputVolumeHeader && <Forms.FormTitle>Output volume</Forms.FormTitle>}
<Slider maxValue={200} minValue={0} onValueRender={v => `${v.toFixed(0)}%`} initialValue={outputVolume} asValueChanges={volume => {
FluxDispatcher.dispatch({
type: "AUDIO_SET_OUTPUT_VOLUME",
volume
});
}} />
</>
);
}

function InputVolumeComponent() {
const [inputVolume, setInputVolume] = useState(configModule.getInputVolume());

useEffect(() => {
const listener = () => setInputVolume(configModule.getInputVolume());
FluxDispatcher.subscribe("AUDIO_SET_INPUT_VOLUME", listener);
});

return (
<>
{Settings.plugins.VCPanelSettings.showInputVolumeHeader && <Forms.FormTitle>Input volume</Forms.FormTitle>}
<Slider maxValue={100} minValue={0} initialValue={inputVolume} asValueChanges={volume => {
FluxDispatcher.dispatch({
type: "AUDIO_SET_INPUT_VOLUME",
volume
});
}} />
</>
);
}

function OutputDeviceComponent() {
const [outputDevice, setOutputDevice] = useState(configModule.getOutputDeviceId());

useEffect(() => {
const listener = () => setOutputDevice(configModule.getOutputDeviceId());
FluxDispatcher.subscribe("AUDIO_SET_OUTPUT_DEVICE", listener);
});

return (
<>
{Settings.plugins.VCPanelSettings.showOutputDeviceHeader && <Forms.FormTitle>Output device</Forms.FormTitle>}
<Select options={Object.values(configModule.getOutputDevices()).map((device: any /* i am NOT typing this*/) => {
return { value: device.id, label: Settings.plugins.VCPanelSettings.showOutputDeviceHeader ? device.name : `🔊 ${device.name}` };
})}
serialize={identity}
isSelected={value => value === outputDevice}
select={id => {
FluxDispatcher.dispatch({
type: "AUDIO_SET_OUTPUT_DEVICE",
id
});
}}>

</Select>
</>
);
}

function InputDeviceComponent() {
const [inputDevice, setInputDevice] = useState(configModule.getInputDeviceId());

useEffect(() => {
const listener = () => setInputDevice(configModule.getInputDeviceId());
FluxDispatcher.subscribe("AUDIO_SET_INPUT_DEVICE", listener);
});

return (
<div style={{ marginTop: "10px" }}>
{Settings.plugins.VCPanelSettings.showInputDeviceHeader && <Forms.FormTitle>Input device</Forms.FormTitle>}
<Select options={Object.values(configModule.getInputDevices()).map((device: any /* i am NOT typing this*/) => {
return { value: device.id, label: Settings.plugins.VCPanelSettings.showInputDeviceHeader ? device.name : `🎤 ${device.name}` };
})}
serialize={identity}
isSelected={value => value === inputDevice}
select={id => {
FluxDispatcher.dispatch({
type: "AUDIO_SET_INPUT_DEVICE",
id
});
}}>

</Select>
</div>
);
}

function VideoDeviceComponent() {
const [videoDevice, setVideoDevice] = useState(configModule.getVideoDeviceId());

useEffect(() => {
const listener = () => setVideoDevice(configModule.getVideoDeviceId());
FluxDispatcher.subscribe("MEDIA_ENGINE_SET_VIDEO_DEVICE", listener);
});

return (
<div style={{ marginTop: "10px" }}>
{Settings.plugins.VCPanelSettings.showVideoDeviceHeader && <Forms.FormTitle>Camera</Forms.FormTitle>}
<Select options={Object.values(configModule.getVideoDevices()).map((device: any /* i am NOT typing this*/) => {
return { value: device.id, label: Settings.plugins.VCPanelSettings.showVideoDeviceHeader ? device.name : `📷 ${device.name}` };
})}
serialize={identity}
isSelected={value => value === videoDevice}
select={id => {
FluxDispatcher.dispatch({
type: "MEDIA_ENGINE_SET_VIDEO_DEVICE",
id
});
}}>

</Select>
</div>
);
}

function VoiceSettings() {
const [showSettings, setShowSettings] = useState(Settings.plugins.VCPanelSettings.uncollapseSettingsByDefault);
return <div style={{ marginTop: "20px" }}>
<div style={{ marginBottom: "10px" }}>
<Link className="vc-panelsettings-underline-on-hover" style={{ color: "var(--header-secondary)" }} onClick={() => { setShowSettings(!showSettings); }}>{!showSettings ? "► Settings" : "▼ Hide"}</Link>
</div>

{
showSettings && <>
{Settings.plugins.VCPanelSettings.outputVolume && <OutputVolumeComponent />}
{Settings.plugins.VCPanelSettings.inputVolume && <InputVolumeComponent />}
{Settings.plugins.VCPanelSettings.outputDevice && <OutputDeviceComponent />}
{Settings.plugins.VCPanelSettings.inputDevice && <InputDeviceComponent />}
{Settings.plugins.VCPanelSettings.camera && <VideoDeviceComponent />}
</>
}
</div>;
}

export default definePlugin({
name: "VCPanelSettings",
description: "Control voice settings right from the voice panel",
authors: [Devs.nin0dev],
settings: definePluginSettings({
title1: {
type: OptionType.COMPONENT,
component: () => <Text style={{ fontWeight: "bold", fontSize: "1.27rem" }}>Appearance</Text>,
description: ""
},
uncollapseSettingsByDefault: {
type: OptionType.BOOLEAN,
default: false,
description: "Automatically uncollapse voice settings by default"
},
title2: {
type: OptionType.COMPONENT,
component: () => <Text style={{ fontWeight: "bold", fontSize: "1.27rem" }}>Settings to show</Text>,
description: ""
},
outputVolume: {
type: OptionType.BOOLEAN,
default: true,
description: "Show an output volume slider"
},
inputVolume: {
type: OptionType.BOOLEAN,
default: true,
description: "Show an input volume slider"
},
outputDevice: {
type: OptionType.BOOLEAN,
default: true,
description: "Show an output device selector"
},
inputDevice: {
type: OptionType.BOOLEAN,
default: true,
description: "Show an input device selector"
},
camera: {
type: OptionType.BOOLEAN,
default: false,
description: "Show a camera selector"
},
title3: {
type: OptionType.COMPONENT,
component: () => <Text style={{ fontWeight: "bold", fontSize: "1.27rem" }}>Headers to show</Text>,
description: ""
},
showOutputVolumeHeader: {
type: OptionType.BOOLEAN,
default: true,
description: "Show header above output volume slider"
},
showInputVolumeHeader: {
type: OptionType.BOOLEAN,
default: true,
description: "Show header above input volume slider"
},
showOutputDeviceHeader: {
type: OptionType.BOOLEAN,
default: false,
description: "Show header above output device selector"
},
showInputDeviceHeader: {
type: OptionType.BOOLEAN,
default: false,
description: "Show header above input device selector"
},
showVideoDeviceHeader: {
type: OptionType.BOOLEAN,
default: false,
description: "Show header above camera selector"
},
}),
renderVoiceSettings() { return <VoiceSettings />; },
patches: [
{
find: "this.renderChannelButtons()",
replacement: {
match: /this.renderChannelButtons\(\)/,
replace: "this.renderChannelButtons(), $self.renderVoiceSettings()"
}
}
]
});
3 changes: 3 additions & 0 deletions src/plugins/vcPanelSettings/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vc-panelsettings-underline-on-hover:hover {
text-decoration: underline;
}
Loading