diff --git a/package.json b/package.json index 750cb71e4..e6fc8f23f 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "stylelint-config-standard": "^33.0.0", "tsx": "^3.12.7", "type-fest": "^3.9.0", + "typed-emitter": "^2.1.0", "typescript": "^5.0.4", "zip-local": "^0.3.5", "zustand": "^3.7.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96562d579..935a6449a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - patchedDependencies: '@types/less@3.0.6': hash: krcufrsfhsuxuoj7hocqugs6zi @@ -132,6 +128,9 @@ devDependencies: type-fest: specifier: ^3.9.0 version: 3.9.0 + typed-emitter: + specifier: ^2.1.0 + version: 2.1.0 typescript: specifier: ^5.0.4 version: 5.0.4 @@ -2594,6 +2593,14 @@ packages: queue-microtask: 1.2.3 dev: true + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + requiresBuild: true + dependencies: + tslib: 2.6.2 + dev: true + optional: true + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true @@ -2889,6 +2896,11 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + optional: true + /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -2942,6 +2954,12 @@ packages: engines: {node: '>=14.16'} dev: true + /typed-emitter@2.1.0: + resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} + optionalDependencies: + rxjs: 7.8.1 + dev: true + /typescript@5.0.4: resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} @@ -3123,3 +3141,7 @@ packages: name: gifenc version: 1.0.3 dev: false + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/src/equicordplugins/betterMicrophone.desktop/components/MicrophoneSettingsModal.tsx b/src/equicordplugins/betterMicrophone.desktop/components/MicrophoneSettingsModal.tsx new file mode 100644 index 000000000..b031fc0dc --- /dev/null +++ b/src/equicordplugins/betterMicrophone.desktop/components/MicrophoneSettingsModal.tsx @@ -0,0 +1,331 @@ +/* + * 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 . +*/ + +import { Flex } from "@components/Flex"; +import { Switch } from "@components/Switch"; +import { ModalSize } from "@utils/modal"; +import { Card, Forms, Select, Slider, TextInput, useEffect, useState } from "@webpack/common"; +import { SelectOption } from "@webpack/types"; + +import { + ProfilableStore, + SettingsModal, + SettingsModalCard, + SettingsModalCardItem, + SettingsModalCardRow, + SettingsModalProfilesCard, + validateNumberInput, + validateTextInputNumber +} from "../../philsPluginLibrary"; +import { Styles } from "../../philsPluginLibrary/styles"; +import { MicrophoneProfile, MicrophoneStore } from "../stores"; + +const simpleVoiceBitrates: readonly SelectOption[] = [ + { + label: "Normal", + value: 96 + }, + { + label: "Medium-High", + value: 160 + }, + { + label: "High", + value: 320 + }, + { + label: "Very-High", + value: 512 + } +] as const; + +export interface MicrophoneSettingsModalProps extends React.ComponentProps { + microphoneStore: ProfilableStore; + showInfo?: boolean; +} + +export const MicrophoneSettingsModal = (props: MicrophoneSettingsModalProps) => { + const { microphoneStore, showInfo } = props; + + const { + currentProfile, + simpleMode, + setSimpleMode, + deleteProfile, + duplicateProfile, + getCurrentProfile, + getDefaultProfiles, + getProfile, + getProfiles, + isCurrentProfileADefaultProfile, + profiles, + saveProfile, + setChannels, + setChannelsEnabled, + setCurrentProfile, + setFreq, + setFreqEnabled, + setPacsize, + setPacsizeEnabled, + setRate, + setRateEnabled, + setVoiceBitrate, + setVoiceBitrateEnabled + } = microphoneStore.use(); + + const { + name, + channels, + channelsEnabled, + freq, + freqEnabled, + pacsize, + pacsizeEnabled, + rate, + rateEnabled, + voiceBitrate, + voiceBitrateEnabled + } = currentProfile; + + const [isSaving, setIsSaving] = useState(false); + + const [rateInput, setRateInput] = useState(rate ? rate.toString() : ""); + const [freqInput, setFreqInput] = useState(freq ? freq.toString() : ""); + const [pacsizeInput, setPacsizeInput] = useState(pacsize ? pacsize.toString() : ""); + const [channelsInput, setChannelsInput] = useState(channels ? channels.toString() : ""); + + useEffect(() => { + setRateInput(rate ? rate.toString() : ""); + setFreqInput(freq ? freq.toString() : ""); + setPacsizeInput(pacsize ? pacsize.toString() : ""); + setChannelsInput(channels ? channels.toString() : ""); + }, [rate, freq, pacsize, channels]); + + const simpleToggle = + + Simple + setSimpleMode(checked)} /> + ; + + const settingsCardVoiceBitrateSimple = + setVoiceBitrateEnabled(status) + }}> + + ({ + label: name, + value: id + }))} + isSelected={value => audioSource === value} + select={value => setAudioSource(value)} + serialize={() => ""} + {...props} + > + ); +}; diff --git a/src/equicordplugins/betterScreenshare.desktop/components/OpenScreenshareSettingsButton.tsx b/src/equicordplugins/betterScreenshare.desktop/components/OpenScreenshareSettingsButton.tsx new file mode 100644 index 000000000..d12493a95 --- /dev/null +++ b/src/equicordplugins/betterScreenshare.desktop/components/OpenScreenshareSettingsButton.tsx @@ -0,0 +1,38 @@ +/* + * 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 . +*/ + +import { Button } from "@webpack/common"; +import React from "react"; + +import { openScreenshareModal } from "../modals"; + +export interface OpenScreenshareSettingsButtonProps { + title?: string; +} + +export const OpenScreenshareSettingsButton = (props: OpenScreenshareSettingsButtonProps) => { + return ( + + ); +}; diff --git a/src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx b/src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx new file mode 100644 index 000000000..ac529445a --- /dev/null +++ b/src/equicordplugins/betterScreenshare.desktop/components/ScreenshareSettingsModal.tsx @@ -0,0 +1,467 @@ +/* + * 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 . +*/ + +import { Flex } from "@components/Flex"; +import { Switch } from "@components/Switch"; +import { ModalSize, openModalLazy } from "@utils/modal"; +import { Button, Card, Forms, React, Select, Slider, TextInput, useEffect, useState } from "@webpack/common"; +import { SelectOption } from "@webpack/types"; + +import { MicrophoneSettingsModal } from "../../betterMicrophone.desktop/components"; +import { + MediaEngineStore, + openURL, + ProfilableStore, + SettingsModal, + SettingsModalCard, + SettingsModalCardItem, + SettingsModalCardRow, + SettingsModalProfilesCard, + types, + validateNumberInput, + validateTextInputNumber +} from "../../philsPluginLibrary"; +import { Styles } from "../../philsPluginLibrary/styles"; +import { PluginInfo } from "../constants"; +import { ScreenshareAudioProfile, ScreenshareAudioStore, ScreenshareProfile, ScreenshareStore } from "../stores"; + +const simpleResolutions: readonly (SelectOption & { value: types.Resolution; })[] = [ + { + label: "480p", + value: { + height: 480, + width: 720 + } + }, + { + label: "720p", + value: { + height: 720, + width: 1280 + } + }, + { + label: "1080p", + value: { + height: 1080, + width: 1920 + } + }, + { + label: "1440p", + value: { + height: 1440, + width: 2560 + } + }, + { + label: "2160p", + value: { + height: 2160, + width: 3840 + } + } +] as const; + +const simpleVideoBitrates: readonly SelectOption[] = [ + { + label: "Low", + value: 2500 + }, + { + label: "Medium", + value: 5000 + }, + { + label: "Medium-High", + value: 7500 + }, + { + label: "High", + value: 10000 + } +] as const; + +export interface ScreenshareSettingsModalProps extends React.ComponentProps { + screenshareStore: ProfilableStore; + screenshareAudioStore?: ProfilableStore; + onAudioDone?: () => void; +} + +export const ScreenshareSettingsModal = (props: ScreenshareSettingsModalProps) => { + const { screenshareStore, screenshareAudioStore, onAudioDone } = props; + + const { + currentProfile, + profiles, + simpleMode, + setVideoBitrateEnabled, + setVideoCodec, + setVideoCodecEnabled, + setFramerate, + setFramerateEnabled, + setHeight, + setKeyframeInterval, + setKeyframeIntervalEnabled, + setResolutionEnabled, + setVideoBitrate, + setWidth, + setCurrentProfile, + getProfile, + saveProfile, + setHdrEnabled, + setSimpleMode, + deleteProfile, + duplicateProfile, + getCurrentProfile, + getProfiles + } = screenshareStore.use(); + + + const { + name, + framerate, + framerateEnabled, + height, + keyframeInterval, + keyframeIntervalEnabled, + resolutionEnabled, + videoBitrate, + videoBitrateEnabled, + videoCodec, + videoCodecEnabled, + width, + hdrEnabled + } = currentProfile; + + const [videoCodecs, setVideoCodecs] = useState([]); + + const [isSaving, setIsSaving] = useState(false); + + const [textinputWidth, setTextinputWidth] = useState(width ? width.toString() : ""); + const [textinputHeight, setTextinputHeight] = useState(height ? height.toString() : ""); + const [textinputFramerate, setTextinputFramerate] = useState(framerate ? framerate.toString() : ""); + const [textinputKeyframeInterval, setTextinputKeyframeInterval] = useState(keyframeInterval ? keyframeInterval.toString() : ""); + + useEffect(() => { + setTextinputWidth(width ? width.toString() : ""); + setTextinputHeight(height ? height.toString() : ""); + setTextinputFramerate(framerate ? framerate.toString() : ""); + setTextinputKeyframeInterval(keyframeInterval ? keyframeInterval.toString() : ""); + }, [width, height, framerate, keyframeInterval]); + + useEffect(() => { + (async () => { + const mediaEngine = MediaEngineStore.getMediaEngine(); + + const stringifiedCodecs: types.CodecCapabilities[] = JSON.parse( + await new Promise(res => mediaEngine.getCodecCapabilities(res)) + ); + + setVideoCodecs(stringifiedCodecs); + })(); + }, []); + + const settingsCardResolutionSimple = + setResolutionEnabled(status) + }}> + + void setVideoBitrate(value)} + isSelected={(value: number) => videoBitrate === value} + serialize={() => ""} /> + + ; + + const settingsCardResolution = + setResolutionEnabled(status), + disabled: isSaving + }}> + + validateTextInputNumber(value) && setTextinputWidth(value)} + onBlur={e => { + const result = validateNumberInput(e.target.value); + setWidth(result); + setTextinputWidth(result ? result.toString() : ""); + }} /> + + + validateTextInputNumber(value) && setTextinputHeight(value)} + onBlur={e => { + const result = validateNumberInput(e.target.value); + setHeight(result); + setTextinputHeight(result ? result.toString() : ""); + }} /> + + ; + + const settingsCardItemFramerate = + + validateTextInputNumber(value) && setTextinputFramerate(value)} + onBlur={e => { + const result = validateNumberInput(e.target.value); + setFramerate(result); + setTextinputFramerate(result ? result.toString() : ""); + }} /> + ; + + const settingsCardFramerateProps: React.ComponentProps = { + title: "Framerate", + switchEnabled: true, + switchProps: { + checked: framerateEnabled ?? false, + disabled: isSaving, + onChange: status => setFramerateEnabled(status) + } + }; + + const settingsCardFramerate = + + {settingsCardItemFramerate} + ; + + const settingsCardFramerateSimple = + + {settingsCardItemFramerate} + ; + + const settingsCardKeyframeInterval = + setKeyframeIntervalEnabled(status) + }}> + + validateTextInputNumber(value) && setTextinputKeyframeInterval(value)} + onBlur={e => { + const result = validateNumberInput(e.target.value); + setKeyframeInterval(result); + setTextinputKeyframeInterval(result ? result.toString() : ""); + }} /> + + ; + + const settingsCardVideoBitrate = + setVideoBitrateEnabled(status) + }}> + +
+ setVideoBitrate(value)} + initialValue={videoBitrate || 500} + minValue={500} + maxValue={10000} + markers={[500, 10000]} + onValueRender={value => `${value.toFixed(0)}kb/s`} /> +
+
+
; + + const settingsCardAudioProps: React.ComponentProps = { + title: "Audio Settings" + }; + + const settingsCardItemAudio = + +